Chapter Deep Read · Database Internals · Ch 8
Database Internals · Ch 8 · Alex Petrov · 2019
You tap "Confirm payment" on your phone. That message crosses a cell tower, some fiber, and several machines in a data center before it lands in a database. The first seven chapters were all about what happens inside one machine. From here on the stage changes: many machines, and the only way they can cooperate is by shouting at each other. This chapter teaches no technique. It tells you which things, on that stage, are impossible in principle.
You text a friend "does 8pm work?" and ten minutes pass with no reply. Maybe the message never arrived. Maybe they replied and the reply got lost. Maybe they are typing right now. From the outside, all three look exactly the same. That indistinguishability is the sharpest edge in distributed systems: when something times out, you do not know whether the other side is dead, slow, or simply had its answer eaten on the way back.
On a single machine, three things come for free: call a function and it will return; read a variable and you get the latest value; glance at the clock and time always moves forward. Across two machines, none of the three survives. A function call becomes "mail a letter and maybe never hear back". "The latest value" becomes "the last thing I heard". And the two clocks may drift seconds apart.
The chapter's move is not to hand you a cure. It is to pin down the vague parts, turning "the network is flaky" into three written-down assumption tables that every later algorithm is designed against:
Stronger promises cost more. The clearest case is "delivered exactly once, no more, no less" — that one is unachievable at the wire level: when the sender sees a failure, it can never tell "never arrived" from "arrived, was processed, and the receipt got lost". What you can actually do is send several times and let the recipient de-duplicate by tracking number. Payment idempotency keys and message-queue dedup tables are all instances of that one rule.
The honest cost: these three tables buy you the ability to say why your design is correct, not reliability itself. Assume too little and no algorithm can run; assume too much and one bad day in the real world takes the whole thing down.
Distributed systems are hard not because there are many machines, but because nobody can see "now". Every machine holds only a stale rumor. The one thing engineering can do is nail down how reliable the link is, how trustworthy the clock is, and how nodes break — and only then start building on top.
Want the mechanisms, the notation and the diagrams? → Switch to the deep read
You think the hard part of distributed systems is having many machines. It is really that no observer can ever see "now". This chapter contains not a single algorithm; it hands you the assumption table that every algorithm in Part II shares — how reliable the link is, how trustworthy time is, and how nodes fail. Fill those three cells in wrong and consensus, replication and anti-entropy are all built on sand.
0.1–0.5 ms inside a data center, commonly 100–200 ms across continents.Part I was about storage engines inside a single machine (B-Trees, LSM-Trees, transaction recovery). Part II steps outside, starting here. It follows Ch 7 and leads into Ch 9 failure detection, Ch 10 leader election, Ch 11 replication and consistency, Ch 12 anti-entropy and dissemination, and on to consensus in Ch 13–14. Which real systems does it map to? Everything with more than one replica — etcd / ZooKeeper, Cassandra / DynamoDB, Kafka, Spanner / CockroachDB. Their disagreements all live inside the coordinate system this chapter draws.
Single-machine programmers get three assumptions for free: a call returns, a read is current, a clock moves forward. Across machines all three die, and they die quietly — not with an error at first, but correctly under light load and wrongly under pressure. The book invokes Peter Deutsch's "fallacies of distributed computing" and names the first few: the network is reliable, latency is zero, bandwidth is infinite, topology doesn't change.
What happens if you ignore this? Three classic crashes. Ordering events by wall-clock time — two machines' now() can differ by milliseconds to tens of milliseconds between NTP corrections, so the later write is judged earlier and data is silently overwritten. Treating a timeout as death — the old leader was merely in an 8-second GC pause, a new leader is already elected, and two leaders accept writes at once (split brain). Blindly retrying on a network error — the first attempt actually succeeded, so the customer is charged twice. None of these is a coding bug; each is a wrongly filled assumption table.
The book's definition is plain: a distributed system is a set of independently executing processes that communicate only by passing messages. One consequence follows immediately — there is no "current global state". Each node holds only two things: its own local state, and whatever messages have arrived. No god's-eye view, no snapshot you can take in one shot.
So any claim about "what the system looks like right now" is only one node's guess at one moment, and it is stale by construction — stale by at least one one-way network delay. Deployed across continents, "latest" is at least 50 ms old. That is not a weak implementation; that is the speed of light.
The most immediately useful section. It splits "the network" into three abstractions, each adding a guarantee:
The line to take away: a "reliable link" is not a property of the network; it is a floor you built on top of an unreliable one out of retries and bookkeeping. And you built it imperfectly — in practice "retransmit forever" is always "retry N times, then time out", so the perfect link is an ideal model, not a product you can buy.
Ordering is a ladder too, four rungs from weak to strong: no order (arrive in any sequence) → FIFO order (messages from one sender are delivered in the order that sender emitted them) → causal order (causally related messages never invert; unrelated ones may arrive in any order) → total order (every node observes exactly the same sequence). Each rung costs more: FIFO needs one increasing counter per link; causal order needs causality metadata such as vector clocks; total order is essentially equivalent to running consensus, which is the job of Ch 13–14.
Delivery semantics have three rungs as well: at-most-once (fire and forget, may be lost), at-least-once (retry until acknowledged, may be duplicated), and exactly-once. That third one is the sentence to carry out of the chapter: at the link level, exactly-once is impossible. When a sender sees a failure or a timeout, it cannot separate "the message never arrived" from "it arrived, was processed, and the acknowledgement was lost" — the only status it can trust is success. What the industry calls exactly-once is always at-least-once plus idempotent de-duplication at the receiver: every message carries a unique ID, and the receiver keeps a table of IDs it has already processed.
The Two Generals' Problem: two armies on opposite sides of a valley can only coordinate a simultaneous attack through messengers who cross enemy ground and may be captured. The result: no matter how many rounds of confirmation you exchange, the sender of the last message can never know whether it arrived. Over a link that may drop messages, two parties cannot reach certain agreement.
FLP impossibility (Fischer–Lynch–Paterson, 1985): in an asynchronous system, even if only one process may crash, there is no deterministic consensus algorithm that guarantees both termination and agreement. The root cause is the same old line — in an asynchronous system you cannot tell "crashed" from "merely slow".
The point is not "so consensus is impossible", it is that you must loosen one screw: add timing assumptions (use timeouts to treat the system as partially synchronous — the Raft and Paxos route), give up determinism (randomized consensus), or give up the promise of termination (never wrong, but possibly unable to elect a leader under adversarial scheduling). Real systems almost always pick the first: trade timeouts for liveness, and accept that timeouts lie sometimes.
The same algorithm yields different conclusions under different timing assumptions, so this cell must be filled first:
The last cell is the shape of failure, cheap to expensive:
f crashes needs 2f+1 nodes; tolerating f Byzantine nodes needs 3f+1.Database systems do not assume Byzantine faults by default — the machines sit in one organization's data center, and paying triple for the possibility of lying is not worth it. The scenarios that open that cell are blockchains and other mutually distrustful settings.
Table 1 · Delivery semantics: what you promise is what you pay for
| Semantics | How it is built | Cost / risk | When to pick it |
|---|---|---|---|
| At-most-once | Send and forget, no retry | May be lost, and you will not know it was | Metrics, analytics events, telemetry that tolerates sampling loss |
| At-least-once | Retry until acknowledged | Duplicates are certain; ordering is lost too | The default rung for almost every messaging system |
| "Exactly-once" | At-least-once + unique ID + receiver dedup table | The dedup table needs storage and an expiry policy; it holds only at one boundary and breaks again across systems | Charges, orders, billing — anywhere a replay costs money |
Table 2 · Timing assumptions: get this cell wrong and the algorithm either cannot be written or cannot stay up
| Model | Assumption | What follows | Where it lives |
|---|---|---|---|
| Synchronous | Known upper bounds on transfer and processing | Timeout = sound failure detector; consensus is easy | Textbooks and special-purpose hardware networks |
| Partially synchronous | Bounded most of the time, occasionally not | Trade timeouts for liveness; safety never depends on clocks | Raft / Paxos implementations, etcd, CockroachDB |
| Asynchronous | No timing bounds whatsoever | FLP: deterministic consensus is impossible | Closest to real networks, and the most powerless |
Table 3 · Failure models: the wilder the assumption, the more replicas it costs
| Model | How a node breaks | Nodes to tolerate f | Who uses it |
|---|---|---|---|
| Crash-stop | Down and never back | 2f+1 | The starting point for analysis |
| Crash-recovery | Comes back with possibly stale state | 2f+1 + durable log | etcd, ZooKeeper, every practical consensus implementation |
| Omission | Alive but dropping messages (GC, overload) | Same, but failure detectors misjudge it | The most common failure in real production |
| Byzantine | Arbitrary behavior, including lying | 3f+1, and more message rounds | Blockchains, cross-organization systems; databases opt out |
The value of this chapter is that it converts the platitude "distributed systems are hard" into three questions you can ask out loud in a design review — which rung of link are we assuming? which timing model? which failure model? The same applies in interviews: asked "how do you guarantee messages are neither lost nor duplicated", answering "we use the MQ's exactly-once" scores nothing. Answering "exactly-once is impossible at the link level, so we do at-least-once delivery plus idempotent de-duplication at the consumer, with a dedup table keyed on the business ID and retained for 7 days" is the answer of someone who has done it.
① A distributed system is independent processes cooperating only by messages; no global state, no snapshot, no god's-eye view — each node holds one stale rumor.
② The three free single-machine assumptions (a call returns, a read is current, clocks move forward) all die across machines, and they only show it under load.
③ The link ladder: fair-loss (lost / duplicated / reordered) → stubborn (infinite resends ⇒ eventual delivery) → perfect (dedup ⇒ validity / no duplication / no creation). Reliability is stacked, not given.
④ The ordering ladder: none → FIFO → causal → total; total order is essentially one round of consensus, and the most expensive.
⑤ Exactly-once is impossible at the link level. Engineering "exactly-once" is always at-least-once + unique IDs + a receiver dedup table.
⑥ Two Generals: over a lossy link, the sender of the last message can never confirm it arrived.
⑦ FLP: in an asynchronous system, even with just one possible crash, no deterministic algorithm gives both termination and agreement — because "crashed" and "slow" are indistinguishable.
⑧ The way out is to loosen a screw: adding timing assumptions (partial synchrony) is the universal choice — timeouts buy liveness, while safety is never built on clocks.
⑨ Failure models: crash-stop / crash-recovery / omission / Byzantine. f crashes need 2f+1 nodes, f Byzantine nodes need 3f+1; databases decline to pay the Byzantine bill.
⑩ The three review questions: which link rung, which timing model, which failure model are we assuming? Without answers, the replication and consensus that follow are castles in the air.