richiebachala opened a new pull request, #2542:
URL: https://github.com/apache/iceberg-go/pull/2542

   Fixes #2195. Thanks @badalprasadsingh for the design and the 
`ReplaceDataFiles` point.
   
   ## Why
   
   Since #2046, any commit that removes a deletion vector sets `noReplay`. So a 
copy-on-write `Delete` or `Overwrite` of a file with a DV fails on a CAS 
conflict even when the concurrent commit is an unrelated append. The guard 
protects against a stranded second DV: a peer replaces or adds a DV for a file 
we touch, and our stale removal replays as a no-op. That can't happen when the 
commit also removes the data file and a validator rejects any concurrent delete 
on it.
   
   ## What
   
   - `snapshotProducer` gets an opt-in, `markRemovedDVsValidated()`. A DV 
removal stays replayable only when the producer is marked **and** every removed 
DV's data file is removed in the same commit (`dvRemovalsReplayable`). Any 
path-keyed delete-file removal still sets `noReplay`. Unmarked producers behave 
exactly as before.
   - **Copy-on-write `Delete` / `Overwrite`:** they already register 
`rewriteValidator(removed)` over every removed data file, so they now mark the 
producer.
   - **Merge-on-read `Delete`, whole-file drops:** the delete now registers 
`rewriteValidator` over the dropped files that carry a DV, and marks the 
producer, but only when the delete drops whole files and nothing else.
     - **Why only files with a DV:** `rewriteValidator` treats any concurrent 
equality delete as a conflict. Drops of files without a DV remove no DV and 
keep replaying as before.
     - **Why only whole-file drops:** a partial delete gives a live file a new 
DV, and nothing checks that file for a concurrent DV. A replay could then leave 
two live DVs on it. So a delete that also partially deletes keeps the no-replay 
behavior.
   
   | Path | Removes the DV with its data file | Validator over the removed 
files | After this PR |
   |---|---|---|---|
   | CoW `Delete` / `Overwrite` | yes | `rewriteValidator(removed)` (existing) 
| replayable |
   | MoR `Delete`, only whole-file drops | yes | `rewriteValidator` over files 
with a DV (new) | replayable |
   | MoR `Delete` with any partial delete | no, for the partially deleted file 
| — | non-replayable (unchanged) |
   | `ReplaceDataFiles` / `ReplaceDataFilesByPath` | yes | none | 
non-replayable (unchanged) |
   | `ReplaceFiles`, dynamic partition overwrite, `RowDelta.RemoveDeletes` | 
varies | not registered before the guard | non-replayable (unchanged) |
   
   A correction to my comment on the issue: I listed `ReplaceFiles` as covered. 
It only registers `rewriteValidatorWithReferencedDataFiles` with rewrite 
semantics plus added deletes, and only after `updater.commit()` has run the 
guard, so it's left out here.
   
   ## Tests (`table/no_replay_removals_test.go`)
   
   - `TestCoWDeleteRemovingDVFailsInsteadOfReplaying` from #2046 asserted the 
old behavior. I renamed it 
`TestCoWDeleteRemovingDVReplaysOverConcurrentAppend`. It now asserts that the 
delete lands on the replay, that the rows the old DV deleted stay deleted, and 
that no DV is left.
   - Replays and lands over a concurrent append:
     - `TestCoWWholeFileDropWithDVReplaysOverConcurrentAppend`
     - `TestCoWOverwriteRemovingDVReplaysOverConcurrentAppend`
     - `TestMoRWholeFileDropWithDVReplaysOverConcurrentAppend`
   - `TestMoRWholeFileDropRejectsConcurrentDVOnDroppedFile`: a peer DV on the 
dropped file fails with `ErrConflictingDeleteFiles` and 
`ErrTransactionUnusable`, nothing is committed, and exactly one live DV remains.
   - Still non-replayable, after exactly one attempt and with the no-replay 
hint:
     - `TestMoRDeleteMixingWholeFileDropAndPartialMergeStaysNonReplayable`
     - `TestMoRDeleteMixingWholeFileDropAndFirstDVStaysNonReplayable`
     - `TestReplaceDataFilesRemovingDVStaysNonReplayable`
   - `TestMoRWholeFileDropWithoutDVStillReplaysOverEqualityDelete` pins that 
drops without a DV gain no validator.
   
   Against unmodified `main`, the four "replays" tests and the rejection test 
fail. The rejection test fails there only because `main` refuses the commit 
through the no-replay path. The three non-replayable tests and the 
equality-delete test pass on both.
   
   Mutation checks, each restored afterwards:
   
   | Mutation | Failing test |
   |---|---|
   | Remove the CoW mark | the three CoW tests |
   | Mark MoR drops without registering their validator | 
`...RejectsConcurrentDVOnDroppedFile` |
   | Register the MoR validator whenever any file is dropped | 
`...WithoutDVStillReplaysOverEqualityDelete` |
   | Allow the MoR mark when the delete also has partial deletes | 
`...FirstDVStaysNonReplayable` |
   
   The data-file-also-removed check in `dvRemovalsReplayable` is now redundant 
for every caller: MoR never marks a delete with partial deletes, and CoW never 
removes a live file's DV. I kept it as a backstop for future callers, but no 
test fails without it.
   
   Commands run:
   - `go test -race ./table/... ./catalog/internal/... ./catalog/sql/...`
   - `go vet ./table/... ./catalog/...` and `go vet -tags integration 
./table/...`
   - golangci-lint v2.14.0: 0 issues
   
   ## Not addressed here
   
   While testing this I found that the same race already corrupts a table on 
`main` without any whole-file drop. Two merge-on-read deletes each give one 
live file its first DV. The second replays, since it removes nothing, and the 
file ends up with two live DVs, so scans fail with `can't index multiple 
deletion vectors`. This PR doesn't change that path. I can file it separately.
   
   ## Docs
   
   One sentence in `website/src/concurrent-writes.md`.
   
   Generated with [Snowflake 
CoCo](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to