Part 7 · Failure
Consistency Models, Causality, Ordering, and Linearizability
Lesson 15 took away your two comforting illusions: that nodes can tell a crash from a slowdown, and that two machines share a clock. We patched the latter with a fencing token — a monotonically increasing number that lets a resource reject a stale leader's writes. But that token quietly assumed something big: that the system can hand out one agreed sequence of numbers that everybody respects. What does it even mean for a distributed store to "agree" on an order, given replication lag (lesson 08) and unsynchronized clocks (lesson 15)? This lesson builds the vocabulary for that question — a ladder of consistency models from weak to strong — and names the rung at the top: linearizability, the illusion that there is a single copy of the data. It is the most useful and the most expensive guarantee in distributed systems, and lesson 17 will spend an entire chapter learning how to actually build it.
New capability: Name precisely what reads a system promises to allow; tell linearizability apart from serializability; know why causal consistency is the strongest model you can keep and stay available under a partition; and put a number on what a strong read costs in latency before you ask for one.
1 · What a consistency model is, and why "eventually" is too weak
A consistency model is a contract between the storage system and the application: given a set of concurrent reads and writes from many clients, it spells out which return values a read is allowed to produce. It is not about whether the data is correct, valuable, or well-typed — it is purely about what a reader may legally observe given the writes that have happened. A strong model permits few histories (easy to reason about, expensive to provide); a weak model permits many (cheap, but you must defend against every anomaly it allows).
The weakest useful model is eventual consistency, which you met in replication (lessons 08 and 09): if writes stop, all replicas eventually converge to the same value. That is the entire promise. Notice the two weasel words. "Eventually" has no bound — it could be 5 ms or 5 minutes, and the model does not say. "If writes stop" never happens in a live system. So an eventually-consistent read may return a value that is arbitrarily stale, and you have no way to know how stale.
2 · The client-centric ladder: cheap guarantees that are often enough
Between "eventual" and "single copy" sits a ladder of client-centric guarantees — promises about what a single client's session sees, which a system can provide cheaply (usually by routing that client's reads carefully or stamping its requests) without global coordination. They were introduced as the cures for replication-lag anomalies in lesson 08; here is the ladder in order of strength:
These are not just weaker linearizability — they are a different kind of promise (about one session, or about causal pairs) that costs almost nothing because it never forces global agreement. The senior instinct: reach for the cheapest rung that removes the anomaly your product actually shows the user. A profile page needs read-your-writes. A feed needs consistent prefix so replies don't precede parents. A metrics dashboard tolerates plain eventual. None of these needs the top rung — which is the one we turn to now, and the one that costs real money.
3 · Linearizability: the single-copy illusion
One sentence: linearizability means the system behaves as if there were a single copy of the data, and every read returns the most recent write. (It is also called atomic consistency or strong consistency.) More precisely: every operation takes effect atomically at some single instant between the moment the client issued the call (the invocation) and the moment it got a response. Pick any execution; if you can slide each operation to a single point on one timeline — inside its own call/return window — and the result is a legal single-machine history, the execution was linearizable.
The consequence that matters most is the recency guarantee: the moment one read returns a new value, every read that starts later must also return that new value (or a still-newer one). There is no window where some clients see the new value and others still see the old. The data has, in effect, a single "now."
This is exactly the guarantee the fencing token of lesson 15 needed under the hood: for a fencing counter to be monotonic globally — so that every later request carries a strictly larger token than every earlier one — the counter itself must be linearizable. Linearizability is what makes "later" a well-defined, system-wide notion.
- Serializability (lesson 13, DDIA Ch.7) is an isolation property of transactions: a set of transactions, each touching many objects, produces a result equivalent to running them one-at-a-time in some serial order. It says nothing about which order, and nothing about real-time recency — a serializable execution may order transactions as if they ran at a time unrelated to the wall clock.
- Linearizability (this lesson, Ch.9) is a recency property of single objects: every operation appears to happen at one real-time instant, and reads see the latest write. It says nothing about grouping operations into transactions.
- They are orthogonal. You can have either without the other. The combination — transactions in a serial order that also respects real-time, i.e. linearizable and serializable — has its own name: strict serializability (what "Spanner-style external consistency" delivers).
4 · Causality, happens-before, and total vs partial order
Linearizability imposes a total order: every operation is comparable to every other, laid out on one line. That is more order than most operations actually need — and forcing it is what makes it expensive. The natural, cheaper notion of order is causality.
Two events are related by happens-before (written A → B) if A could have caused B: A finished before B started on the same node, or B read a value that A wrote, or there is a chain of such links between them. If neither A → B nor B → A, the two events are concurrent — they happened without knowledge of each other, and no causal order between them exists. This is the same "concurrent" you met with write conflicts in lesson 09; version vectors there were detecting exactly the absence of a happens-before relation.
Happens-before defines a partial order: some pairs are ordered (the causally-related ones), and some pairs are simply incomparable (the concurrent ones). Contrast a total order, where every pair is ordered. Causal consistency promises only to respect the partial order — effects never overtake their causes — while leaving concurrent operations free to be applied in any order on each replica. That freedom is the whole point: where there is no causal link, there is nothing to coordinate, so replicas need not talk.
How does a system track and impose order cheaply? Three mechanisms sit on a ladder of cost and power. Start with the cheapest.
Sequence numbers from a single leader. The cheapest total order is to route every operation through one node and have it hand out a monotonically increasing counter — operation 1, 2, 3, … This is the fencing counter of lesson 15, generalized: one authority, one strictly-increasing number per event. Because every operation gets a distinct number from a single source, you get a total order for free (just sort by the number), and because later operations always get larger numbers, that order is automatically consistent with causality — anything that happened-before was numbered earlier. It is exactly how a single-leader replicated database (lesson 08) orders its writes: the leader's append position is the sequence number. The catch is the obvious one: it needs a leader, and if the leader fails or partitions you must elect a new one and agree where its counter left off — which is consensus (lesson 17). Cheap order, but it inherits the leader's single point of coordination.
Lamport timestamps get a causally-consistent total order without a single leader. Each node keeps a counter; the rule is precise and worth memorizing:
The max-on-receive rule is what makes it work: whenever B hears from A, B fast-forwards its counter past A's, so any event B does afterward is numbered strictly higher than the event it heard about. Hence if A → B then timestamp(A) < timestamp(B), and the node-id tie-break makes every pair comparable, giving a total order consistent with causality. But that totality is bought by flattening concurrency: two genuinely concurrent events still get ordered (by whichever counter/id is smaller), and from two Lamport timestamps alone you cannot tell whether one caused the other or they were concurrent — the information is gone.
Version vectors (lesson 09) keep the concurrency information that Lamport timestamps throw away. Instead of one counter they carry n counters — one per node — so a timestamp records how much it has seen from every node. Comparison then has a third outcome that a single number cannot express:
So the same family resolves the same partial order at three different costs. Lamport = one number, total order, but it loses concurrency info (it must pick an order even for unrelated events). Version vectors = n numbers, partial order, and they detect concurrency (the one thing a single number cannot do) — which is exactly why lesson 09 reached for them to flag conflicting writes that need merging. The contrast is the whole point: choose Lamport when you want one agreed line and don't care which unrelated event came first; choose version vectors when you must know two writes were concurrent so you can merge rather than silently drop one.
But notice what even Lamport timestamps do not give you. They totally order events after the fact — but at the moment a node assigns a timestamp, it cannot know whether some other node is about to produce a lower one, so it can never be sure its operation is final at a given position. Enforcing a total order that every node can act on the instant it is decided — "this message is position 3, irrevocably, deliver it now" — is a strictly stronger primitive called total-order broadcast, and it turns out to be equivalent to consensus. That is the bridge into lesson 17: the cheap single-leader sequence numbers above are the simplest way to build it, and consensus is how you keep handing them out when the leader fails.
5 · The bill: CAP, and the latency you pay even with no partition
The famous CAP theorem is often mis-stated as "pick two of three." Precisely: when a network partition occurs (some nodes cannot reach others), a system that keeps serving must choose between C (linearizable reads/writes) and A (every non-failing node still answers). It cannot have both during the partition, because a node cut off from its peers either refuses to answer (sacrificing A to stay linearizable) or answers from its own possibly-stale copy (sacrificing C to stay available). When there is no partition, CAP says nothing — you can have both. So CAP is really "during a partition, C or A," and the everyday choice is which one your data needs.
Worked number — a cross-region linearizable read. Suppose three replicas, one each in Virginia, Ireland, and Singapore, with inter-region round-trip time (RTT) of 70 ms between the two nearest. A client in Virginia issues a read.
- Linearizable read (quorum, n = 3, r = 2): the local replica must confirm it holds the latest committed value, which means contacting at least one other region and waiting for the reply — one inter-region round trip. Effective read latency ≈ local processing (~1 ms) + 70 ms RTT ≈ 71 ms, on every read. Staleness: 0 by construction.
- Causal / eventual read (served from the local Virginia replica): no cross-region trip. Effective read latency ≈ 1 ms. Staleness: bounded only by replication lag — say ~20 ms of typical lag, occasionally more.
So the recency guarantee here costs about 70 ms × (read volume). At 50,000 reads/second that is 50,000 operations each paying an extra 70 ms of coordination latency — a colossal tax to pay on a feed counter or a recommendation cache that no human would notice was 20 ms stale. The discipline writes itself: spend linearizability only where staleness is actually unacceptable. The widget below lets you turn the RTT and consistency knobs and watch effective latency and worst-case staleness trade against each other.
6 · Where you actually need linearizability
Having priced it, here is the short list of jobs that genuinely require the single-copy illusion — and the common thread is that all of them enforce a uniqueness or single-winner invariant that causality alone (§4) cannot, because the contending operations are concurrent and causality refuses to order them:
| Job | Why linearizability is required |
|---|---|
| Locks / leases | Only one client may hold the lock at a time. If two clients could both "see" themselves as the holder (stale read), the lock is worthless — this is the fencing-token scenario of lesson 15. |
| Leader election | The system must agree on exactly one leader. Two nodes each believing they are leader (split brain) corrupts everything downstream. |
| Uniqueness constraints | A unique username, a unique email, a once-only account number. Two concurrent registrations of the same value must be ordered so exactly one wins — causality can't, because they're concurrent. |
| Cross-channel consistency | You write a file to object storage, then enqueue a message "process this file." The consumer must not read the queue and find the file missing — the two channels must agree on which write came first. |
For an ML platform: model-registry promotion ("which checkpoint is currently serving in prod?") wants linearizability — two trainers must not both believe their checkpoint is the live one. Distributed training leader/rank-0 election wants it. But feature aggregates, recommendation caches, click counters, and analytics rollups do not — they are eventual or causal, served locally, cheap. The senior move is per-operation consistency: one system exposing a strong path for the handful of control-plane invariants and a cheap local path for the high-volume derived reads.
Failure modes
- Unstated model. A system never declares its consistency, so users discover it by accident — stale permissions, a setting that flips back, a username registered twice. The model was eventual; nobody said so.
- Assuming linearizable because single-region. A leader plus async followers (lesson 08) is not linearizable: a read from a lagging follower violates recency. "We have one database" does not imply strong reads.
- Conflating it with serializability. "It's serializable, so reads are fresh" — no; serializable transactions can be ordered acausally to wall-clock time. You need strict serializability for both.
- Linearizability everywhere. Paying the cross-region coordination round trip on every read, including dashboards and caches, then blaming "the network" for p99 latency.
- Expecting availability from a strong store under partition. A linearizable system must refuse some requests during a partition (CAP). If it kept answering, it wasn't linearizable.
Decision checklist
- For each read path, write down the weakest model that hides the anomaly the user would notice — often read-your-writes or consistent prefix, not linearizable.
- Does this operation enforce a uniqueness / single-winner invariant (lock, leader, unique key)? If yes, it needs linearizability → consensus (lesson 17).
- Estimate the coordination tax: how many cross-region round trips, at what RTT, times your read/write volume?
- During a partition, must this path stay available (choose A, accept staleness) or stay correct (choose C, accept unavailability)?
- Are causally-related operations (post→reply, write-file→enqueue-message) tracked so effects never precede causes?
- If you claim "strong consistency," is it linearizable, serializable, or both (strict serializable)? Say which.
Checkpoint exercise
Where this points next
We have defined the prize — linearizability, the single-copy illusion with a recency guarantee — and shown exactly what it buys (uniqueness, locks, leader election) and what it costs (a coordination round trip per operation, and unavailability during a partition). What we have not done is build it. How do a handful of nodes that can crash, slow down, and disagree about the clock (lesson 15) come to irrevocably agree on a single value or a single ordering of events? That is the consensus problem, and lesson 17 shows that linearizable storage, total-order broadcast, locks, and leader election are all the same problem in disguise — then walks the machinery (Raft/Paxos, total-order broadcast) and the coordination services that package it so you rarely have to implement it yourself.
Interview prompts
- Why is eventual consistency hard to build on? (§1 — it promises only that replicas converge "eventually" with no time bound and only if writes stop; a read may return arbitrarily stale values and even appear to go backward, giving you no handle to reason "after X, a read sees Y.")
- Define linearizability and its recency guarantee. (§3 — the system behaves as one copy; each operation takes effect atomically at one real-time instant between its invocation and response, so the moment any read returns a new value, all later reads return that value or newer.)
- How is linearizability different from serializability? (§3 — serializability is an isolation property of transactions: equivalent to some serial order, no real-time recency. Linearizability is a recency property of single objects. Orthogonal; both together = strict serializability.)
- What is causal consistency and why is it the ceiling for staying available under a partition? (§4 — it preserves happens-before (a partial order via Lamport timestamps / version vectors) while letting concurrent ops reorder; a replica needs only the causally-prior writes, never the acausal concurrent ones a partition could hide, so it never has to block on a peer.)
- State the CAP theorem precisely, and the cost CAP omits. (§5 — during a network partition a serving system must choose linearizable-C or available-A, not both; when there's no partition you can have both. The omitted always-on cost is latency: a linearizable op pays a coordination round trip every time, ≈71 ms cross-region in the worked example, vs ≈1 ms local for causal/eventual.)
- Give two jobs that need linearizability and two that don't. (§6 — need it: locks/leases, leader election, uniqueness constraints, cross-channel ordering — all single-winner invariants causality can't settle. Don't: feature aggregates, recommendation caches, click counters, dashboards — eventual/causal, served locally.)
- Contrast single-leader sequence numbers, Lamport timestamps, and version vectors for ordering. (§4 — a single leader handing out a monotonically increasing counter gives a cheap total order consistent with causality but needs a leader (and consensus to fail it over). Lamport timestamps (counter, node-id) with the max-on-receive rule give a leaderless total order consistent with causality but lose concurrency info. Version vectors carry n counters, give the partial order, and uniquely can detect that two events were concurrent.)
- State the Lamport max-on-receive rule and why it preserves causality. (§4 — on receiving a message a node sets counter = max(local, msg.counter) + 1, so any event after hearing from A is numbered strictly above A's event; thus A → B implies timestamp(A) < timestamp(B), and the node-id tie-break makes the order total.)