Repository navigation
[core][spark]: support rewrite_manifest procedure - #9195
zhongyujiang wants to merge 7 commits into
Conversation
2b33320 to
960fbc2
Compare
|
@JingsongLi Hi, can you help review this when you have time? I added this to sort manifest entries, which speeds up both scan and manifest commit a lot. |
| procedureBuilders.put("reset_consumer", ResetConsumerProcedure::builder); | ||
| procedureBuilders.put("mark_partition_done", MarkPartitionDoneProcedure::builder); | ||
| procedureBuilders.put("compact_manifest", CompactManifestProcedure::builder); | ||
| procedureBuilders.put("rewrite_manifest", RewriteManifestProcedure::builder); |
There was a problem hiding this comment.
Added rewrite_manifest instead of reusing the old compact_manifest, since this operation does not necessarily produce a compaction.
JingsongLi
left a comment
There was a problem hiding this comment.
The core replacement protocol and sort-key behavior look consistent, and the 12 focused core tests pass. I found two blockers around public API compatibility and Spark task-attempt output ownership.
| FileStoreTable t = tableRef; | ||
| FileStorePathFactory pathFactory = | ||
| t.store().pathFactory(); | ||
| Path manifestPath = pathFactory.newManifestFile(); |
There was a problem hiding this comment.
[P2] Make task-attempt manifest output abortable
Each Spark task writes its final manifest directly into the table metadata directory. If a later partition fails, collect() throws before the driver receives newManifests, so all manifests already completed by successful partitions are leaked. A retried/speculative attempt can likewise create a second file while only one attempt’s metadata is returned. This can leak a full rewrite’s worth of manifests on every failed run. Please use task-attempt temporary output plus an attempt commit/abort protocol (and clean all produced paths when the stage/commit fails), and add a test that injects a failure after another partition has finished writing.
There was a problem hiding this comment.
Thanks for the review!
I've added a catch block in mapPartitions that calls writer.abort() to delete the manifest file when a task fails (including task-killed scenarios).
I didn't make the writer output to a temorary output because it would require an extra rename during the final commit, and I feel this overhead is not justified since the probability of collect() failing is relatively low. What do you think?
JingsongLi
left a comment
There was a problem hiding this comment.
Can you take a look to BatchTableCommit.compactManifests?
I'm aware that |
Add a rewrite_manifest Spark procedure that reads all manifest entries, sorts them globally by partition -> bucket -> level -> fileName (canceling ADD/DELETE pairs of the same file along the way) and writes them back as new manifest files so that ManifestFileMeta statistics become more compact for scan pruning. The sort runs distributed via a Spark sortByKey shuffle. - ManifestEntrySortKey: serializable sort key with byte[] partition for Kryo compatibility and lazy RecordComparator - FileStoreCommit#replaceManifest: optimistic-concurrency commit with conflict detection (containsAll) and concurrent delta preservation - RewriteManifestProcedure: distributed sortByKey + streaming ADD/DELETE cancellation + where clause + auto parallelism - FormatTableCommit: add unsupported replaceManifests - register rewrite_manifest in SparkProcedures - tests: ManifestEntrySortKeyTest (9), FileStoreCommitTest replaceManifest conflict scenarios (3), RewriteManifestProcedureTest (8) Co-Authored-By: Claude <noreply@anthropic.com>
rewritten_manifests_count should be the number of input manifests rewritten (currentManifests), not the output. added_manifests_count should be the number of new manifests produced (newManifests), not the net delta. Also rename the post-filter variable to manifestsToRewrite for clarity. Co-Authored-By: Claude <noreply@anthropic.com>
Update the assertion to match the corrected semantics: rewritten_manifests_count == input manifest count, added_manifests_count == output manifest count (> 0). Co-Authored-By: Claude <noreply@anthropic.com>
The test expected compactManifest to change manifest file names, but with default settings the merge may be a no-op (same manifests, no new snapshot). Use a small manifest.target-file-size and manifest.full-compaction-file-size to force compactManifest to actually rewrite the manifests, making the stale manifestsBefore absent from the current base. Co-Authored-By: Claude <noreply@anthropic.com>
Apache paimon master uses ManifestAvroWriter (not ManifestEntryWriter). Replace createManifestEntryWriter(Path) with createAvroWriter(Path), and use result().isEmpty() + abort() for empty manifest cleanup instead of recordCount() + path().getName(). Co-Authored-By: Claude <noreply@anthropic.com>
compactManifest may be a no-op (same manifests, no new snapshot) so the stale manifestsBefore stays in currentBase. Instead, manually rewrite the manifests to new files via manifestFile.write(), then replaceManifest — this changes file names, guaranteeing the subsequent replaceManifest with the stale manifestsBefore throws "Manifest conflict". Co-Authored-By: Claude <noreply@anthropic.com>
When a Spark task fails during mapPartitions, call writer.abort() to delete the partially written manifest file instead of leaving it as an orphan. This addresses the reviewer's concern about leaked manifests on task failure. Co-Authored-By: Claude <noreply@anthropic.com>
c1416aa to
ea88bc6
Compare
|
Hi @JingsongLi, comments has been resolved, can you take a look when you have time? Thanks! |
|
@zhongyujiang The current manifest sort is already extremely fast; you might want to test it against your internal large tables. |
|
Take a look to #9802 , If it doesn't meet your needs, submit a new one. |
Motivation
Manifest entries are written in commit order, which can become unordered over time. This makes ManifestFileMeta statistics (partitionStats / minBucket / maxBucket / minLevel / maxLevel) loose, reducing manifest-level pruning during scan.
This PR adds a rewrite_manifest Spark procedure that reads all manifest entries, sorts them globally by partition -> bucket -> level -> fileName (canceling ADD/DELETE pairs of the same file), and writes them back as new manifest files with compact statistics.
Usage
An optional where clause restricts the rewrite to manifests whose partition stats may match the predicate; the remaining manifests are left untouched.
Implementation
Tests