fix: track batch position delete references - #842
Conversation
|
Hi @wgtmac, gentle ping for a review when you have a moment. This has been sitting for about two weeks; all CI checks are green and it merges cleanly against the latest main. The change tracks referenced data-file paths for batch position-delete writes so |
|
This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 1 week if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the dev@iceberg.apache.org list. Thank you for your contributions. |
| ArrowArrayViewSetArray(&array_view, data, &error), error); | ||
|
|
||
| const auto* path_view = array_view.children[0]; | ||
| if (ArrowArrayViewComputeNullCount(path_view) != 0) { |
There was a problem hiding this comment.
ArrowArrayViewComputeNullCount(path_view) scans the child view length, not necessarily the parent batch length. Please check it only for i less than data->length, and apply the same check to pos field.
There was a problem hiding this comment.
Done in ce7184a. The null check is now per row over i < data->length using ArrowArrayViewIsNull, applied to both the file_path and the pos child views, so it no longer depends on the child view length.
| // TODO(anyone): Extract file paths from ArrowArray to update referenced_paths_. | ||
| return writer_->Write(data); | ||
|
|
||
| ArrowSchema arrow_schema; |
There was a problem hiding this comment.
Every Write rebuilds the same schema and allocates a fresh ArrowArrayView. The schema is immutable for this writer, so this adds allocator work to every batch. Perhaps we can initialize the schema and view once in Impl and only rebind the incoming array here.
There was a problem hiding this comment.
Done in ce7184a. The delete schema is immutable, so it is converted to Arrow once in Impl::InitSchema() and the ArrowArrayView is initialized from it there. Write now only rebinds the existing view with ArrowArrayViewSetArray, and FlushBuffer reuses the same Arrow schema instead of rebuilding it. The view and the schema are released in Impl's destructor.
| return InvalidArrowData("Position delete file paths must not contain null values"); | ||
| } | ||
|
|
||
| std::set<std::string> pending_paths; |
There was a problem hiding this comment.
pending_paths allocates a tree node and copies each unique path for every batch, then merges into another set. Batch writes can be frequent so a reusable scratch set or a vector plus post-write insertion would avoid much of this churn while keeping the failure-safe delayed merge.
There was a problem hiding this comment.
Done in ce7184a, with a slightly different approach. A reusable scratch set does not actually avoid the churn, since clear() frees every node, so the allocations would keep coming for paths that are already tracked.
Instead the paths are recorded optimistically: referenced_paths_ is now std::set<std::string, std::less<>>, so a string_view that is already tracked costs only a lookup and no allocation at all. The iterators of the entries inserted by the current batch are kept in a small reused vector and erased again if the batch is rejected, which keeps the failure-safe delayed merge: a rejected batch still leaves no trace in the metadata.
| EXPECT_EQ(data_file->referenced_data_file.value(), "data_file_1.parquet"); | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, WriteBatchDataForMultipleFiles) { |
There was a problem hiding this comment.
This is one batch containing two paths, not multiple successful Write calls. A bug that replaces referenced_paths_ instead of unioning across batches would still pass. Add two successful batches with disjoint paths.
There was a problem hiding this comment.
Done in ce7184a. WriteBatchDataForMultipleFiles now performs two successful writes with disjoint paths, so a bug that replaced referenced_paths_ instead of unioning across batches would fail the test. It also asserts the public WriteResult::referenced_data_files.
| ArrowArray bad_array; | ||
| ASSERT_TRUE(::arrow::ExportArray(*bad_data, &bad_array).ok()); | ||
| internal::ArrowArrayGuard bad_array_guard(&bad_array); | ||
| ASSERT_THAT(writer->Write(&bad_array), IsError(ErrorKind::kInvalidArrowData)); |
There was a problem hiding this comment.
This looks odd to me because we continue to use a failed writer which should not happen in production. And this does actually verify the case name FailedBatchWriteDoesNotTrackReferencedFiles.
There was a problem hiding this comment.
Agreed, reworked in ce7184a. The test now writes a successful batch first and a rejected batch last, so the writer is never used after a failure. The rejected batch references a valid path and is then rejected by the null path, and after Close() the test verifies that only data_file_1.parquet is tracked. Because the bad path is inserted before the batch fails, this also covers the rollback path.
|
|
||
| auto metadata_result = writer->Metadata(); | ||
| ASSERT_THAT(metadata_result, IsOk()); | ||
| EXPECT_FALSE(metadata_result.value().data_files[0]->referenced_data_file.has_value()); |
There was a problem hiding this comment.
This only checks the per-file hint. WriteResult also exposes referenced_data_files; assert that public result as well if this writer is meant to satisfy the FileWriter contract.
There was a problem hiding this comment.
Done in ce7184a. Metadata() now populates WriteResult::referenced_data_files, and both WriteBatchThenDeleteTracksAllReferencedFiles and WriteBatchDataForMultipleFiles assert it in addition to the per-file hint.
| HasErrorMessage("Position delete file paths must not contain null values")); | ||
| } | ||
|
|
||
| TEST_F(PositionDeleteWriterTest, WriteBatchRejectsNullData) { |
There was a problem hiding this comment.
This only exercises a one-line null precondition and does not touch batch paths or metadata. It is low-value so please consider dropping it or folding it into a broader invalid-input test.
There was a problem hiding this comment.
Done in ce7184a. WriteBatchRejectsNullData and WriteBatchRejectsNullFilePath are folded into a single WriteBatchRejectsInvalidInput covering a null array, a null file path, a null position, and an empty file path.
| std::set<std::string> pending_paths; | ||
| for (int64_t i = 0; i < data->length; ++i) { | ||
| auto path = ArrowArrayViewGetStringUnsafe(path_view, i); | ||
| if (path.size_bytes == 0) { |
There was a problem hiding this comment.
Should we error out in this case?
There was a problem hiding this comment.
Good catch, it is now an error in ce7184a: InvalidArrowData("Position delete file paths must not be empty"). Silently tracking an empty path would have been wrong, and it is covered by WriteBatchRejectsInvalidInput.
Take ownership before validation so early-return paths honor the FileWriter contract and do not leak Arrow buffers. Assert that rejected sliced and null-path batches are released.
Build the Arrow delete schema and its array view once in the writer instead of rebuilding them for every batch, and validate the batch per row: null file paths and null positions are rejected, and an empty file path is now an error rather than an empty referenced path. Record referenced paths as they are seen using a transparent lookup and roll back the entries added by a batch that is rejected. A batch that keeps referencing the same file now costs a lookup instead of a scratch allocation per unique path, while a rejected batch still leaves no trace in the metadata. WriteResult also exposes the public referenced_data_files list now. Tests: union disjoint paths across two successful batches, assert referenced_data_files instead of only the per-file hint, and fold the null input cases into one invalid-input test that covers empty paths too.
39b9b78 to
ce7184a
Compare
Summary
Testing
pre-commit run --files src/iceberg/data/position_delete_writer.cc src/iceberg/test/data_writer_test.ccbuild-gcc14/src/iceberg/test/data_test(159 tests passed)git diff --check