Software Development

CRDTs: The Data Structure That Makes Distributed Consistency Optional and What It Costs

Conflict-free Replicated Data Types let replicas diverge freely and still guarantee eventual convergence. Here is the math behind why that works, a tour of the most useful types, and an honest look at where CRDTs break down.

1. The Problem CRDTs Solve

Imagine two users editing a shared counter. Node A increments it from 5 to 6 while, simultaneously, Node B also increments it from 5 to 6. When they sync, what should the result be? Naively merging the two writes gives you 6, but the correct answer is 7 — two distinct increments happened and both should count.

Traditional distributed systems solve this with coordination: locks, consensus protocols such as Paxos or Raft, or a central coordinator that serialises every write. Coordination works, but it carries a price: latency spikes, unavailability during network partitions, and throughput bottlenecks. According to the CAP theorem, in the presence of a partition you must choose between consistency and availability.

CRDTs take the other branch of that choice. They sacrifice strong consistency — you will not get linearisable reads — but they guarantee something weaker and often sufficient: eventual consistency with mathematical certainty. No coordinator, no consensus round, no lock. Replicas can accept writes offline, sync in any order, and the states will always converge to the same value.

Where you already use CRDTsGoogle Docs and Apple Notes use CRDT-derived algorithms for collaborative text editing. Redis uses a CRDT internally for its sorted-set replication in Redis Enterprise Active-ActiveRiak KV exposes CRDTs as first-class data types. Figma’s multiplayer engine is built on a variant of the same ideas.

2.The Mathematics: Join-Semilattices and Monotone Functions

CRDTs are not a clever hack — they rest on a precise mathematical foundation. Understanding that foundation is what makes the convergence guarantee feel inevitable rather than lucky.

Join-semilattices

join-semilattice is a partially ordered set in which every pair of elements has a least upper bound — called the join, written ⊔. Think of a set of subsets ordered by inclusion: the join of {A, B} and {B, C} is {A, B, C}. Three properties define a valid join operation:

Semilattice axioms (must all hold for convergence to be guaranteed)
Commutativity: a ⊔ b = b ⊔ a
Associativity: (a ⊔ b) ⊔ c = a ⊔ (b ⊔ c)
Idempotency: a ⊔ a = a
— together these mean: merge in any order, any number of times → same result

These three properties are exactly what a distributed merge function needs. Commutativity means messages can arrive in any order. Associativity means you can batch merges arbitrarily. Idempotency means you can safely replay a message you already received, which is critical in at-least-once delivery networks.

State-based vs. operation-based CRDTs

There are two families of CRDTs, and the distinction matters for what your network layer must guarantee.

PropertyState-based (CvRDT)Operation-based (CmRDT)
What is transmittedFull state snapshotIndividual update operations
Merge functionMust satisfy semilattice axioms on the state spaceConcurrent operations must commute
Network requirementEventual delivery onlyExactly-once delivery (or idempotent ops)
BandwidthHigh — full state per syncLow — delta operations only
Typical useCounters, sets, flagsCollaborative text editing

In practice, modern systems often use delta-state CRDTs — a hybrid that sends only the portion of state that changed since the last sync, achieving the bandwidth efficiency of operation-based CRDTs while requiring only eventual delivery from the network.

3. A Tour of the Main CRDT Types

Rather than an exhaustive catalogue, what follows covers the types you are most likely to encounter in real systems — along with the subtleties that catch developers off guard.

G-Counter: the simplest CRDT in code

The G-Counter is the “Hello, World” of CRDTs. Implementing it from scratch takes about a dozen lines of Java and, as a result, makes the core idea immediately tangible.

import java.util.HashMap;
import java.util.Map;

public class GCounter {

    // One slot per replica node, identified by its node ID
    private final Map<String, Long> counts = new HashMap<>();
    private final String nodeId;

    public GCounter(String nodeId) { this.nodeId = nodeId; }

    // Local increment — no coordination needed
    public void increment() {
        counts.merge(nodeId, 1L, Long::sum);
    }

