CRDT Tombstone Garbage Collection & Compaction: Production Playbook

2026-03-21 · software

CRDT Tombstone Garbage Collection & Compaction: Production Playbook

CRDTs converge beautifully, but production systems pay for that convergence in history growth.

Deletes become tombstones, old ops stay around for causality, and sync logs keep expanding unless you run a disciplined GC + compaction loop.

This playbook is a practical guide for keeping CRDT replicas correct and bounded.


One-Line Intuition

You may physically delete tombstoned data only after proving every replica has causally passed the delete point.


1) The Core Problem

Most CRDT incidents around storage cost have the same root:

If you remove tombstones too early, a late replica can resurrect deleted state or fail to apply downstream ops that reference removed nodes.


2) Safety Invariant (What Must Be True Before GC)

For an element removed at causal dot (actor=a, counter=c), physical GC is safe only when:

[ \text{stability}[a] \ge c ]

where stability is a global lower-bound frontier (often min-version-vector across active replicas/sessions for that document).

Practical meaning:

If you cannot compute this frontier reliably, do not hard-delete.


3) Architecture Pattern That Works in Production

A. Keep two clocks/planes

  1. Causal plane: version vectors / dots / heads for correctness
  2. Storage plane: incremental ops + periodic snapshots for size

B. Keep two delete phases

  1. Logical delete (tombstone now)
  2. Physical purge (after stability proof)

C. Keep two retention policies

  1. Convergence retention (minimum needed for merge correctness)
  2. Product retention (undo/history/legal/audit)

Do not mix them. Product history should be explicit snapshots/events, not accidental CRDT garbage.


4) Data Structures (Minimal)

For each document:

Component-wise minimum:

[ \text{minVV}[k] = \min_i \text{vv}_i[k] ]

Missing actor component should be treated conservatively (usually as 0 unless membership/lease logic says replica is retired).


5) GC Algorithm (Reference)

onSync(clientId, clientVV):
  vv_client[clientId] = clientVV
  minVV = componentwise_min(vv_client over active clients)

for each tombstone t in doc:
  a = t.removedAt.actor
  c = t.removedAt.counter
  if minVV[a] >= c:
    purge_physically(t)

Key production detail: "active clients" must be defined by membership/lease policy, not wishful thinking.


6) Membership, Offline Clients, and the Real Trap

minVV can stall forever if zombie sessions are never retired.

Use explicit policy:

A safe compromise:


7) Sequence/Text CRDT Notes

Text CRDTs are where GC pain becomes visible first.

Typical practical choices:

Operationally, teams often run GC enabled by default and route "full history" documents to a separate policy tier.


8) Compaction Strategy (Incremental + Snapshot)

A robust loop:

  1. append incremental ops continuously
  2. when (incremental_bytes > k * snapshot_bytes) or op_count > threshold, build snapshot
  3. persist snapshot keyed by content identity (e.g., doc id + heads/hash)
  4. delete only incremental chunks proven included in snapshot

This avoids write-write races across concurrent compactors and works with simple key-value backends.


9) Observability: Metrics That Predict Pain Early

Track at document and fleet level:

Alert patterns:


10) Failure Modes

  1. Lamport-only cleanup without causal frontier -> premature purge risk
  2. Never-expiring client set -> GC never triggers
  3. Undo feature piggybacking on tombstones -> unbounded growth by design
  4. Compaction race deleting unseen increments -> silent divergence bugs
  5. No cold-start plan for long-offline devices -> permanent retention tax

11) Rollout Ladder

Stage 0 — Observe only

Stage 1 — Conservative purge

Stage 2 — Full policy with canaries

Stage 3 — Cost-optimized steady state


12) Practical Decision Matrix


Minimal Implementation Checklist

  1. Define causal metadata contract (vv/dot/heads)
  2. Implement stable frontier computation with membership TTL
  3. Add two-phase delete (logical first, physical later)
  4. Add safe compaction (incremental -> snapshot)
  5. Ship metrics + drift alerts
  6. Canary enablement and rollback switch

One-Sentence Summary

CRDT storage stays healthy when tombstone purge is gated by a causal stability frontier and paired with race-safe snapshot compaction—anything weaker eventually pays either correctness risk or runaway storage cost.


References