Matías Fernández / System Design DDIA · CHAPTER 01

System Design DDIA Chapter 01

Reliable, scalable,
maintainable.

The opening chapter does something quietly ambitious: before any database, index or queue is discussed, it argues that almost every meaningful decision in a data system can be traced back to three concerns. Everything in the remaining eleven chapters is a technique for buying one of them, usually at the expense of another.

PARTI · Foundations
CONCERNS3
CENTRAL CASETwitter fan-out
READING~15 min

01 / THE PREMISE

Data-intensive,
not compute-intensive.

The systems most of us build are rarely limited by raw CPU. They are limited by the amount of data, its complexity, and the speed at which it changes.

That shift has a consequence the chapter draws out carefully. No single tool covers the whole job any more, so we assemble systems out of parts: a database for the source of truth, a cache for the expensive reads, a search index for queries the database is bad at, a queue for work that should not block the request, a stream or batch processor for everything derived.

The moment you put an API in front of that assembly, something changes that is easy to miss. You have not integrated tools — you have built a new data system, and its guarantees are now yours. The cache does not promise it agrees with the database. The search index does not promise it is current. Whatever consistency your users experience is a property you designed, deliberately or not.

FIGURE A The boundary you become responsible for
CLIENT

YOUR SERVICE — one API, one implied set of guarantees

DATABASEsource of truth CACHEmay be stale SEARCH INDEXeventually current QUEUEasync, retried

No component promises the others agree with it. That promise is the one you make.

01

Reliability

It keeps working correctly when something goes wrong — hardware, software, or a person.

02

Scalability

There are reasonable options for keeping performance acceptable as load grows.

03

Maintainability

People can operate it, understand it, and change it without fear — for years.

02 / RELIABILITY

Continuing to work correctly
when things go wrong.

The definition is doing real work. Not "nothing goes wrong" — things will go wrong. The claim is that the system keeps performing its function anyway.

The distinction the chapter insists on is between a fault and a failure. A fault is one component deviating from its spec. A failure is the system as a whole stopping. They are not the same event, and the entire discipline lives in the gap between them: fault tolerance means arranging things so that a fault never gets to become a failure.

FIGURE B The gap where fault tolerance lives
FAULTreplica goes down requests pile up threads exhausted FAILUREservice is down
FAULTreplica goes down health check ejects it traffic reroutes CONTAINEDdegraded, still serving

Same fault, two systems. The difference is not luck — it is whether this specific fault was named in advance and given a path that stops short of the user.

There is a counter-intuitive move here that is worth sitting with: the way to trust fault tolerance is to deliberately increase the rate of faults. Killing processes at random in production is not recklessness; it is the only way to keep the tolerance path exercised, because an untested recovery path is not a recovery path.

Hardware faults

Random and mostly independent. One machine failing says little about the next.

LOOKS LIKE

A disk dies, a memory module develops a bad cell, a rack loses power, someone unplugs the wrong cable.

WHAT ACTUALLY HELPS

Redundancy first. But past a certain scale the answer shifts to software tolerance across machines, which buys something extra: you can patch and reboot nodes one at a time, with no planned downtime.

Software faults

Systematic and correlated. The same latent bug lives on every node and fires on every node at once.

LOOKS LIKE

The 2012 leap second that hung Linux kernels simultaneously worldwide; a runaway process exhausting a shared resource; a slow dependency cascading into its callers.

WHAT ACTUALLY HELPS

No single countermeasure. Test the assumptions, isolate processes, let things crash and restart, and measure behaviour in production so a drifting system announces itself.

Human faults

The most frequent source in practice. Configuration mistakes lead the statistics on outage causes.

LOOKS LIKE

A config change rolled out everywhere at once, a migration run against the wrong environment, a manual step skipped at 3am.

WHAT ACTUALLY HELPS

Reduce the opportunity for error rather than the humans: well-designed abstractions, realistic sandboxes, tests at every level, gradual rollouts, fast rollback, and telemetry that shows what actually happened.

03 / SCALABILITY

"Is it scalable?"
is the wrong question.

Scalability is not a property a system has or lacks. The useful version is a conditional: if load grows along this specific dimension, what are our options?

Which makes the first task descriptive rather than architectural. You have to say what "load" means here, as a number: requests per second, ratio of reads to writes, number of simultaneously active users, hit rate on the cache. The chapter calls these load parameters, and choosing the wrong one is how teams end up optimising something that was never the bottleneck.

TYPICAL LOAD PARAMETERS

requests / second read : write ratio simultaneously active users cache hit rate fan-out per write working set size

The chapter then spends its best pages on a single example, and it is worth following closely because the punchline is not the architecture — it is how the right load parameter was found. The figures come from a November 2012 Twitter talk that Kleppmann cites.

FIGURE C Two ways to build a home timeline
4.6ktweets posted / sec (average)
12ktweets posted / sec (peak)
300ktimeline reads / sec

Writes are not the problem. Reads outnumber them by roughly 65 to 1 — so the design question is where to pay for the join between "who I follow" and "what they posted".

APPROACH 1 · JOIN ON READ

Store every tweet in one collection

WRITEinsert 1 row GLOBAL TWEETS READjoin followees, sort, merge

Writes are trivial. But every one of the 300k reads per second pays for the join, and that is the operation happening 65 times more often.

APPROACH 2 · FAN-OUT ON WRITE

Precompute a mailbox per user

WRITEinsert into every follower TIMELINE CACHES READreturn a ready list

Reads become nearly free. The cost moves to write time, where there is 65× more headroom — which is exactly why Twitter moved in this direction.

THE COST OF APPROACH 2

4.6k tweets/sec ~75 followers on average 345k writes/sec into timeline caches