    // Global value is the sum of all slots
    public long value() {
        return counts.values().stream().mapToLong(Long::longValue).sum();
    }

    // Merge: take the maximum of each corresponding slot (? operation)
    public void merge(GCounter other) {
        other.counts.forEach((id, v) ->
            counts.merge(id, v, Math::max)   // max is the join for natural numbers
        );
    }
}

// ?? Demo: two nodes increment concurrently, then sync ??
GCounter nodeA = new GCounter("A");
GCounter nodeB = new GCounter("B");

nodeA.increment(); // A = {A:1}
nodeA.increment(); // A = {A:2}
nodeB.increment(); // B = {B:1}

// Sync in any order — result is always 3
nodeA.merge(nodeB);
nodeB.merge(nodeA);

// nodeA.value() == 3  ✓
// nodeB.value() == 3  ✓

Notice that Math::max is the join operation here. It satisfies all three semilattice axioms: max(a,b) = max(b,a)max(max(a,b),c) = max(a,max(b,c)), and max(a,a) = a. The whole convergence guarantee falls out of those three lines.

OR-Set: where the semantics get interesting

The OR-Set is worth studying because it reveals the design trade-offs that all CRDT authors face. Earlier designs — the 2P-Set and the U-Set — had intuitive semantics but either disallowed re-adding removed elements or required a global source of unique IDs. The OR-Set solves both problems by tagging every addition with a universally unique identifier (UUID in practice) and defining removal as “remove all tags I have currently observed for this element.”

The result is that a concurrent add(x) and remove(x) always resolves in favour of the add — because the add generates a fresh tag that the remove operation never saw. Whether that semantic is correct depends entirely on your application. For a collaborative shopping cart, it is usually right (the person who added the item wins). For a permission revocation system, it is emphatically wrong.

“The right CRDT for your use case is the one whose conflict-resolution semantics match what your users actually expect — not the one with the most elegant theory.”

4. How Convergence Actually Happens

The semilattice proof guarantees that if every replica eventually receives every update, they will converge. But “eventually” hides a lot of engineering. In practice, convergence requires an anti-entropy protocol — a background process where replicas periodically exchange state and reconcile differences.

The most common pattern is gossip (epidemic) propagation: each node, at random intervals, picks a small set of peers and exchanges its current state (or delta). The convergence rate is probabilistically bounded and depends on the gossip fanout and the network topology.

Gossip Convergence: Fraction of Nodes Converged Over Time

Simulated 100-node cluster. Each round, every node gossips to 3 random peers. A single update originates at node 0 at round 0. Based on standard epidemic broadcast analysis (Demers et al., 1987).

One important subtlety: convergence tells you nothing about when a replica will see a given update. If Node A is partitioned for six hours, its stale reads are perfectly valid CRDT behaviour — the data structure makes no availability guarantee for reads. Applications that need “read your own writes” semantics need to route reads back to the originating replica or maintain a causal consistency layer on top.

5. What CRDTs Cost You

The mathematical elegance of CRDTs can make them sound like a free lunch. They are not. Each benefit comes with a specific and sometimes painful cost.

Tombstone Accumulation in a PN-Counter Over Time

Simulated workload: 60% increments, 40% decrements. Tombstone (metadata) entries grow without bound unless compacted. Dataset size reflects in-memory footprint for 10,000 operations.

1. Metadata bloat and the tombstone problem

Every deletion in a set-based CRDT leaves a tombstone — a permanent record that the element was removed. Without tombstones, a replica that missed a delete would simply re-add the element on the next sync. Over time, tombstones accumulate and can dominate the storage cost of the data structure. Production deployments need a garbage-collection protocol (sometimes called “stable compaction”) that only removes tombstones once every replica has acknowledged the deletion — which, ironically, requires a coordination step.

2. Conflict resolution is semantic, not automatic

