Repository navigation
Description
Apache Iceberg version
main (development)
Please describe the bug 🐞
On retry, Transaction._rebuild_snapshot_updates resolves a single CommitWindow from the first snapshot producer's
branch and starting snapshot, then uses it for every producer:
starting_id = self._snapshot_producers[0]._starting_snapshot_id if self._snapshot_producers else None
target_branch = self._snapshot_producers[0]._target_branch if self._snapshot_producers else None
commit_window = CommitWindow.resolve(self._table.metadata, starting_id, target_branch)
for producer in self._snapshot_producers:
producer._commit_window = commit_windowIn a transaction that writes more than one branch, a validating producer (delete/overwrite) on a later branch is
checked against the first branch's commits. If the first branch didn't change, the window is empty and validation is
skipped, so a serializable delete commits over a conflicting concurrent append it never saw.
Blocked by #4315 (fix for #4183). Before that fix, the second branch's AssertRefSnapshotId was dropped, so the
conflict never triggered a retry and this path was unreachable; #4315 is what exposes it. I'm not folding this into
#4315, to keep that PR scoped to #4183, but I'm willing to work on this one and will open a PR on top of #4315 once it
lands.
import tempfile
import pyarrow as pa
from pyiceberg.catalog.sql import SqlCatalog
cat = SqlCatalog("c", uri="sqlite:///:memory:", warehouse=f"file://{tempfile.mkdtemp()}")
cat.create_namespace("ns")
t = cat.create_table("ns.t", schema=pa.schema([("x", pa.int64())]),
properties={"commit.retry.min-wait-ms": "1", "commit.retry.max-wait-ms": "2"})
t.append(pa.table({"x": [0, 1]}))
t.manage_snapshots().create_branch(t.current_snapshot().snapshot_id, "b").commit()
tx = t.transaction()
tx.append(pa.table({"x": [9]})) # remove this line and the commit below raises ValidationException
tx.delete("x == 1", branch="b")
cat.load_table("ns.t").append(pa.table({"x": [1]}), branch="b") # concurrent matching append to b
tx.commit_transaction()
# commits; should raise ValidationException like the single-branch delete does
print(sorted(cat.load_table("ns.t").scan().use_ref("b").to_arrow()["x"].to_pylist())) # [0, 1]Tracing CommitWindow.resolve shows the retry resolving branch='main' (empty window) for the delete on b. Without
the main append, it resolves branch='b' (non-empty) and validation raises.
Resolving a window per producer from its own _starting_snapshot_id and _target_branch would scope the validation
correctly.
tested on main with #4315 applied
Willingness to contribute
- I can contribute a fix for this bug independently
- I would be willing to contribute a fix for this bug with guidance from the Iceberg community
- I cannot contribute a fix for this bug at this time
Activity
Validate each branch against its own commits on retry (apache#4317)