Neuw84 opened a new pull request, #17287:
URL: https://github.com/apache/iceberg/pull/17287

   ## What
   
   Adds `ClusteredDVFileWriter` (core) and `ClusteredDVWriter` (io adapter), a 
deletion vector writer for inputs clustered by data file, and wires Spark 3.5 / 
4.0 / 4.1 position delta writes to use it when the incoming deletes are ordered 
(`fanout-enabled=false` requests ordering by `_spec_id, _partition, _file, 
_pos`).
   
   ## Why
   
   `BaseDVFileWriter` buffers one in-memory position index per data file the 
task deletes from, and at `close()` loads every touched file's previous DV 
(blob bytes + deserialized bitmap) and merges them - all resident 
simultaneously. Peak state is
   O(files touched by the task). For hot-key MERGE workloads on bucketed 
tables, a task can touch nearly every data file in its buckets every batch, so 
this grows with the table.
   
   The data path has had a bounded variant for ordered input since the 
beginning (`ClusteredDataWriter` vs `FanoutDataWriter`), and the v2 position 
delete path picks `ClusteredPositionDeleteWriter` when the input is ordered. 
The DV path had no such option: `PartitioningDVWriter` is used regardless of 
input ordering. This PR closes
   that asymmetry.
   
   With clustered input, the writer keeps one live position index. On the first 
delete of the next data file it merges the previous file's existing deletes, 
flushes the blob into the shared Puffin file and releases the bitmap - peak 
resident state becomes a single position index plus small per-file blob 
metadata, independent of how many files the task touches. Input that revisits a 
completed file fails fast (same contract as `ClusteredWriter`), since that 
would produce two DVs for one data file in a single commit.
   
   ## Benchmark honesty
   
   We ran a three-arm A/B on EKS (Spark 4.0, Iceberg 1.11 line, 6x8-core 
executors, 32g heap + 8g overhead, ~90k rows/s hot-key CDC MERGE into a 
bucket(64) MoR mirror,no compaction; details in apache/iceberg#17241):
   
   | arm | writer | fanout | first executor OOM wave |
   |---|---|---|---|
   | baseline | PartitioningDVWriter | true | t+75min |
   | control | PartitioningDVWriter | false | t+90min |
   | clustered | ClusteredDVWriter | false | t+93min |
   
   At this scale the end-to-end OOM timing was dominated by an off-heap term 
common to both writers (page cache / native buffers from the Puffin 
read-modify-write churn), so the clustered writer did NOT measurably delay 
container kills - the fanout=false ordering accounted for the improvement. 
Throughput at matched batch was identical
   across writers.
   
   The claim of this PR is therefore the architectural bound, not an OOM fix: 
heap-side
   DV writer state becomes O(1 data file) instead of O(files touched) by 
construction.
   That matters for heap-constrained executors and for tasks that touch many 
more files
   than ours did (file-heavy tables, partition-scoped tasks, infrequent 
compaction),
   and it aligns the DV path with the existing clustered/fanout design on the 
data path.
   
   ## Testing
   
   - New tests in `TestDVWriters` (runs via `TestSparkDVWriters` in all three 
Spark versions, 30 tests each, green): basic clustered write across two files, 
previous-DV merge marking the old DV rewritten (commit replaces it), and
     unclustered-input rejection.
   - Existing `TestMergeOnReadDelete` / `TestMergeOnReadMerge` suites pass (the 
 parameter matrix includes v3 + fanout=false rows that route through the new   
writer end to end): 780 tests, 0 failures (run on the 1.11-based branch with
     identical sources; re-run on main pending CI).
   
   ## Notes for reviewers
   
   - The gate is `context.inputOrdered()` - the same signal the v2 position 
delete
     path uses to pick `ClusteredPositionDeleteWriter`. Fanout (unordered) 
input keeps
     the existing `PartitioningDVWriter` behavior; nothing changes for those 
plans.
   - `ClusteredDVFileWriter` duplicates the blob/DV construction of 
`BaseDVFileWriter`
     rather than subclassing: the base class's `deleteIndexes` map and 
close-time merge
     loop are exactly the state this writer exists to avoid, so sharing would 
mean
     refactoring the base. Happy to extract a shared helper if preferred.
   
   - Related: apache/iceberg#17281 (slim rewritable-deletes broadcast) 
addresses the
     driver-side term of the same workload; the two are independent.
   
   ---
   


-- 
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