Part 3 · Demand
Reliability, Scalability, SLOs, and Tail Latency
Lesson 05 kept a record's meaning stable as code changes underneath it — a contract across time. Now real users arrive, and the system must answer to demand: load, latency, and the promises you make under both. This lesson defines the yardstick the whole track is measured against — the three non-functional properties every design must keep true: what stays correct when components fail, what stays fast enough when load grows, and what stays changeable when humans keep editing the software. Every later mechanism — replication, partitioning, quorums, streams — is a move that buys one of these and bills another. You cannot grade a move until you can name what it protects.
New capability: A vocabulary for the three constraints themselves. After this you can state a load with concrete parameters, read a latency distribution in percentiles instead of averages, predict how tail latency amplifies under fan-out, and judge any design by which promise it protects and which it taxes.
1 · Reliability: correct behavior despite faults
Reliability is the promise that the system keeps doing what the user expects even when things go wrong. To make that precise we need two words people routinely confuse.
A fault is one component deviating from its spec: a disk returns a corrupt block, a process pauses for two seconds in garbage collection, a network packet is dropped, a deploy ships a logic bug, an operator runs DROP TABLE on the wrong host. A failure is when the system as a whole stops providing its required service to the user. The entire discipline of reliability is the art of preventing faults from cascading into failures. A fault is local and expected; a failure is global and is the thing you actually promised not to do. A system that tolerates faults can experience a thousand of them a day and never fail.
Faults come in three classes, and each demands a different defense.
The key asymmetry: redundancy defeats the independent faults (hardware) but is useless against the correlated ones (a bug deployed everywhere). That is why reliable systems do more than keep spare copies — they build idempotent operations so retries do not double-apply work, keep a durable log before acknowledging a write so a crash mid-operation is recoverable (we built that log in lesson 03), and expose enough observability that a human sees partial failure before it becomes corruption.
ML framing. In ML infrastructure the same logic governs the model registry and training-data lineage. A failed feature-transform job should not silently emit a half-empty training set (a fault becoming silent data corruption). A model promotion should be atomic — it either fully happens or not at all — so a crash never leaves the serving tier pointing at new weights while still feeding them yesterday's feature schema. The promotion writes its intent to a durable log first, then flips the pointer; a crash replays cleanly.
2 · Scalability: describe the load before you solve it
Scalability is the system's ability to cope with increased load. The word is meaningless on its own — "is X scalable?" is a non-question — until you answer two sub-questions concretely: what is the load? and what happens to performance when it grows?
Load is described with load parameters: the handful of numbers that characterize what the system is being asked to do. Which numbers matter is workload-specific, and picking the right parameter is half the problem. The social home timeline is the canonical worked example this whole topic hangs on — DDIA 2e uses it to ground load, latency, and scalability, and so do we. The naive load parameter is "tweets posted per second," but the one that actually sizes the system is the fan-out: each post must be delivered to all of an author's followers, and a celebrity with 30 million followers turns one write into 30 million timeline inserts. The system that looks easy under "posts/sec" is brutal under "timeline deliveries/sec" — so the design (push to followers' timelines on write, vs. pull from authors on read, vs. a hybrid that pulls only for celebrities) is chosen by which load parameter dominates. We carry this example through percentiles and fan-out below; the full end-to-end design — read/write paths, the fan-out hybrid, the SLOs — is the worked case at lesson 29 (Case: Social Home Timeline).
The same lesson holds for a model-serving endpoint, whose load parameters might be: requests per second, the read/write ratio, the fan-out per request (how many downstream feature lookups one inference triggers), the cache hit rate, and the freshness SLA on features. The same nominal QPS can be trivial or brutal depending on the parameters: 20,000 req/s where each request reads one cached row is easy; 20,000 req/s where each request fans out to 100 feature lookups across a sharded store is a very different machine.
Percentiles, properly
Once load is named, performance is described as a distribution of response times — and you read that distribution at percentiles, never as a mean. A percentile pNN is the value below which NN% of requests fall. Sort every request's latency from a window and:
- p50 (the median): half of requests are faster than this. The "typical" experience.
- p95: 95% are faster, 1 in 20 is slower. The edge of the common case.
- p99: 1 in 100 requests is slower than this.
- p99.9: 1 in 1,000 requests is slower. The "tail."
Why not the mean? Because the mean is dominated by, and yet hides, the tail. Consider ten requests with latencies, in milliseconds: 10, 10, 12, 12, 13, 14, 15, 16, 18, 900. The mean is (10+10+12+12+13+14+15+16+18+900) / 10 = 102 ms — a number describing no actual request: nine were near 13 ms and one was catastrophic. The p50 is 13 ms (the honest "typical"), and the p90 is 900 ms (the honest "worst common case"). A single average of 102 ms would let you believe the system is mediocre-but-uniform when in truth it is excellent-but-occasionally-broken. The tail is where queueing, GC pauses, retries, TCP retransmits, and overloaded partitions reveal themselves — and the tail is what your most active users hit, because the more requests a user makes, the more likely one lands in the slow bucket.
Tail-latency amplification: the killer of fan-out systems
Now the move that makes tail latency a systems problem and not just a metrics nicety. Suppose a request fans out to N backends and must wait for all of them before it can respond (the model endpoint above — it cannot rank until every feature has arrived). Suppose every backend is healthy and independently meets p99 = 10 ms: each backend is "fast" (under 10 ms) with probability 0.99 and "slow" on the unlucky 1%.
The parent request is slow if any one of its N backends is slow. With independent backends, the probability that all N are fast is 0.99 ^ N, so:
Read N = 100 carefully: a 1%-rare event at each backend has become a 63% chance at the parent. The "p99 of one backend" is now roughly the p37 of the fanned-out request — the slow case stopped being rare and became the common case. This is tail-latency amplification: fanning a request out to many backends multiplies the chance of touching at least one slow one, so the parent's tail is far worse than any child's tail. Worse, your busiest users (highest QPS, highest fan-out) are exactly the ones who feel it most.
3 · Throughput vs response time; vertical vs horizontal
Two pairs of words that are easy to slur together and costly to confuse.
Response time is what a single client waits — the time from sending a request to receiving the answer, including queueing. Throughput is how many requests the system completes per unit time. They are not the same dial, and they trade against each other: batching ten lookups into one call raises throughput (fewer round trips, better amortization) but can raise the response time of the first request in the batch (it waits for the batch to fill). A system tuned for maximum throughput (large batches, full queues) often has terrible tail latency, because a full queue means every new request waits behind a long line.
When load outgrows one machine, you scale one of two ways. Vertical scaling (scaling up) means a bigger machine — more CPU, RAM, faster disk. It is operationally simple (one node, strong local transactions, no distributed coordination) but hits a ceiling: the largest machine money can buy, and its cost grows super-linearly. Horizontal scaling (scaling out) means more machines sharing the load. It has effectively no ceiling, but it imports every hard problem in this track — replication (lesson 08), partitioning (lesson 11), and the consistency questions that follow — because state now lives in more than one place and those places can disagree. The honest rule: scale up until it hurts, scale out when you must, and know that scaling out is what turns the rest of this curriculum on.
4 · Maintainability: the bill a human pays
Maintainability is not the soft sibling of the other two. Most of the cost of software is not the initial build; it is the ongoing care — fixing faults, keeping it running, adapting to new requirements, paying down complexity. Maintainability is the property that decides whether the system can keep changing without collapsing under its own special cases. DDIA breaks it into three:
| Sub-property | What it means | What erodes it |
|---|---|---|
| Operability | People can keep the system running smoothly and understand what it is doing: dashboards, logs, runbooks, predictable behavior, clear ownership, the ability to backfill and replay. | Opaque state, no telemetry, manual recovery steps, surprises during deploys. |
| Simplicity | The design has fewer states a new engineer must hold in their head. Managing complexity, not raw line count — removing accidental complexity that the problem did not require. | Tangled special cases, leaky abstractions, a distributed-transaction protocol that adds stuck-coordinator states to reason about. |
| Evolvability | Schemas, APIs, encodings, and derived views can change while old and new code coexist. The system bends to new requirements instead of fighting them. | Rigid schemas with no versioning, no backward/forward compatibility (the home of lesson 05), tightly coupled services. |
The recurring trade-off this track will hammer: a design can buy scale by adding copies and asynchronous workflows, but every copy is another thing that can be stale, broken, or forgotten during a migration, and every async hop is another failure mode (lag, duplicates, replays, poison messages). Maintainability is the question of whether the on-call engineer can actually operate that design at 3 a.m. on a bad Tuesday — which is the only time it truly matters.
Failure modes
- Treating redundancy as a cure-all. Replicas defend against independent hardware faults but are useless against a bug deployed to every node at once. Correlated faults need testing, canaries, and rollback — not more copies.
- Reporting the mean. An average latency hides the tail entirely and gives no enforceable guarantee. One outlier moves the mean off every real request. Always quote percentiles.
- Ignoring fan-out amplification. Shipping a per-backend p99 SLA while the user-facing request fans out to 100 of them — the parent's tail is far worse than any child's, and grows with N.
- Running near saturation. Sizing for average utilization with no headroom, then watching queueing latency explode the first time traffic spikes.
- Untested failover. A recovery path that has never run is not a feature; its first execution is during your incident. If you never induce faults, you never learn it is broken.
- Buying scale with un-operable complexity. Adding async copies and services until no human can reason about the system's states, trading a throughput win for a maintainability collapse.
Decision checklist
- Which faults are independent (redundancy helps) vs correlated (it does not)? Name a defense for each class.
- Have you written down the load parameters — QPS, read/write ratio, fan-out, cache hit rate, freshness SLA — before discussing scale?
- Is the latency SLA a percentile (p99/p99.9) at a named utilization, never a mean?
- What is the fan-out N of a user-facing request, and what per-backend percentile does that force?
- What headroom are you running with, and does the tail stay bounded under a 2× traffic spike?
- Do you induce faults on purpose so the failover path is exercised before an incident needs it?
- For each new copy or async hop you add for scale: who operates it, how does it recover, and how does a human see it go stale?
Checkpoint exercise
Where this points next
We now have the yardstick: reliability (correct despite faults), scalability (fast enough as load grows, measured in percentiles and stressed by fan-out), and maintainability (changeable on a bad Tuesday). Every choice from here is graded by which of these it protects and which it taxes. But naming the demand is not meeting it — the home timeline still has to answer reads in single-digit milliseconds. Lesson 07 takes the first concrete swing at demand: the indexes, caches, and serving paths that make a read cheap enough to hit its SLO. We will see how an index trades write cost for read speed, how a cache shifts the load parameters (hit rate becomes the dial that decides whether 20,000 req/s is trivial or brutal), and how the serving tier holds a tail-latency budget under fan-out.
Interview prompts
- What is the difference between a fault and a failure, and why does it matter? (§1 — a fault is one component deviating from spec; a failure is the whole system stopping its promised service. Reliability is preventing faults from cascading into failures, so you design assuming faults are normal.)
- Name the three fault classes and which one redundancy does not fix. (§1 — hardware faults are independent and cured by redundancy; software faults are correlated across all nodes running the same code, so redundancy fails — testing/isolation/canaries do; human faults are the biggest cause, cured by good abstractions, sandboxes, and fast rollback.)
- Why report p99 instead of the mean latency? (§2 — the mean is dragged off every real request by outliers and gives no enforceable guarantee; a percentile names the worst 1-in-100 you can write into an SLA, and the tail is where queueing/GC/retries and your busiest users live.)
- Explain tail-latency amplification with a number. (§2 — if each of N backends is slow 1% of the time and the parent waits for all, P(slow) = 1 - 0.99^N: 9.6% at N=10, 63.4% at N=100. The rare backend tail becomes the common parent case.)
- A request fans out to 100 backends; what p99 must each hit to keep the parent at p99? (§2 — solve p^100 = 0.99 → p = 0.99^(1/100) ≈ 0.9999, i.e. roughly p99.99 per backend. Chasing nines is expensive, so reduce N (cache/batch) or hedge to a second replica instead.)
- When do you scale vertically vs horizontally? (§3 — scale up for operational simplicity until you hit the biggest/most-expensive machine; scale out when you must, accepting replication, partitioning, and consistency — the rest of this track — because state now lives in multiple places that can disagree.)
- Why is running at 90% utilization risky if 50% looks fine? (§3 — response time degrades hyperbolically near saturation: queueing time explodes as idle gaps vanish, so the tail blows up without any single request costing more. SLOs must name a percentile at a named utilization with headroom.)