Skip to content

Core: Write rewrite manifests concurrently but commit one at a time - #17986

Draft
smaheshwar-pltr wants to merge 8 commits into
apache:mainfrom
smaheshwar-pltr:codex/serialize-commit-service
Draft

smaheshwar-pltr wants to merge 8 commits into
apache:mainfrom
smaheshwar-pltr:codex/serialize-commit-service

Conversation

@smaheshwar-pltr

@smaheshwar-pltr smaheshwar-pltr commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Why

With partial-progress.enabled, each rewrite worker commits its own batch, so up to max-concurrent-file-group-rewrites commits to the same table run at once. One wins each round. The rest back off and retry, writing a new manifest list each time and re-filtering any input manifest that another commit replaced. A batch that runs out of commit.retry.num-retries is cleaned up and its rewrite is lost. With the default 4 retries this happens even without other writers: in benchmark runs with 4 to 16 workers, main lost groups in 54 of 75.

Change

The rewrite commit managers call apply() before taking a lock and commit() inside it. apply() runs concurrently and the producer caches the manifests it writes. Under the lock, commit() refreshes, validates, writes the manifest list and swaps metadata, and only re-filters or re-merges manifests that another commit changed in the meantime. A failed apply() is logged and commit() applies again, so failures and cleanup behave as before.

Applying again after a failed delete manifest write counted the added delete files twice in the snapshot summary, so MergingSnapshotProducer now rebuilds that summary with the manifests.

Locking the whole commit() would be simpler, but it serializes manifest writing too. The "whole-commit lock" column below shows the cost, measured with a variant that commits ready batches one at a time while the other workers keep rewriting.

Benchmark

Spark 4.1 local[16], HadoopCatalog, one group per commit, median of 5 runs in seconds. Groups are 4 small files that rewrite in about 70 ms, so runs are commit-bound. Delays simulate object-store latency on writes; reads are not delayed.

Scenario main whole-commit lock this PR
No added latency, 16 workers, 4 retries 7.6 3.3 2.5
100 ms manifest writes, 8 workers, 20 retries 6.5 9.7 2.7
500 ms manifest writes, 16 workers finishing together, 20 retries 13.6 17.8 2.5
100 ms on all metadata writes, 16 workers, 20 retries 34.4 16.3 9.2
100 ms manifest writes, 1 worker 11.9 11.9 13.4
100 ms on all metadata writes, 1 worker 18.3 18.3 23.0

With this change no commit retried and no groups were lost. In the first row main also lost 2.8 of 32 groups per run. With one worker nothing overlaps the early apply(), so its extra refresh, validation and manifest list write show up: about 50 ms per commit, or 150 ms when manifest list writes are slow. Rewrites without partial progress and Flink's rewrite committer pay this once per commit.

manifests-replaced in the snapshot summary and commit report can over-count when a manifest that the early apply() filtered or merged is replaced before commit(). Retries on main already do this; I'll fix the count separately.

Testing

TestRewriteDataFilesCommitManager and TestRewritePositionDeletesCommitManager commit 8 groups concurrently with commit.retry.num-retries=0, which fails on main with CommitFailedException, and check that concurrent and failed commits leave no unreferenced metadata files. A new TestRowDelta case covers the summary fix. The Spark partial-progress test no longer tolerates a failed commit.


AI Disclosure

  • Model: Claude Opus 5.5 (core change, tests, benchmark); [unknown - human to fill in] via Codex (first version, Spark test changes)
  • Platform/Tool: Codex, Claude Code
  • Human Oversight: [unknown - human to fill in]
  • Prompt Summary: Stop partial-progress rewrite commits from conflicting with each other, keeping the change small and the manifest writing concurrent.

🤖 Generated with Claude Code

Give one offering thread non-blocking drain ownership so ready groups never commit concurrently and competing offers do not wait. Reject non-positive batch sizes so the drain condition always makes progress.

Generated-by: OpenAI Codex
Apply the zero-failure assertions from Spark 4.1 to the identical 3.5, 4.0, and 4.2 tests. Also remove an unused TrackingCommitService constructor from the core test.
Give the blocking file-group fixture a constant string representation so debug logging does not release the extraction latch before enqueueing.

Generated-by: OpenAI Codex
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

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.

@github-actions github-actions Bot added the stale label Oct 9, 2026
@smaheshwar-pltr

Copy link
Copy Markdown
Contributor Author

Not stale

@github-actions github-actions Bot removed the stale label Oct 14, 2026
smaheshwar-pltr and others added 3 commits October 19, 2026 21:25
Concurrent partial-progress commits to the same table conflict, retry
with backoff, and can drop rewritten groups when retries run out. Commit
under a lock in the rewrite commit managers, applying each rewrite first
so manifest writing stays concurrent and only the metadata update is
serialized. This replaces the BaseCommitService lock, which serialized
manifest writing as well.

Generated-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Log a failed early apply at debug level since commit() reports any
failure that persists. Check Parquet manifests on v4 tables and that
concurrent commits leave no unreferenced metadata files.

Generated-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@smaheshwar-pltr smaheshwar-pltr changed the title Core: Serialize commits in BaseCommitService Core: Write rewrite manifests concurrently but commit one at a time Oct 20, 2026
smaheshwar-pltr and others added 2 commits October 20, 2026 13:57
If writing new delete manifests failed, applying again counted the added
delete files twice in the snapshot summary. The early apply in the
rewrite commit managers makes this retry reachable.

Generated-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add commit manager tests for position deletes, describe what commit()
reuses from the early apply, and drop a data-file test that the
concurrent test already covers.

Generated-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant