Skip to content
About
Main content

Choose a mutation strategy

The decision

Stored data changes: rows are updated and deleted in database pages or in files on . Storage writes in units (a page, a 128 file), but a change may touch a few bytes. You can overwrite in place, rewrite the whole unit with the change applied, or write the change on its own and let readers merge it. Which fits these updates?

You will end with how updates and deletes are written, the compaction interval, and what to monitor.


Decide

1

Can the storage overwrite at your update size, with no reader needing the old version meanwhile?

A database page on a local disk, without concurrent snapshot readers: yes. Immutable files on object storage: no.

Why

is optimal for reads and space. It needs a medium that overwrites at your , and if readers need consistent old versions it has to grow version machinery anyway.

2

How big is the unit compared with a typical change?

Unit size ÷ change size: a 128 MB file changed by a 1 KB row is about 100,000.

Under ~10Copy-on-write

Over ~1,000Go to 4

In betweenGo to 3

Why

rewrites the whole unit for every change, so its write cost is that ratio. Small ratios are cheap and simplest; above about 1,000 it is ruled out regardless of anything else.

3

Which is larger: write volume × unit size, or read volume × the cost of merging changes on each read?

Why

Copy-on-write costs about writes × unit size; costs about reads × merge cost per change × changes waiting. At 1,000 reads per write on a page-sized unit, merging on read is a bad trade; at 1,000 writes per read on a 128 MB file, it is the only workable one.

4

When writing a delete, can you cheaply find the exact position of the row it removes?

Why

A marks row positions as deleted: tiny, and cheap for readers. It needs positions that stay stable, so frequent reclustering breaks it. An records the key instead, and every reader must probe for it.


Where you can land

Update in place

Use it when the medium overwrites at your update size and no reader needs old versions.

What it costs: a , which at least doubles the bytes written.

Copy-on-write

Use it when units are small relative to changes, or reads dominate.

Rewrite the affected unit with the change applied, then swap a pointer. come almost free, since the old copy is still there.

What it costs: unit size ÷ change size for every write.

Merge-on-read with delete vectors

Use it when writes dominate and deleted rows' positions are known and stable.

The best deal on this page.

Before you build it

Derive the compaction interval

Changes pile up until merges them back; readers pay for every pending change. Compact every rather than guessing a schedule: the interval comes from three measurable quantities.

Merge-on-read with equality deletes

Use it when writes dominate but positions are unknown at write time, or data is reclustered often.

What it costs: every read probes the deleted keys. Compact on the same derived interval as for delete vectors.


Whichever you land on

1

Monitor the age of the oldest reader

Old versions can be reclaimed only after every reader that might see them finishes, so one long-running reader pins everything. Name what pins reclamation in your system and graph its age, not the collector's throughput. is the metric that predicts failure, and almost nobody graphs it.

2

Choose the fill level deliberately

When storage is never overwritten, reclaiming space means copying live data: at utilization u, writing one new byte moves u ÷ (1 − u) old ones. At 50% full that is 1; at 90%, 9; at 95%, 19. Decide what you are buying with headroom, instead of discovering it at 95% full.