xrpld · NodeStore · online_delete

Generational online_delete: measured write reduction

Today every rotation copies the entire live state into a fresh NuDB file — write volume scales with total state. A generation ring copies only the cold nodes that the retiring generation still serves, so write volume scales with churn instead.

Branch dangell7/online-delete-gc Bench xrpl.app.SHAMapStoreBench Build 3.3.0-rc1 (debug) Measured 2026-08-31
fewer NodeStore nodes re-stored at the default 8-generation ring, over the same 16 rotations and the same workload. The reduction factor is exactly the ring budget.

What changes

Today. SHAMapStoreImp::run() rotates by copying every live node of the state map into a brand-new backend file, then dropping the old one. The copy is O(total state) and it happens on every rotation, whether or not anything changed — a node that has been cold for a year is rewritten every interval.

With a generation ring. Writes always land in the newest generation; reads walk newest→oldest. When the ring exceeds its budget, the oldest generation is retired: only nodes that are still live and still served by that generation get copied forward, then the whole file is dropped. An evacuated node lands in the newest generation, so it then has to age across the entire ring before it can be touched again — a cold node is re-stored once per budget rotations instead of once per rotation.

Legacy full copy versus a generation ring TODAY — EVERY ROTATION live state (all of it) copy all fresh backend file write volume = O(total state) × rotations GENERATION RING — ONE RETIREMENT retiring oldest gen gen newest writable write volume = O(churn) + state / budget copy forward: cold survivors only
The ring never rewrites what it can simply drop. Only the cold nodes still answered by the retiring generation are re-stored, and they land in the newest generation — so each one pays its write once per full trip around the ring. Reads walk newest→oldest, and a retired file is dropped whole rather than rewritten.

Measured write volume

Nodes re-stored over 16 rotations
cold set 16,000 nodes · 32 churned nodes per rotation
Today (full copy) Ring, 2 generations Ring, 4 Ring, 8 (default) Ring, 16
064,000128,000192,000256,000

Full matrix

Every row is one bench run: 16 rotations, 32 churned nodes per rotation. Retention is asserted after every retirement — zero cold nodes missing in all 12 runs.
Cold nodesRing budgetRetirements Re-storedPer retirementToday (full copy) ReductionWall time

How it was measured

The bench drives the real DatabaseRotatingImp rotation path — advance(), beginRetire(), retireOldest() — against an in-process backend ring, with the production evacuation pattern: a cold working set written once, fresh churn every interval, and a fetch of every live node during each retire window (what visitNodes(copyNode) does). The reported volume is copyForwardCount(): nodes actually re-stored because the retiring generation was the one serving them.

cd build
./xrpld --unittest=xrpl.app.SHAMapStoreBench