Consistency and consensus
History of one register x (initially 0)
✗ not linearizableDrag any operation along its row to change when it ran; click a read or CAS to change what it returned. Green ticks are linearization points: the moment each operation appears to take effect atomically. Try: in the first history, drag B's first read left until it overlaps A's second read.
- A @4: no change here helps
- A @24: 0
- A @78: no change here helps
- B @44: 1
- B @64: no change here helps
Two datacenters, one register
t = 0.0 sTry: partition the WAN in each mode. Linearizable: DC2's clients start timing out, DC1 carries on. Available: everyone keeps working, but DC2 reads stale values until the network heals. Then slide the delay: in linearizable mode DC2 pays 2× it on every operation even with no fault at all.
Ordering and causality: Lamport timestamps vs version vectors
causal-consistencylamport-timestampsvector-clocklamport-uniqueness-gapThree nodes exchanging messages
Click events to compare two of them. Try: in the example (preselected), compare C's second write (counter 2) with A's second write (counter 3). Lamport puts C's write first, yet neither could have known about the other; only the version vectors reveal that they are concurrent. Then deliver m1 and m2 and watch the counters jump to max + 1.
Consistent with causality: every event sorts after everything in its causal past. But it is only known after the fact: a node cannot tell whether some other node has issued a smaller timestamp it hasn't heard about yet.
Total order broadcast: same messages, same order, everywhere
total-order-broadcastlamport-uniqueness-gapRacing username claims, replicated three ways
t = 0 ms- —
- —
- —
Each claim race sends the same fresh username to two different nodes at nearly the same moment. Try: run plain broadcast and watch replicas crown different owners; switch to total order broadcast and see out-of-order arrivals held back until the gap fills, so every replica applies the same log.
| name | N1 | N2 | N3 |
|---|
Two-phase commit across three databases
step 0Try: with “coordinator dies after votes”, step until the crash and press Wait a few times: the locks never go away. Then make a participant give up, and recover the coordinator with “dies after logging commit” to see why it isn't allowed to.
Fault-tolerant consensus: a Raft cluster you can break
consensusconsensus-algorithmsepoch-numberflp-impossibilityFive Raft nodes
t = 0.00 s- press Play: every node starts as a follower in term 0
Try: let a leader emerge, write a few entries, then partition the leader with one follower. The old leader keeps accepting writes it can never commit (outlined); the majority side elects a new leader in a higher term. Heal, and the old leader steps down and its uncommitted entries are overwritten. Then crash three nodes: nothing commits without a majority.
A lock on a coordination service
t = 0.0 s- n-0001 A zxid 31 ← lock
- n-0002 B zxid 32
- n-0003 C zxid 33
- 0.0s A is notified: it now holds the lock (token 31)
- 0.0s C creates ephemeral /lock/n-0003 (zxid 33)
- 0.0s B creates ephemeral /lock/n-0002 (zxid 32)
- 0.0s A creates ephemeral /lock/n-0001 (zxid 31)
Try: press Play, then GC-pause the lock holder for longer than the 3-second session timeout. Its session expires, the ephemeral znode vanishes and the next client is notified. Resume it: it still thinks it holds the lock. With fencing on, its stale token is rejected; turn fencing off and repeat to see the corrupt write.
- takeawayLinearizability is a recency guarantee: once a new value is visible to anyone, it is visible to everyone. It is invaluable for locks, leader election and uniqueness, but every operation costs a round trip, and a partitioned minority must refuse to answer.
- takeawayCausality is a partial order that can be tracked cheaply (Lamport timestamps, version vectors). Deciding things the moment they happen needs a total order everyone agrees on, which means total order broadcast, which is consensus.
- takeaway2PC is consensus that blocks when the coordinator dies. Raft, Paxos and Zab survive any minority of failures by using epochs and overlapping majority quorums. Most applications should borrow them through ZooKeeper or etcd rather than reinvent them.
Key concepts · 20
book chapter 9- Eventual consistency
If writes stop, all replicas eventually return the same value; until then, reads may return anything older.
It is a very weak guarantee: it says nothing about when replicas converge, and it makes reads after writes unpredictable. Stronger models cost performance, which is why many systems settle for it.
Dynamo-style storesasync followers↑ see it in the visualization - Linearizability
Make a replicated object behave as if there were one copy and every operation took effect atomically at a single point between its start and end.
It is a recency guarantee: once any read returns a new value, all later reads (in real time) must too. Checking it means finding a total order of operations that respects real time and register semantics.
etcdZooKeeper writesSpanner↑ see it in the visualization - Compare-and-set
An atomic operation that sets a value only if it currently equals an expected value, and reports whether it succeeded.
With a linearizable register plus CAS you can implement locks, leader election and uniqueness constraints; whether a CAS may succeed depends on the single global order of all writes.
↑ see it in the visualization - CAP theorem
During a network partition, a system must choose between linearizability and availability.
Partitions are not optional, so CAP really says: when one happens, either some clients get errors (stay consistent) or everyone gets answers that may be stale (stay available). It is a narrow theorem, more slogan than design tool.
↑ see it in the visualization - Cost of linearizability
Linearizable reads and writes have response times at least proportional to network delay.
Most systems that drop linearizability (even multicore CPU caches) do it for speed, not for partition tolerance. On a WAN with variable delays, every linearizable operation pays the round trip.
↑ see it in the visualization - Causal consistency
Operations that are causally related are seen by everyone in the same order; concurrent ones may be seen in any order.
Causality gives a partial order, linearizability a total one. Causal consistency is the strongest model that does not slow down with network delay and stays available during partitions.
↑ see it in the visualization - Lamport timestamps
A (counter, node id) pair; every node sends its counter on every message and, on receipt, sets its counter to max(local, received) + 1.
They give a compact total order that is consistent with causality: if A happened before B, A's timestamp is smaller. The converse fails: a smaller timestamp does not mean the event came first causally.
↑ see it in the visualization - Version vectors (vector clocks)
One counter per node, merged element-wise on each message, so each event carries a summary of everything it has seen.
Unlike Lamport timestamps, vectors can tell whether two events are concurrent or causally ordered, at the cost of size growing with the number of nodes.
RiakDynamo↑ see it in the visualization - Why timestamps aren't enough
A total order of timestamps can pick a winner among conflicting claims only after all claims have been collected.
To enforce 'one owner per username' at the moment of the request, a node must know no other node is concurrently claiming it with a lower timestamp, which requires hearing from every node. You also need to know when the order is final: total order broadcast.
↑ see it in the visualization - Total order broadcast
A protocol that delivers every message reliably to every node, and in the same order on all of them.
It is state machine replication: apply the same deterministic operations in the same order and replicas stay identical. It is equivalent to repeated consensus, and lets you build linearizable compare-and-set on top of an append-only log.
ZooKeeper Zabetcd RaftKafka (per partition)↑ see it in the visualization - Atomic commit
Ensuring that a transaction spanning several nodes either commits on all of them or aborts on all of them.
Committing independently on each node risks some committing while others abort, violating atomicity. Once committed, a transaction cannot be retracted, so the decision must be made together.
↑ see it in the visualization - Two-phase commit (2PC)
A coordinator asks every participant to prepare (vote), then writes a commit or abort decision to its log and broadcasts it.
Two points of no return make it work: a participant that votes yes can no longer abort on its own, and the coordinator's logged decision is final. Its weakness is that it blocks if the coordinator dies.
↑ see it in the visualization - In-doubt transaction
A participant that has voted yes but not yet heard the decision: it can neither commit nor abort.
It must hold its locks until the coordinator recovers, blocking other transactions. Orphaned in-doubt transactions sometimes need a human (heuristic decision) to resolve, which risks breaking atomicity.
↑ see it in the visualization - XA transactions
A standard C API for two-phase commit across heterogeneous systems such as databases and message brokers.
The coordinator is often a library inside the application, so if the application server dies, its log goes with it. XA must also be lowest common denominator: it cannot detect deadlocks across systems or support SSI.
JTAMySQL XAPostgreSQL PREPARE TRANSACTION - Consensus
Getting several nodes to agree on a value, with uniform agreement, integrity, validity and termination.
Leader election, atomic commit, uniqueness, linearizable CAS and total order broadcast all reduce to it. Unlike 2PC, fault-tolerant consensus keeps making progress as long as a majority is up.
↑ see it in the visualization - Fault-tolerant consensus algorithms
Algorithms such as Raft, Multi-Paxos, Zab and Viewstamped Replication that decide a sequence of values (a log).
They elect a leader per epoch and require a majority quorum both to elect it and to accept each proposal; any two majorities overlap, so a decision can never be lost or contradicted.
RaftPaxosZabVSR↑ see it in the visualization - Epoch (term) numbers
A monotonically increasing number attached to each leadership; a higher epoch always wins.
A leader cannot trust that it is still leader. Before deciding anything it needs a quorum of acknowledgments in its epoch, so a deposed leader from an older epoch can never get a value committed.
↑ see it in the visualization - FLP impossibility
In a fully asynchronous model with no clocks, no deterministic algorithm can always reach consensus if a node may crash.
Real systems escape it with timeouts (to suspect crashed nodes) or randomness. That is why election timeouts are randomized and why consensus can stall when the network is very unreliable.
↑ see it in the visualization - Coordination service
A small, fault-tolerant, linearizable key-value store that applications use for locks, leader election, membership and configuration.
It outsources consensus: few apps should implement Raft themselves. It offers linearizable atomic operations, total ordering (zxid as fencing tokens), failure detection via sessions, and change notifications.
ZooKeeperetcdConsul↑ see it in the visualization - Ephemeral nodes and sessions
A znode tied to the client session that created it; when heartbeats stop and the session times out, it is deleted automatically.
It turns failure detection into data: a lock or membership entry disappears when its owner dies. But a paused client may not know its session expired, so the resources it guards must check fencing tokens.
ZooKeeperetcd leases↑ see it in the visualization