Optimize RangeTombstoneList copying with copy-on-write array sharing (CASSANDRA-21492)#4919
Optimize RangeTombstoneList copying with copy-on-write array sharing (CASSANDRA-21492)#4919perfloop-agent wants to merge 1 commit into
Conversation
…(CASSANDRA-21492)
ifesdjeen
left a comment
There was a problem hiding this comment.
@perfloop-agent is it possible to demonstrate a win on a many-tombstone example? One can use HarryCompactionWithRangeDeletionsTest as a template: we already have machinery to generate SSTables and compact them there.
Also, 2 things from the review:
-
In RangeTombstoneList,
copy()/addAll()now share backing arrays, butunsharedHeapSize()` still "charges" the full arrays and boundary storage to every instance as if each copy owned them exclusively. I'm actually thinking that the correct accounting is not expressible in the current design. Maybe if we're overcounting this is not too tragical though. -
The old
copy()compacted arrays to size; the new one aliases fullcapacity(). Same for the empty-target addAll() fast path, which now just adopts the source arrays without trimming them (I would need to double check how much of an impact that is in wider codebase, but this seems to be a real problem).
| growToFree(i); | ||
| else if (i < size) | ||
| moveElements(i); | ||
| else |
There was a problem hiding this comment.
Maybe worth mentioning in a comment that growToFree would allocate arrays anyways, so we do not need to isolate in addition.
Optimize RangeTombstoneList copying with copy-on-write array sharing (CASSANDRA-21492)
Description
Each memtable write carrying a range tombstone runs
BTreePartitionUpdater.merge-> existingDeletionInfo.mutableCopy()->RangeTombstoneList.copy, which unconditionally copies all four parallel arrays (starts, ends, markedAts, and delTimesUnsignedIntegers) of the existing list regardless of insert position.Here, the variable
Nrepresents the number of range tombstones in theRangeTombstoneList. Under modeled production workloads,Ntypically ranges from small lists of sizeN ~ 10(for small partitions with light deletion traffic) to large lists of sizeN >= 1000(under heavy range-delete or partition-level tombstone-heavy workloads). Successive range-tombstone merges into one unflushed partition pay O(N^2) parallel array copying work on the mutation write path because unmutated lists are repeatedly re-copied. Duplicating these parallel arrays is highly expensive at scale due to array allocation and element-by-element copy overheads.This optimization implements copy-on-write array sharing (starts, ends, markedAts, and delTimesUnsignedIntegers backing arrays) between copies, reducing unmutated copying to an O(1) pointer-sharing operation. This optimization introduces no behavior changes and preserves full copy independence under all mutating operations.
Limits of the Mechanism:
Because this is a copy-on-write mechanism, any subsequent mutation on a shared list forces an O(N) isolation copy (via
isolate()) before the write occurs. This means that if mutations are performed immediately after copying, the parallel array copying cost is deferred rather than eliminated. However, in workloads where RangeTombstoneLists are copied but not subsequently mutated, the copying cost is entirely eliminated.Headline Win (benchCopyOnly)
RangeTombstoneListBench.benchCopyOnlydrops from3362.5156478614854ns/op to9.018492886907715ns/op (a 99.7318% reduction in copying latency, n=10, p=1.08251e-05).Guardrails (Categorized as Held)
RangeTombstoneListBench.benchCopyAndAdd(size N=1000):8842.89307604306ns/op ->5485.592293470344ns/op (Held, a 37.9661% reduction, n=10, p=1.08251e-05)RangeTombstoneListSize10Bench.benchCopyOnly(size N=10):56.7137252431266ns/op ->9.083180562797722ns/op (Held, an 83.9842% reduction, n=10, p=1.08251e-05)RangeTombstoneListSize10Bench.benchCopyAndAdd(size N=10):186.2202636628365ns/op ->122.92588844176737ns/op (Held, a 33.989% reduction, n=10, p=1.08251e-05)Co-measured Ground-Truth Comparison
Measurements are co-measured. The baseline was measured at baseline commit tree with the benchmarks compiled in.
061749b0ae48d5b3be1f78754f177bdd745c7a1eca5e7e41597ff05a09028d2e0f382df62db4620d(shipped commitca5e7e41)ca5e7e41(Median)RangeTombstoneListBench.benchCopyOnlyRangeTombstoneListBench.benchCopyAndAddRangeTombstoneListSize10Bench.benchCopyOnlyRangeTombstoneListSize10Bench.benchCopyAndAddCorrectness Verification
The following correctness checks and suites passed completely:
pairing,environment_consistent,baseline_valid,benchmark_passed,RangeTombstoneListTest(verifies full copy independence under mutations),StyleCheck,measurements_sane,metric_present,sample_sufficiency,sample_independence.Reproduction
061749b0ae48d5b3be1f78754f177bdd745c7a1eca5e7e41597ff05a09028d2e0f382df62db4620d(shipped commitca5e7e41)Full Proof
This is an automated, human-reviewed Perfloop contribution opened by perfloop-agent. The body links to reproducible proof for the change. The change was benchmarked on perfloop/cassandra, an automation fork of apache/cassandra; the commit on this PR's head branch carries the proof.
Assisted-by: PerfloopAgent:gemini-3.5-flash