Part IV — Peer-to-Peer & Decentralized Data · Chapter 5

CRDTs: conflict-free replicated data types

Hook

Two replicas of the same counter each accept writes while offline, then reconnect in some unpredictable order — no coordinator, no locking, maybe not even in the same order twice. Is there a data structure where "just apply whatever you both have" is guaranteed to produce the same answer, no matter the order?

Intuition
Replica PP:1 Q:0value 1
Replica QP:0 Q:0value 0
P and Q currently disagree, with no coordination between their clicks

Click P and Q's buttons in whatever order and however many times you like — they never talk to each other while doing it. Then hit "Merge": both boxes turn out exactly the same, holding the full total of everything either one ever did.

Formalize

A G-Counter (grow-only counter) CRDT gives every replica its own slot that only that replica may increment. Merging two replicas' states takes the pointwise max of every slot:

merge(A,B)i=max(Ai,Bi)\text{merge}(A, B)_i = \max(A_i,\, B_i)
  • AiA_i, BiB_i — replica ii's slot count as last seen by state AA or BB.
  • merge(A,B)i\text{merge}(A,B)_i — the merged state's slot ii: whichever of AA or BB has seen more of replica ii's increments.
  • value(state) — the counter's logical total: the sum of every slot.
  1. Merge is commutative and idempotent

    max(Ai,Bi)=max(Bi,Ai)\max(A_i,B_i) = \max(B_i,A_i) for every slot, so it never matters who initiates a merge — and merging a state with itself changes nothing, so re-merging the same update twice is harmless.

  2. Merge is associative

    max\max over three or more values doesn't depend on how they're grouped, so merging replicas two at a time, in any order or pairing, reaches the exact same final state as merging them all at once.

Play
Replica PP:0 Q:0
Replica QP:0 Q:0
merge(P, Q) = P:0 Q:0 (value 0)
merge(Q, P) = P:0 Q:0 (value 0)
identical either way, at any point

Increment P and Q in any pattern — three P's then one Q, alternating, whatever. The panel always computes merge(P, Q) and merge(Q, P) side by side: they never disagree, at any point, no matter what either replica has done up to that moment.

Worked example

Three replicas, each incrementing only their own slot, entirely independently:

  1. Independent local states
    • P increments 3 times: {P:3,Q:0,R:0}\{P{:}3, Q{:}0, R{:}0\}
    • Q increments 2 times: {P:0,Q:2,R:0}\{P{:}0, Q{:}2, R{:}0\}
    • R increments once: {P:0,Q:0,R:1}\{P{:}0, Q{:}0, R{:}1\}

    None of them has seen either of the others' updates.

  2. Merging P and Q, either direction

    merge(P,Q)={P:max(3,0),Q:max(0,2),R:max(0,0)}={3,2,0}\text{merge}(P,Q) = \{P{:}\max(3,0), Q{:}\max(0,2), R{:}\max(0,0)\} = \{3, 2, 0\}. Merging the other way, merge(Q,P)\text{merge}(Q,P), produces the exact same {3,2,0}\{3, 2, 0\}.

  3. Bringing in R, in either grouping

    merge(merge(P,Q),R)={P:max(3,0),Q:max(2,0),R:max(0,1)}={3,2,1}\text{merge}(\text{merge}(P,Q), R) = \{P{:}\max(3,0), Q{:}\max(2,0), R{:}\max(0,1)\} = \{3,2,1\}, and merge(P,merge(Q,R))\text{merge}(P, \text{merge}(Q,R)) also comes out to {3,2,1}\{3,2,1\} — total value 66, regardless of which two replicas happened to sync with each other first.

Checkpoint

P, Q, and R each incremented independently, with no coordination. Using the sync buttons below in any order you like, get all three replicas to fully converge.

Replica PP:3 Q:0 R:0value 3
Replica QP:0 Q:2 R:0value 2
Replica RP:0 Q:0 R:1value 1
Sync any pair to begin
Summary
merge(A,B)i=max(Ai,Bi)\text{merge}(A,B)_i = \max(A_i, B_i)

A CRDT's merge rule is built so that commutativity, associativity, and idempotence hold by construction — not by careful protocol design, but because max\max (or union, for other CRDTs) simply has those properties. That's what "no coordination" really buys: replicas can accept writes and sync with whichever peers happen to be reachable, in whatever order, and still be mathematically guaranteed to converge to one shared state. The next chapter's Dynamo-style store reaches for a different tool — vector clocks — for exactly the writes a CRDT's merge rule can't silently resolve on its own.