CRDT Data Structures
Conflict-free Replicated Data Types (CRDTs) are data structures that converge to the same value across replicas, regardless of update order, without requiring coordination.
For systems with multiple writers and high availability requirements, CRDTs offer strong eventual consistency without the complexity of consensus.
The problem CRDTs solve
In a distributed system with multiple replicas:
- Updates happen anywhere
- Network partitions exist
- We want availability (CAP's A)
Without coordination, concurrent updates conflict. How do replicas reconverge?
Options:
- Last-write-wins: simple but lossy
- Multi-value: surface conflicts to application
- Consensus before each write: slow, high coordination
- CRDT: automatic merge with mathematical guarantees
CRDTs trade some flexibility for automatic correctness.
Strong eventual consistency
Eventual consistency: replicas eventually converge if updates stop.
Strong eventual consistency: replicas with the same set of updates have the same state — regardless of order.
CRDTs guarantee SEC by design.
Two flavors
State-based (CvRDTs)
Each replica has state. Replicas exchange full state. Merge function:
- Commutative: merge(A, B) = merge(B, A)
- Associative: merge(merge(A, B), C) = merge(A, merge(B, C))
- Idempotent: merge(A, A) = A
These properties define a join-semilattice.
Operation-based (CmRDTs)
Replicas exchange operations. Operations must commute.
Requires reliable, exactly-once delivery (or operations must be idempotent).
State-based vs op-based: different constraints; choose based on network reliability and bandwidth.
Common CRDTs
G-Counter (grow-only counter)
State: vector of counts per replica.
Increment: replica i increments its slot.
Merge: max per slot.
Value: sum of slots.
Used for: counters that only increase (page views, likes).
PN-Counter
Two G-counters: one for increments, one for decrements.
Value: positive sum - negative sum.
Used for: counters that can increase or decrease.
G-Set
Add-only set.
Add: insert element.
Merge: union.
Used for: append-only collections.
2P-Set
Add and remove. Two G-Sets: added and removed.
Once removed, can't re-add.
LWW-Element-Set
Last-write-wins set. Each element has add timestamp and remove timestamp.
Element is present if add timestamp > remove timestamp.
Used for: sets with eventual additions and removals.
OR-Set (Observed-Remove Set)
More sophisticated. Each add gets a unique tag. Remove removes specific tags.
Removes only what was observed; concurrent adds are preserved.
Lists / sequences
Hard to do well. Various designs:
- RGA (Replicated Growable Array)
- LSEQ
- Logoot
- Causal Trees / Yjs algorithm
Used for collaborative text editing.
Maps
Replicated map structures, often using OR-Set semantics for keys + CRDTs for values.
Counters with constraints
Bounded counters, monotonic-only counters — for specific domain semantics.
Causal context
For correct merge, often need to track causality:
- Vector clocks
- Version vectors
- Dotted version vectors
These track which updates each replica has seen.
Practical CRDT systems
Yjs
JavaScript CRDT library. Used for real-time collaboration (Y.js, Tiptap, Liveblocks).
Automerge
JSON-like CRDT. Used in collaborative apps.
Riak
Distributed database with CRDT support (counters, sets, maps).
Redis
CRDB feature in Redis Enterprise.
Antidote
Distributed database designed around CRDTs.
Operational Transformation (OT)
Alternative to CRDT for collaborative editing. Used historically (Google Docs).
CRDTs increasingly preferred for distributed scenarios.
Use cases
Collaborative editing
Multiple users editing same document. CRDTs for text, spreadsheets, drawings.
Shopping carts
Distributed shopping cart. Add items concurrently; merge cleanly.
Counters and metrics
Distributed counters across regions.
Configuration
Distributed config that multiple admins update.
Offline-first apps
Mobile apps that work offline; sync when online. CRDT structures merge cleanly.
IoT / edge
Devices update locally; sync to cloud. CRDTs handle concurrent changes.
Costs
Memory overhead
CRDTs often store metadata per element (vector clocks, tombstones).
OR-Set: tombstones grow with deletions.
Bandwidth
State-based: send full state. Large for big structures.
Op-based: send ops. Often smaller.
Delta-based CRDTs send only changes.
Garbage collection
Tombstones, version vectors accumulate. Need cleanup mechanism.
Often requires occasional coordination (which defeats the purpose somewhat).
Implementation complexity
Subtle bugs are common. Use libraries; don't roll your own for non-trivial structures.
Limitations
Non-trivial semantics
Some operations don't fit CRDT model:
- "Remove all elements" (need consensus on what "all" means)
- Conditional updates ("if X then Y")
- Read-modify-write across replicas
Read consistency
CRDTs guarantee write convergence; reads can return any state up to date.
For "I just wrote X, so I should read X" semantics: same-replica reads or session guarantees.
Bounded counters
Can't have a CRDT counter with strict upper bound (without coordination).
Garbage collection challenges
Tombstones live forever in basic OR-Set. Real implementations need GC strategies.
When to use CRDT
CRDT fits when:
- Multiple writers
- High availability required (no coordination on writes)
- Eventual consistency acceptable
- Can express semantics in CRDT-friendly way
CRDT doesn't fit when:
- Need strong consistency
- Operations require coordination
- Coordination cost is acceptable
When NOT to use CRDT
If you have a single writer (master-replica), CRDTs are unnecessary.
If you need strong consistency (banking transactions, inventory), use consensus-based systems.
If your domain has natural conflicts that need user resolution: surface conflicts; don't auto-merge.
Common failure patterns
Wrong CRDT for the semantics
OR-Set when you wanted last-write-wins. Subtle differences matter.
Memory growth
Forgetting that tombstones / version vectors grow. Production OOM eventually.
Causal violations
Mixing CRDT with non-CRDT operations. Break SEC.
Custom CRDTs
Designing your own CRDT is hard. Composition of standard CRDTs is safer.
Pretending CRDT solves everything
CRDT is a tool, not a complete solution. Many distributed systems still need coordination.
Theoretical foundations
CRDTs rely on:
- Lattice theory (join-semilattice)
- Causal histories
- Strong eventual consistency theorems
The math is well-developed. The engineering is the hard part.
Practical advice
1. Identify if your problem genuinely needs multi-writer + high availability
2. If yes, look for existing CRDT library covering your data shape
3. If your data fits standard CRDTs (counters, sets, maps), use them
4. If complex (rich documents): use Yjs, Automerge
5. Avoid designing custom CRDTs unless you've read the literature carefully
Further Reading
- [EventualConsistency](EventualConsistency) — Background
- [ByzantineFaultTolerance](ByzantineFaultTolerance) — Different consistency challenge
- [Distributed Systems Hub](DistributedSystemsHub) — Cluster index