Hi all, A quick status update on this thread, and a request for reviewers.
The first step, merging concurrently added DVs for pure delete commits ( https://github.com/apache/iceberg/pull/17754), has been reviewed by Huaxin and Uros. All comments are addressed, including the case where the concurrent DV disappears between retry attempts, and CI is green. Huaxin asked for a second committer to look at the commit/retry semantics in MergingSnapshotProducer before it goes in. Eduard, Amogh (whom Huaxin tagged on the PR), Ryan, Russell, Szehon: since you have worked on the DV commit path (e.g. the duplicate-DV merge in #15006), would one of you be able to take a look? Once #17754 lands, the next step is the position-overlap check that lets the same merge apply to UPDATE/MERGE when the newly deleted positions are disjoint, as described in https://github.com/apache/iceberg/issues/18020. Feedback on the design there is welcome in parallel. Thanks, EJ On Mon, Sep 14, 2026 at 4:23 PM EJ Song <[email protected]> wrote: > Thanks both, and thanks for the support on the direction. > > On magnitude, agreed it depends on the workload. The design keeps the > added cost targeted: the finer-grained check only does work for a data file > where both the committing operation and a concurrent commit added a DV, and > that overlap is determined from manifest metadata (partition + column > bounds, then the referenced data file) before anything is read. Commits > that touch disjoint files fall through unchanged, so the cost is only paid > in the case that fails outright today. > > On the retry question (option 1 vs 2), I'd do the union in the validation > path (option 1), and I think that already satisfies the "retry until > resolved or fail" model. validateAddedDVs runs inside the existing commit > loop after refresh() on every attempt, so on a genuine version conflict the > loop retries and the union is recomputed against the freshly refreshed > state; a disjoint overlap unions in place and the commit proceeds, and a > true position overlap throws a ValidationException and fails. The outer > retry on CommitFailedException stays as-is for real version conflicts; we > just resolve the DV overlap in validate() rather than making the overlap > itself a separate retryable loop-back, which is what avoids recomputing the > whole operation on a spurious conflict. > > For concrete grounding: the pure-delete case (concurrent DELETEs producing > DVs for the same file, which are commutative) is already implemented in > #17754, which merges the concurrent DVs unconditionally. The extension here > is the position-overlap check that lets the same merge apply to operations > that also rewrite rows (UPDATE / MERGE), where the union is only safe when > the newly deleted positions are disjoint. > > On gating the added granularity under a table property, I think the > DV-bitmap tier may not need one, since the cost is already bounded by the > layering above: an operation gate, then a conflicting-file metadata gate, > then a single small roaring-bitmap read only on genuine overlap > (disjoint-file commits read nothing). The tier that would actually read > data files (value-exact comparison, to resolve overlaps a bitmap can't > decide) is separate and only invoked when necessary; that's where > per-commit data reads grow, and where a table property to gate it makes > sense. It can be introduced together with that tier. > > Thanks > EJ > > On Thu, Sep 10, 2026 at 5:07 PM huaxin gao <[email protected]> wrote: > >> Hi EJ, >> >> Thanks for initiating the discussion and +1 on the direction. A few notes >> on your questions. >> >> (1) Yes, it is worth doing. The cost is that it adds driver-side I/O >> during >> the commit: one read per data file in scope, while the rest of the job >> waits. That is probably fine for DV bitmaps, which are small, but it grows >> with the number of files in scope, and the value-comparison tier would >> mean >> reading data files rather than bitmaps. So I would suggest gating the new >> granularity under a table property. >> >> (2) Xiening suggested the retry path — I'd lean the other way and do it in >> the validation path, so we don't have to abandon the attempt and loop >> back. >> Validation already runs after a refresh() on every attempt, so the union >> gets recomputed against fresh state anyway. >> >> Also, most of the read-merge-write code already exists and already runs in >> the commit path on the driver: MergingSnapshotProducer.mergeDVs() into >> DVUtil.mergeAndWriteDVsIfRequired. >> >> (3) Three pieces of prior art: >> https://github.com/apache/iceberg/pull/17754 — does this for pure delete >> commits. >> https://github.com/trinodb/trino/pull/30860 — skipped the coarse delete >> check >> outright for v3 UPDATE/MERGE >> https://github.com/apache/iceberg/issues/14613 — earlier row-level >> concurrency >> discussion, closed as stale. >> >> Huaxin >> >> On Tue, Sep 8, 2026 at 2:47 PM Xiening Dai <[email protected]> wrote: >> >>> Hi EJ, >>> >>> I think in general this is a good idea. A finer-granularity conflict >>> resolution is going to help the overall commit performance. Whether or not >>> it will make a significant difference would depend on the user scenario. >>> >>> For your 2nd question, I think you always need to re-try commit until >>> either 1) there's real conflict you cannot resolve and transaction has to >>> fail, or 2) there's no more version conflict and you commit successfully. >>> >>> >>> On 2026/09/08 20:44:57 EJ Song wrote: >>> > Hi all, >>> > >>> > I'd like to gauge appetite for making concurrent UPDATE/MERGE on V3 >>> > deletion vectors fail less often when the operations touched disjoint >>> rows. >>> > >>> > Background: with one DV per data file, validateAddedDVs fails the >>> commit >>> > when a concurrent op added a DV for a file this op also DV'd, at file >>> > granularity, regardless of whether the deleted positions actually >>> overlap. >>> > That ValidationException is not retryable (the commit loop is >>> onlyRetryOn >>> > CommitFailedException), so the whole operation is recomputed and >>> re-run. >>> > For two operations that modified disjoint rows in the same file, that >>> is >>> > wasted work on a spurious conflict. Delta Lake's row-level concurrency >>> > already reduces this to row granularity on DV-enabled tables; I would >>> like >>> > to pursue the same for Iceberg V3 DVs. >>> > >>> > Core idea: refine validateAddedDVs from file granularity to position >>> > granularity. When a concurrent DV exists for a file this op also DV'd, >>> > compare the newly-deleted positions: >>> > >>> > (dv_concurrent intersect dv_self) minus dv_base == empty >>> > >>> > If disjoint, union the two into a single DV and proceed; if they >>> overlap, >>> > fail as today. This reuses existing DV primitives (DVUtil.readDV, >>> > PositionDeleteIndex.merge, BaseDVFileWriter); the new code is a >>> > bitmap-overlap test plus wiring a cross-operation union into the >>> validation >>> > path. It does not change isolation semantics, which stay in >>> > validateNoConflictingDeletes and validateNoConflictingData. >>> > >>> > I wrote up the full design (soundness conditions, a sequenced set of >>> > follow-up refinements, non-goals including compaction-vs-DML which is >>> not >>> > reconciled today, and configuration) as a GitHub issue: >>> > https://github.com/apache/iceberg/issues/18020 >>> > >>> > Questions: >>> > >>> > 1. Is the file-level false-failure in validateAddedDVs worth refining >>> to >>> > position granularity? >>> > 2. Should the disjoint-case union happen in the validation path (union >>> in >>> > place), or via a retryable commit that re-reads and unions the base DV >>> on >>> > each refreshed apply? (Details in the issue.) >>> > 3. Any prior art or earlier design decision I have missed? >>> > >>> > Thanks, >>> > EJ >>> > >>> >>