CRDTs resolve structural conflicts automatically, but they cannot resolve semantic conflicts. If two users concurrently rename a file to different names, an LWW-Register silently discards one rename. If two users concurrently delete and modify the same document section, the OR-Set semantics favour the modification. These outcomes may or may not align with user intent. The CRDT handles the mechanics of convergence; your application must handle the meaning.

3. Causal consistency is not guaranteed by default

A plain CRDT sync does not preserve causality. Node B might receive “Alice replied to Bob’s message” before it receives “Bob’s message” — because gossip delivers updates in arbitrary order. Applications that need causal ordering (which includes most chat, collaboration, and workflow systems) must layer a vector-clock or causal-consistency protocol on top of the CRDT.

LimitationPractical ImpactMitigation
Tombstone growthUnbounded storage cost for delete-heavy workloadsStable compaction with a coordination round
Semantic conflictsSilent data loss that users noticeApplication-level conflict UI; intent-preserving CRDTs
No causal orderingOut-of-order delivery breaks application logicVector clocks; causal broadcast middleware
LWW clock skewWrites from clocks running behind are silently droppedHybrid Logical Clocks (HLC) instead of wall clocks
Throughput under churnHigh-churn workloads amplify gossip trafficDelta-state CRDTs; adaptive gossip fanout

6. Byzantine-Fault-Tolerant CRDTs: The Frontier

Standard CRDTs assume a crash-fault model: nodes may fail by stopping, but they do not send malicious or corrupted messages. In open, adversarial networks — think peer-to-peer systems, blockchain-adjacent applications, or multi-tenant edge deployments — this assumption breaks down. A single Byzantine node can forge operations, replay deleted values, or inject arbitrary state into the merge function, corrupting the CRDT on every honest replica it syncs with.

This limitation received serious academic attention through the 2020s. The key insight from researchers at INRIA and Imperial College London (Kleppmann et al., 2022) is that Byzantine-fault tolerance and CRDT semantics can coexist, but only by adding a cryptographic authentication layer and redefining the merge function to reject unauthenticated state.

Concretely, Byzantine-fault-tolerant (BFT) CRDTs require three additions beyond a standard CRDT:

The trade-off is significant: BFT CRDTs are considerably more expensive in both computation (signature verification per operation) and communication (causal history exchange). As a result, they are mainly applicable to scenarios where Byzantine tolerance is a hard requirement — decentralised collaboration tools, auditability-first data structures, and multi-party computation — rather than typical enterprise distributed systems.

Active research areaBFT CRDTs are not production-standard yet. The Kleppmann et al. framework is theoretically sound but the engineering implementations remain nascent as of mid-2025. If you need Byzantine tolerance today, you are likely combining a standard CRDT with a Byzantine-fault-tolerant consensus protocol (BFT-SMaRt, Tendermint) for write validation, accepting the coordination cost on the write path.

7. What We Have Learned

This article walked through CRDTs from first principles to the cutting edge. To summarise the key takeaways:

  • The core trade-off: CRDTs trade strong consistency for availability and partition tolerance, guaranteeing eventual convergence without any coordinator or consensus round.
  • The math that makes it work: Join-semilattice structure — commutativity, associativity, idempotency — ensures that merging diverged states in any order always produces the same result.
  • Two families: State-based CRDTs (CvRDTs) transmit full state and need only eventual delivery; operation-based CRDTs (CmRDTs) are bandwidth-efficient but need exactly-once delivery. Delta-state CRDTs offer a practical middle ground.
  • Practical types: G-Counter (grow-only, slots per node), PN-Counter (two G-Counters), OR-Set (tagged additions with tombstoning), LWW-Register (timestamp-wins, clock-sensitive).
  • Hidden costs: Tombstone accumulation, semantic conflicts the data structure cannot resolve, no built-in causal ordering, and LWW’s dependence on synchronised clocks are all real production concerns.
  • The frontier: Byzantine-fault-tolerant CRDTs add cryptographic signing and causal history to resist malicious nodes — promising for decentralised systems but computationally expensive and not yet production-standard.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button