Still tractable. But this is an average, and the average is hiding the entire problem.

THE DISTRIBUTION IS THE LOAD PARAMETER

median user a handful of followers
average ~75 followers
celebrity 30M+ followers — 30M writes for one tweet

A single celebrity tweet would have to land in tens of millions of mailboxes, and the chapter's stated goal of delivering timelines within about five seconds makes that impossible to do naively. So the answer is a hybrid: fan out on write for the vast majority of users, and merge in the handful of high-follower accounts at read time.

The lesson is the method, not the architecture. The load parameter that decided this design was not requests per second — it was the distribution of followers per user. Averages chose the wrong architecture; the tail chose the right one.

Describing performance once load is defined

With load described, performance becomes answerable in two ways. Batch systems care about throughput — records processed per unit of time. Online systems care about response time — what the user waits through.

And response time is not a number — it is a distribution. The same endpoint, called repeatedly with identical arguments, returns a spread of times: a context switch, a garbage collection pause, a lost packet and retransmission, a cold page. Which is why the chapter is blunt about averages: the mean describes no actual user. Percentiles do.

FIGURE D What percentiles say that an average cannot
p50 median half of users are faster than this
p95 1 request in 20 is slower
p99 1 in 100 — often your heaviest accounts
p999 1 in 1,000 — the tail Amazon chose to watch

fast RESPONSE TIME — shape is illustrative, not measured slow

The argument for caring about the tail is commercial, and the chapter cites it directly: the slowest requests often belong to the customers with the most data — that is, the most valuable ones. Amazon observed that 100 ms of extra response time cost about 1% in sales, and others reported a 1-second slowdown cutting a customer satisfaction metric by 16%. Amazon still drew a line at p999: optimising the 99.99th percentile was judged too expensive for the return.

Why tails get worse in distributed systems

Two mechanisms make this sharper than it first looks. The first is head-of-line blocking: a server handles a limited number of requests in parallel, so a few slow ones delay everything queued behind them — requests that are fast to process still arrive late. This is why the measurement has to be taken on the client side, where the queueing is visible.

The second is tail latency amplification, and it is the one that surprises people. If a single user request fans out into several backend calls, the user waits for the slowest of them. Rare slowness stops being rare as fan-out grows.

FIGURE E Tail latency amplification
1 USER REQUEST
fastfastfast SLOWfast
The user waits for the slowest one. With enough parallel calls, hitting the tail stops being unlikely — it becomes the normal case.

Coping with load

Only now, with load and performance described, does the chapter allow the architectural question — and it deflates it on purpose. Scaling up (a bigger machine) and scaling out (more machines) are not a religious choice; real systems use both, because a handful of strong machines is often simpler and cheaper than a large fleet of small ones.

Distributing stateless services is largely mechanical. Distributing stateful ones is where the difficulty concentrates — which is the reason the next eleven chapters exist at all. And the closing warning is the one worth memorising: there is no generic scaling architecture. A system designed for a hundred thousand small events per second looks nothing like one designed for a handful of enormous ones, even at identical throughput. The architecture follows from the load parameters, and those are specific to your problem.

04 / MAINTAINABILITY

The majority of the cost
comes after it ships.

Most of the money a system consumes is spent on maintenance: fixing it, adapting it to new requirements, operating it, and paying down the debt of decisions made quickly.

Operability

Make routine work boring.

Give the people running the system visibility into its behaviour, predictable and documented defaults, and good support for the tasks they perform every week. Most of a system’s life is operation, not construction.

Simplicity

Remove the complexity that is not the problem’s fault.

Some complexity is essential — it comes from the domain. The rest is accidental: it comes from the implementation. Accidental complexity is the kind you are allowed to delete, and abstraction is the tool for deleting it.

Evolvability

Assume the requirements will move.

They always do: new features, new regulations, new scale, new platforms. A system that is simple and well understood is one you can change cheaply — which is why evolvability is mostly simplicity, seen over time.

The distinction inside simplicity is the one that earns its keep in code review. Essential complexity comes from the problem: tax rules really are that complicated. Accidental complexity comes from how we built it — tangled dependencies, inconsistent naming, special cases papering over a leaky abstraction. Only the second kind is negotiable, and separating them turns "this is too complex" from an opinion into a claim you can check.

05 / VOCABULARY

Four terms the rest
of the book assumes.

Chapter 1 is largely there to install these. They reappear constantly from Chapter 5 onward.

Fault ≠ failure
A fault is one component deviating from spec. A failure is the system as a whole stopping. Tolerance means containing the first so it never becomes the second.
Load parameter
The number that describes what actually grows: requests per second, read/write ratio, fan-out, cache hit rate. Choosing the wrong one hides the bottleneck.
Tail latency
The p99 and p999 response times. Averages describe nobody; the tail describes the users most likely to matter.
Accidental complexity
Complexity that comes from the implementation rather than from the problem itself. It is the kind you are allowed to remove.

06 / IN PRACTICE

What to carry into
the next design review.

The chapter is conceptual, but it converts into four questions that change conversations.

  1. 01

    Name the load parameter before arguing about architecture.

    Requests per second, read/write ratio, fan-out, working set size. If the group cannot agree on which number is growing, the design debate has no ground truth.

  2. 02

    Ask for percentiles, never averages.

    A mean response time describes no real user. Ask for p50, p99 and p999, and ask where they were measured — server-side numbers hide queueing that the client feels.

  3. 03

    Separate the fault from the failure.

    For each dependency: what happens when it is slow, wrong, or gone? A design is fault-tolerant only for the faults you named out loud.

  4. 04

    Treat operations as a first-class requirement.

    Rollback path, deploy strategy, and the signals you will alert on belong in the design, not in a runbook written after the incident.