Everything beyond the Educative lesson — CAP theorem, PACELC, linearizability, CRDTs, Paxos, Raft, TrueTime, and how real systems like Cassandra, Spanner, DynamoDB, and Kafka apply these concepts.
The deeper "why" behind every consistency trade-off
In the presence of a network Partition, a distributed system can guarantee either Consistency or Availability — but not both simultaneously.
CAP only covers the partition case. PACELC extends it: If Partition → choose C or A. Else (normal operation) → choose Latency or Consistency. Most real systems trade latency for consistency even without partitions.
| System | Partition behaviour | Normal behaviour | Classification |
|---|---|---|---|
| DynamoDB | Prefers Availability | Prefers Latency (async replication) | PA/EL |
| Cassandra | Prefers Availability | Tunable (ONE→ALL) | PA/EL |
| Google Spanner | Prefers Consistency | Prefers Consistency (TrueTime) | PC/EC |
| Zookeeper | Prefers Consistency | Prefers Consistency | PC/EC |
| MongoDB | Prefers Consistency | Prefers Latency | PC/EL |
Two terms often confused — they mean very different things
A single-object guarantee. Every operation appears to take effect atomically at some point between its invocation and completion. The system behaves as if there's one copy of the data. Also called "atomic consistency" or "strong consistency."
Key property: real-time ordering is preserved. If operation A completes before operation B starts, B must see A's result.
A multi-object / transaction guarantee. Concurrent transactions produce a result equivalent to some serial (one-at-a-time) execution. Does NOT require real-time ordering — a "serializable" execution might look like transactions ran in the past.
Key property: used in databases (SQL ISOLATION LEVEL SERIALIZABLE). Weaker than linearizability in terms of time.
The strongest guarantee: transactions appear to execute serially AND in real-time order. Google Spanner provides this. Most systems provide one or the other, not both.
Both terms use the word "consistency" in conversation. The difference: linearizability is about individual reads/writes on one object. Serializability is about transactions across multiple objects. A bank transfer (debit account A, credit account B) needs serializability. Reading the latest balance needs linearizability.
| Level | Prevents | Still allows | Real system |
|---|---|---|---|
READ UNCOMMITTED |
Nothing | Dirty reads, phantoms, lost updates | Rarely used |
READ COMMITTED |
Dirty reads | Non-repeatable reads, phantoms | PostgreSQL default |
REPEATABLE READ |
Dirty + non-repeatable reads | Phantom reads | MySQL InnoDB default |
SNAPSHOT ISOLATION |
Most anomalies | Write skew | CockroachDB, Spanner |
SERIALIZABLE |
All anomalies | — | PostgreSQL (explicit) |
How distributed systems reason about time without a global clock
// Each node tracks: (physical_time, logical_counter) // abbreviated as (pt, l) On send message: pt = max(pt, wall_clock_now()) l = l + 1 attach (pt, l) to message On receive message with timestamp (msg_pt, msg_l): if msg_pt == pt: l = max(l, msg_l) + 1 else if msg_pt > pt: pt = msg_pt l = msg_l + 1 else: l = l + 1 // Result: stays within a few ms of wall time, // but still captures causality within the same ms
Data structures that merge automatically — no coordination needed
CRDTs (Conflict-free Replicated Data Types) are data structures designed so that any two replicas can be merged and the result is always the same, regardless of merge order. This makes eventual consistency correct by construction — no conflict resolution logic needed.
Nodes periodically exchange their full state. Merge function must be commutative, associative, and idempotent. Example: a G-Counter (grow-only counter) where each node tracks its own increment. Merge = element-wise max. Value = sum of all.
Nodes broadcast operations (not full state). Operations must be commutative. Smaller messages — just send "increment" instead of full state. Requires reliable delivery (no lost ops). Example: collaborative text editors.
| Type | Operations | Merge rule | Used in |
|---|---|---|---|
| G-Counter | increment | element-wise max, sum | View counters, likes |
| PN-Counter | increment, decrement | two G-Counters (P - N) | Inventory, balance |
| G-Set | add | set union | Tags, membership |
| 2P-Set | add, remove | union of adds ∪ removes | Shopping cart (limited) |
| OR-Set | add, remove (any order) | unique tags per add | Riak, collaborative lists |
| LWW-Register | write with timestamp | last-write-wins | Profile fields |
| RGA / LSEQ | insert, delete at position | unique position IDs | Google Docs, Figma |
How distributed nodes agree on a single value despite failures
Given N nodes, some of which may crash or be unreachable, reach agreement on a single value such that: (1) only proposed values are chosen, (2) only one value is chosen, (3) a node only learns a chosen value. FLP impossibility theorem: no deterministic algorithm can guarantee consensus in an asynchronous system with even one crash failure. Paxos and Raft work around this with timeouts and probabilistic guarantees.
n higher than any
it has used before, and broadcasts Prepare(n) to a
majority of acceptors. It is asking: "Will you promise not to accept
any proposal numbered less than n?"
n is the highest proposal number the acceptor has
seen, it replies with a Promise — it will ignore any
future proposals with number < n. It also includes the value it
last accepted (if any), so the proposer can maintain safety.
Accept(n, v). The value v is either the
proposer's own value (if no acceptor returned a prior value), or the
highest-numbered previously accepted value from the promises.
Accept(n, v) accepts it if
n is still the highest proposal number it has promised.
It then notifies all Learners of the accepted value.
(n, v). Learners (which may overlap with
proposers/acceptors) can now act on this value. Multi-Paxos extends
this to a log of values by electing a stable leader to skip Phase 1
for subsequent entries.
Paxos: theoretical foundation. Flexible but underspecified — each implementation makes different choices. Used in Chubby (Google), Zookeeper (ZAB variant).
Raft: designed for understandability. Prescribes leader election, log replication, and safety rules explicitly. Used in etcd, CockroachDB, TiKV, Consul.
One leader at a time (per term). Leader replicates log entries to followers. Entry committed when majority acknowledges. Leader election via randomized timeouts — reduces split votes. Safety: a node only votes for a candidate whose log is at least as up-to-date as its own.
Cassandra, Spanner, DynamoDB, MongoDB, Kafka
Cassandra lets you choose consistency level per query. The
magic number: R + W > N guarantees strong consistency
(where R = read replicas, W = write replicas, N = total replicas).
Spanner uses atomic clocks + GPS to bound clock uncertainty to ~7ms. Every transaction must wait out this uncertainty before committing — this pause is the price of global consistency.
How distributed transactions work — and why they're fragile
Saga pattern: break a
distributed transaction into a sequence of local transactions, each
with a compensating action for rollback. No global locks — eventual
consistency with explicit rollback logic. Used in microservices.
Percolator (Google):
optimistic concurrency with timestamps. Read a snapshot, detect
conflicts at commit time. Used in TiKV / TiDB.
Calvin: pre-order
transactions before executing — avoids distributed locking entirely by
making sequencing the only coordination step.