01/14One server

LEARN TO PROGRAM · CHAPTER 14 · ARCHITECTURE

System design at scale

Every big website started as one program on one computer. Then more people came. This chapter grows one system from a single box to three continents, adding each part at the moment the last design breaks, and shows you exactly where it breaks.

Most scenes have a traffic slider. Drag it and the 3D system takes the load: parts warm from blue to amber to red, and requests start dying where it gives out. The numbers come from a small, honest model you can read in the source; the formulas are real queueing results, and the capacities are typical guesses for a simple web app.

Every code block runs in your browser. Edit it, break it, press Run.

01 · ONE SERVER

One server, and why it falls over suddenly

Start with the simplest design that works: one computer runs your program and its database. A user's phone sends a request over the internet, the server does some work, reads or writes the database, and sends a response. For a new product this is the right design: cheap, easy to understand, easy to debug.

Push the slider up. Nothing much happens, and nothing much happens, and then everything happens at once. That is not a quirk of the model; it is how queues work. Requests arrive at random, so sometimes three arrive together and two have to wait. The busier the server, the less slack it has to catch up, and waiting time grows like 1 / (1 − utilisation).

TRAFFIC300 requests/s
101k100k1M
one server50%

Healthy. Busiest part: one server, at 50%.

1 server · p99 ≈ 135 ms

First break ≈ 600/s
one-server.js

From 50% to 90% busy, the p99 goes up five times. From 90% to 99%, ten times more. Try mu = 1200: a server twice as fast pushes the cliff out, but the cliff is still there.

p99 means the 99th percentile: 99 requests in 100 are faster than this, 1 is slower. It is the number engineers watch, because the average hides the unlucky users.

4.6×the p99 of a simple random queue is ln(100) ≈ 4.6 times its average response time, because that time is exponentially distributed. M/M/1 queue

02 · LATENCY NUMBERS

Latency numbers: what is fast, what is far

Before you can design for speed you need a feel for how long things take. The ladder in the 3D is on a log scale: each line is ten times the one below. Reading a value from the CPU's L1 cache takes about half a nanosecond; a packet from California to the Netherlands and back takes about 150 milliseconds. That is three hundred million times longer.

latency.js

If one nanosecond lasted a second, a trip to main memory would take under two minutes, a round trip across a datacentre almost six days, and a packet across the Atlantic and back nearly five years.

Three rules fall out of this table. Memory beats disk, so keep hot data in RAM (that is what a cache is). A network hop inside a datacentre costs about as much as thousands of memory reads, so avoid chatty designs that make many small calls. And distance is physics: light in glass fibre travels at about two thirds of its speed in a vacuum, so nothing you build will get a London user to a New York server and back in under about 55 ms.

speed-of-light.js

The real figures drift (spinning disks gave way to SSDs, networks got faster), so learn the orders of magnitude, not the digits. For what a memory reference actually is, see Memory; for the buildings these round trips happen in, see the Datacentre.

150 msto send a packet California → Netherlands → California, in Jeff Dean's "numbers everyone should know". Dean, LADIS 2009

03 · UP OR OUT

Scale up, or scale out

When one server is too slow you have two moves. Vertical scaling (scaling up) means a bigger machine: more cores, more RAM. It needs no code changes, which is why it is the right first move. But big machines cost more per unit of work, there is a largest machine you can buy, and one machine is still one point of failure.

Horizontal scaling (scaling out) means more machines running the same program. It has no ceiling, and losing one machine is survivable. It has one requirement: the app servers must be stateless. If a user's login session lives in one server's memory, their next request must reach that same server. Move session state into a shared store (a database or a cache) and any server can answer any request.

TRAFFIC1,800 requests/s
101k100k1M
load balancer<1%
app server45%
database53%

Healthy. Busiest part: database, at 53%.

1 4× server · p99 ≈ 133 ms

First break ≈ 3,400/s

Drag the slider and watch the box grow (2×, 4×) and then multiply. Notice what breaks next: around 3,400 requests a second it is not the app servers but the database, which is still one machine. This is the pattern of the whole chapter. You never remove the bottleneck; you move it.

how-many-servers.js

The 70% target and the two spares are the important parts. A fleet at 100% at peak has no room for a slow deploy, a dead machine or a busy minute.

04 · LOAD BALANCER

The load balancer

With several app servers, something has to decide which one gets each request. That is a load balancer: one address the world talks to, which forwards each request to a healthy server behind it. It can work at the transport level (layer 4: it sees TCP connections) or at the HTTP level (layer 7: it can route by URL, add headers and end TLS).

It also does health checks: every few seconds it asks each server "are you OK?", and stops sending traffic to one that doesn't answer. Kill a server below and watch the wires: the balancer routes around it, and the others get hotter.

TRAFFIC2,600 requests/s
101k100k1M
load balancer1%
app servers65%
database76%

Healthy. Busiest part: database, at 76%.

4 servers · p99 ≈ 171 ms

First break ≈ 3,400/s

How it chooses matters. Random is simple but uneven. Round robin takes turns. Least connections asks every server how busy it is, which is costly with many balancers. A beautiful trick sits in between: pick two servers at random and send to the less busy one.

balancing.js

Asking just two servers gets almost all the benefit of asking all hundred. Change N to 1000 and compare.

2random choices are enough: picking the less loaded of two random servers shrinks the worst server's overload exponentially compared with picking one. Mitzenmacher, 2001

The load balancer is itself a single point of failure, so in practice it runs as a pair (or a fleet) sharing an address. To see a rack of real machines behind a balancer, open the Cluster.

05 · CACHES

Caches: CDN, app cache, and invalidation

The fastest request is one that never reaches you. A cache keeps a copy of an answer close to whoever needs it, so the slow path runs once instead of every time. Two caches appear in the 3D:

A CDN (content delivery network) is a set of servers in hundreds of cities. Images, scripts and any page that is the same for everyone are served from the one nearest the user, and never touch your servers. HTTP itself says what may be cached and for how long, with headers like Cache-Control: max-age=3600. An app cache (Redis or Memcached) sits beside the app servers and holds hot database results in RAM.

TRAFFIC15,000 requests/s
101k100k1M
load balancer4%
app servers68%
database67%

Healthy. Busiest part: app servers, at 68%.

11 servers · p99 ≈ 144 ms · 50% served by the CDN

First break ≈ 22,000/s

With half the traffic answered at the edge and 90% of the rest from the cache, the database sees a fraction of the reads, and the first break jumps from about 3,400 to about 22,000 requests a second. Look at what breaks now: writes. Caches only help reads.

cache-aside.js

This pattern is cache-aside: the app checks the cache, and fills it on a miss. renameWrong shows the classic bug: the database changed, the cache didn't.

That bug is the hard part. Invalidation is deciding when a cached copy is no longer true. The usual tools: a TTL (every entry expires, so staleness is bounded), delete on write (the code that changes the database deletes the cache key), and versioned keys (user:1:v7). Watch for the thundering herd: a popular key expires and a thousand requests miss at once and all hit the database. Facebook's answer was to give one request a lease to refill it while the rest wait or take the stale copy.

06 · READ REPLICAS

Replication and read replicas

Replication keeps a copy of the same data on several machines. The common form is leader-follower: one primary takes all writes and streams a log of changes to replicas, which apply them in the same order. Reads can go to any replica. If the primary dies, a replica is promoted.

Add replicas below and look at the first break: it barely moves, from about 22,000 to 27,000. Every replica must apply every write, so replicas multiply read capacity and do nothing for write capacity. The database is still bound by one primary's writes.

TRAFFIC22,000 requests/s
101k100k1M
load balancer6%
app servers69%
database82%

Healthy. Busiest part: database, at 82%.

16 servers · p99 ≈ 149 ms · 50% served by the CDN

First break ≈ 27,000/s

Replication is usually asynchronous: the primary says "saved" before the replicas have the change. That is fast, but it means a replica can be behind. That gap is replication lag, normally milliseconds, sometimes seconds or minutes under load.

replication-lag.js

For two ticks the user who just saved sees the old value. Fixes: read your own recent writes from the primary, or remember the log position of your write and wait until the replica has reached it.

Kleppmann names the guarantees you may want: read-your-writes (you always see your own changes), monotonic reads (you never see time go backwards by hitting a staler replica next), and consistent prefix (you never see an answer before its question). Each one is a promise that has to be engineered, not a default.

07 · SHARDING

Sharding: splitting the data

To scale writes, split the data itself. Sharding (or partitioning) gives each database server a slice of the keys: users 0 to 1 million on shard 1, the next million on shard 2, and so on, or a hash of the key decides. Now each shard takes a quarter of the writes, and each can have its own replicas. In the 3D the sparks take the colour of the shard their key belongs to.

TRAFFIC60,000 requests/s
101k100k1M
load balancer15%
app servers70%
database shards58%

Healthy. Busiest part: app servers, at 70%.

43 servers · p99 ≈ 146 ms · 50% served by the CDN

First break ≈ 100,000/s

The first break jumps to about 100,000 requests a second. The price is real, though. A query that needs data from every shard ("top 10 posts overall") must ask all of them. A transaction across two shards needs a protocol like two-phase commit. And one celebrity key can make one shard a hot spot while the others idle.

The biggest cost comes when you add a shard. With hash(key) % N, changing N moves nearly every key. Consistent hashing places shards and keys on a ring, and a key belongs to the next shard clockwise; adding a shard only takes keys from its neighbours.

resharding.js

Mod N moves 80% of the keys. The ring moves only the keys the new shard takes over; with 100 points per shard its share comes out a little under the fair 20%. Try 400 points per shard.

2007Amazon's Dynamo paper put consistent hashing with "virtual nodes" at the centre of a production store; Cassandra and Riak drew on its design. Dynamo, SOSP 2007

08 · QUEUES

Queues and backpressure

Not every job has to finish before you answer. Sending an email, resizing a photo, updating a search index: put a message on a queue and reply "accepted" at once. Workers take messages off the other end at their own pace. Here the writes go through a queue, and the workers drain it no faster than the database can bear, so reads stay fast even in a write storm.

TRAFFIC80,000 requests/s
101k100k1M
load balancer20%
app servers69%
database78%
write queue83%

Healthy. Busiest part: write queue, at 83%.

58 servers · p99 ≈ 148 ms · 50% served by the CDN

First break ≈ 96,000/s

A queue doesn't add capacity. It adds time: a burst can be soaked up and worked off later. If writes arrive faster than workers drain them for long, the queue grows without end. So a good queue has a limit, and when it is full it pushes back: producers get "503, try later" instead of joining a line that will never move. That is backpressure, and it is far kinder than a server that accepts everything and answers nothing.

bounded-queue.js

The queue absorbs a 20-second burst and drains in about 15 seconds. Set MAX = 100000: nothing is rejected, but at the peak a new job waits over 13 seconds. Pick the limit from how long a job may usefully wait.

Little's law ties it together: items in the system = arrival rate × time each spends there (L = λW). It holds for any stable queue, from a coffee shop to a Kafka topic. Most queues deliver at least once, so a worker can see the same message twice: workers must be idempotent, which is the next act but one.

09 · CONSISTENCY & CAP

Consistency models, CAP and PACELC

Once data lives on several machines, "what does a read return?" has more than one answer. These are consistency models, strongest first:

Linearizable: the system behaves as if there were one copy, and every read sees the latest completed write. Causal: if one write could have caused another, everyone sees them in that order. Read-your-writes and monotonic reads: guarantees for one user (act 6). Eventual: if writes stop, replicas end up the same, some time.

Below, shard 1's primary keeps writing a new value of x, and its replica copies it. Now cut the network between them. A user asks the replica for x. It has two honest options: refuse (it can't be sure it is up to date), or answer with what it has (which may be stale).

TRAFFIC50,000 requests/s
101k100k1M
load balancer13%
app servers69%
database49%
write queue52%

Healthy. Busiest part: app servers, at 69%.

36 servers · p99 ≈ 144 ms · 50% served by the CDN

First break ≈ 96,000/s

That is the CAP theorem, proved by Gilbert and Lynch in 2002: during a network Partition, a replicated system cannot be both Consistent (linearizable) and Available (every working node answers). Stated honestly, it is narrower than its fame:

You can't "choose" to give up P: networks fail whether you like it or not, so the choice is C or A while a partition lasts. When there is no partition, CAP says nothing. Its C means linearizability only, and its A means every node, which few real systems promise. That is why Kleppmann argues for not labelling databases "CP" or "AP" at all.

PACELC (Abadi, 2012) adds the part you live with every day: if Partitioned, choose A or C; Else, choose Latency or Consistency. Waiting for replicas in other buildings to confirm a write makes it consistent and slow; answering from the nearest copy makes it fast and possibly stale.

quorums.js

With N copies, writes to W and reads from R: if R + W > N, every read set overlaps every write set, so a read always reaches at least one copy with the latest write. Dynamo-style stores let you pick N, R and W per request.

10 · RETRIES

Timeouts, retries and idempotency

Networks drop packets and servers restart, so a call can fail for no lasting reason. The cure is simple: set a timeout, and if the call fails, try again. For rare, random failures it works wonders: if 10% of calls fail at random, four tries succeed 99.99% of the time.

Retries are poison when the failure is overload. Set the slider just past the break and compare the three policies. With three immediate retries, every failure comes back as more load, which causes more failures: a system a little past its limit ends up serving nothing that reaches its servers. Rejecting a request isn't free either, so the more it rejects the less real work it gets done. Backoff with a budget can't cure a steady overload (nothing can, except more capacity or less traffic), but it holds the extra load to about 10%, so the system stays up.

TRAFFIC140,000 requests/s
101k100k1M
load balancer35%
app servers109%
database100%
write queue146%

Breaks at the app servers: asked for 109% of what it can do.

64 servers · p99 timing out · 8% of requests fail · 50% served by the CDN · queue grows 2,200/s

First break ≈ 96,000/s
backoff.js

Exponential backoff spreads retries out; jitter (the random part) stops thousands of clients from retrying in lockstep.

The Google SRE book's rules: limit each request to three attempts; keep a retry budget so retries are at most 10% of a client's requests; use randomised exponential backoff; and retry at one layer only, because retries multiply: three layers that each try four times can send 64 attempts to the database for one click. Give each request a deadline and pass it down, so no one works on an answer nobody is waiting for.

retry.py · the same policy in Python
import random, time
 
def call_with_retries(fn, attempts=4, base=0.1, cap=5.0):
    for attempt in range(attempts):
        try:
            return fn()
        except TimeoutError:
            if attempt == attempts - 1:
                raise                          # give up: let the caller decide
            time.sleep(random.uniform(0, min(cap, base * 2 ** attempt)))

Retries have a second trap. When a call times out, the client can't tell whether the request was lost or the reply was. Retrying a lost reply repeats the work: a second charge, a second order. The fix is idempotency: doing it twice has the same effect as once. GET, PUT and DELETE are idempotent by definition in HTTP; POST is not. For POST, the client sends an Idempotency-Key, and the server stores the reply under that key and returns it again on a repeat. Turn the keys off above and look at the ledger.

idempotency-key.js

11 · RATE LIMITING

Rate limiting and load shedding

When demand is more than you can serve, someone is going to be told no. The question is whether you choose who, and how fast. A rate limiter at the front door counts requests per client (or per API key, or overall) and refuses the excess at once with 429 Too Many Requests, often with a Retry-After header saying when to come back.

TRAFFIC160,000 requests/s
101k100k1M
load balancer23%
app servers71%
database89%
write queue95%

Close to the edge: write queue at 95%, and queues grow fast from here.

64 servers · p99 ≈ 160 ms · 22% get a fast 429 · 50% served by the CDN

First break ≈ 96,000/s

Push the slider far past the break. Without the limiter, everything behind the door overloads and everyone gets slow failures. With it, the system serves what it can at full speed and refuses the rest in a millisecond. Shedding load early and cheaply is what keeps an overload from becoming an outage.

The classic algorithm is the token bucket: tokens drip into a bucket at a steady rate, up to a maximum; each request takes one; an empty bucket means 429. It allows short bursts (up to the bucket size) but enforces the average. Others: fixed window (count per minute: simple, but allows double bursts at the edges), sliding window, and the leaky bucket, which smooths output to a constant rate.

token-bucket.js

Change BURST to 1: the bucket becomes a strict rate. Change RATE to 10: nothing is refused.

12 · OBSERVABILITY

Observability: metrics, logs and traces

You can't fix what you can't see, and at scale you can't see anything by looking at one machine. Observability is building the system so it tells you what it is doing. Three kinds of signal, all on the panels above the system:

Metrics are numbers over time, cheap to keep: requests per second, error rate, p99 latency, CPU. The SRE book's four golden signals are latency, traffic, errors and saturation; if you measure only four things, measure those. Logs are a record per event; write them as structured data (JSON), not prose, so a machine can search them. Traces follow one request through every service it touches, as a tree of timed spans, joined by a trace ID passed along with each call. Google's Dapper paper made this practical; OpenTelemetry is today's open standard.

TRAFFIC90,000 requests/s
101k100k1M
load balancer23%
app servers70%
database88%
write queue94%

Close to the edge: write queue at 94%, and queues grow fast from here.

64 servers · p99 ≈ 157 ms · 50% served by the CDN

First break ≈ 96,000/s

Push the load past the break and watch the panels: saturation hits the top first, then the latency bars, then the errors. Saturation is the early warning; alert on it before your users notice.

percentiles.js

The mean (64 ms) describes nobody: most requests take 35 ms and some take over a second. And a page built from 100 backend calls hits a 1-in-100 slow reply 63% of the time.

63%of requests are slow when each fans out to 100 servers that are each slow only 1% of the time: 1 − 0.99¹⁰⁰. Dean and Barroso, The Tail at Scale

Teams turn this into promises: a service level objective such as "99.9% of requests under 300 ms over 30 days". The gap between 100% and the SLO is an error budget you are allowed to spend on risk, like shipping faster.

13 · ESTIMATION

Capacity estimation on the back of an envelope

Before you design, estimate. The goal is the right order of magnitude in five minutes, so you know whether you need one server or a thousand. Write your assumptions down, round boldly, and keep the units.

Say we're building a URL shortener. Assume 100 million new links a month and 100 clicks per link. Numbers worth knowing by heart: a day is 86,400 seconds (about 105), a month about 2.6 million. So:

envelope.js

Change the assumptions and rerun: double the reads per write, or keep links for ten years. The storage grows with them; the traffic per second barely notices.

Read the results the way an architect would. 39 writes a second is nothing; one database primary handles that easily. About 3,900 reads a second (12,000 at peak) is real but cacheable: links are written once and read many times, and a few links get most of the clicks. 3 TB fits on one disk but should be sharded for growth, and 33 GB of hot links fits in the RAM of one cache server. The tower in the 3D grows one slab a month for five years.

14 · A URL SHORTENER

A worked design: a URL shortener, worldwide

Put it all together. The process is the same for any system: requirements, estimates, API, data model, high-level design, then the bottlenecks and failures.

Requirements. Make a short link for a long URL; redirect anyone who opens it, fast; links last five years. Not in scope: editing links, analytics dashboards. Estimates: the last act, about 4,000 reads a second, 40 writes, 3 TB. API: two endpoints.

the API
POST /api/links HTTP/1.1
Host: sho.rt
Content-Type: application/json
Idempotency-Key: 9b1d4c8e-6f0a-4a57-9a43-2f1e7c3d5b20
 
{"url": "https://example.com/a/very/long/path"}
 
HTTP/1.1 201 Created
{"short": "https://sho.rt/8tmGqb"}
 
GET /8tmGqb HTTP/1.1
Host: sho.rt
 
HTTP/1.1 302 Found
Location: https://example.com/a/very/long/path
Cache-Control: private, max-age=90

Notice 302 Found: a 301 Moved Permanently would let browsers cache the redirect forever, which is faster but means you never see the click again and can't change the target. And notice the idempotency key on the POST: a retried "create" returns the same short link instead of making two.

the data model
CREATE TABLE links (
  code        VARCHAR(10) PRIMARY KEY,
  long_url    TEXT        NOT NULL,
  owner_id    BIGINT,
  created_at  TIMESTAMP   NOT NULL DEFAULT now(),
  expires_at  TIMESTAMP
);
 
SELECT long_url FROM links WHERE code = 'xqH3a';

Making the code. Three options. Hash the long URL and take 7 characters: simple, but collisions must be checked. Random codes: also need a collision check. A counter turned into base 62: no collisions, and to avoid one counter becoming a bottleneck each server reserves a block of ids (say 10,000 at a time) from a central store. With 7 characters, base 62 gives 3.5 trillion codes, enough for 6 billion links many times over.

shortener.js

The whole read path is visit(): cache, then database, then a 302. Everything else in this chapter is about making that one function fast and available for a million people at once.

High-level design, as the 3D shows it: the CDN caches popular redirects at the edge (with a short max-age); a rate limiter stops one client from minting millions of links; stateless app servers behind a load balancer; a cache for hot codes; the links table sharded by code, each shard with a replica; a queue to update click counters without slowing redirects. Then the step most designs skip: three regions, so a user in Sydney isn't paying 150 ms per round trip to Europe, and so the service survives losing a whole datacentre. Reads are local; new links are written in one region and replicated, and because a code never changes once created, a few hundred milliseconds of lag is harmless. That is PACELC in practice: we chose latency, because this data allows it.

Where would it break next? Drag the slider. Then ask the questions that matter in a real review: what if one link goes viral (a hot key: it is served from the CDN and every cache, so fine)? What if a region goes dark (DNS sends its users to the next nearest)? What if someone uses it to spread malware links (a separate scanning queue and a block list)?

TRAFFIC250,000 requests/s
101k100k1M
load balancer21%
app servers69%
database81%
write queue87%

Close to the edge: write queue at 87%, and queues grow fast from here.

60 servers · p99 ≈ 100 ms · 50% served by the CDN

First break ≈ 290,000/s

PRACTICE

Now you do it

Type the code yourself rather than pasting it: producing an answer builds memory that reading does not. Use the hints one at a time.

1

How many servers?

Peak traffic is 18,000 requests per second. One server handles 1,200 per second in a load test. Keep each server at most 75% busy and run two spares. Set servers to the number you run.

exercise-1.js
2

Cache-aside with a TTL

Finish get(key): return a fresh cached value if there is one, otherwise read the database, cache the value for 30 time units, and return it. The program should make exactly three database reads.

exercise-2.js
3

A token bucket

Write allow(now) for a limiter that refills 2 tokens a second and holds at most 4. Six requests at time 0 should give four true then two false; at time 1 there is room for two more.

exercise-3.js
4

Exponential backoff, capped

Write ceiling(attempt): the longest wait before retry number attempt, doubling from 50 ms and never more than 2,000 ms.

exercise-4.js
5

Make a transfer idempotent

Clients retry transfer() when a reply is lost. Change it so the same requestId never moves money twice, and a retry gets back the very same reply object.

exercise-5.js
6

Decode a short code

Write decode(s), the inverse of encode, so a URL shortener can turn a code back into a row id. The check round-trips six numbers up to 627 − 1.

exercise-6.js

RECALL

Check yourself, from memory

Answer each one before you open it. Then come back tomorrow, and again next week, and answer them cold: retrieval spaced over days is the most reliable study method we know of. Why that works.

Why does latency shoot up near 100% utilisation, rather than rising in a straight line?

Read replicas: what do they scale, and what don't they?

State CAP precisely. Which choice does it actually force?

Why can retries turn a small overload into an outage?

What makes an operation idempotent, and why do retries need it?

Name the four golden signals, and say why you'd watch p99 rather than the mean.

Back of the envelope: about how many seconds in a day and in a month?

GO FURTHER

Resources

  • BOOK · FREESite Reliability EngineeringBeyer, Jones, Petoff and Murphy (eds.) · GoogleHow Google runs production, free online. Read "Monitoring Distributed Systems", "Handling Overload" and "Addressing Cascading Failures" first.
  • BOOK · PAIDDesigning Data-Intensive ApplicationsMartin Kleppmann · O'ReillyThe book on replication, partitioning, transactions and consistency. This chapter is a map of it; the book is the territory.
  • VIDEO · FREEDistributed Systems lecture seriesMartin Kleppmann · University of CambridgeEight hours of clear lectures: time, replication, quorums, consensus. Lecture notes are linked from the videos.
  • COURSE · FREEMIT 6.5840: Distributed SystemsMITRead the classic papers (MapReduce, Raft, Spanner) and build a fault-tolerant key-value store in Go. The labs are the best practice there is.
  • PRACTICE · FREEThe System Design PrimerDonne MartinA big, well-organised index of the topics in this chapter, with worked interview-style designs to practise on.
  • ARTICLE · FREEAmazon Builders' LibraryAmazonSenior engineers on timeouts, retries, jitter, load shedding and caching, from systems at enormous scale.
  • DOCS · FREEConsistency modelsJepsen (Kyle Kingsbury)A clickable map of consistency models, from linearizable down to read-your-writes, each with a plain definition.
  • TOOL · FREELatency numbers every programmer should knowColin ScottJeff Dean's table, re-estimated year by year with a slider.

ON THIS SITE

See it running

SOURCES

Where the facts come from

  1. Kleppmann, Designing Data-Intensive Applications (O'Reilly) Replication and replication lag, partitioning, consistency models and the critique of CAP.
  2. Google SRE book: Monitoring Distributed Systems The four golden signals: latency, traffic, errors, saturation.
  3. Google SRE book: Handling Overload Per-request limit of three attempts; per-client retry budget of 10%; retry growth "just below 3X" vs 1.1x.
  4. Google SRE book: Addressing Cascading Failures Randomised exponential backoff; retries at three layers giving 64 (4³) attempts; deadline propagation.
  5. Google SRE book: Service Level Objectives Why percentiles, not means.
  6. Dean, Designs, Lessons and Advice from Building Large Distributed Systems (LADIS 2009) The original "numbers everyone should know" table.
  7. Colin Scott: interactive latency numbers SSD random read about 16 µs (2020 estimate).
  8. Dean and Barroso, The Tail at Scale (CACM, 2013) Fan-out to 100 servers with a 1-in-100 slow reply: 63% of requests are slow.
  9. Little, A Proof for the Queuing Formula L = λW (1961)
  10. M/M/1 queue: response time distribution Mean time in system 1/(μ − λ); the distribution is exponential.
  11. Mitzenmacher, The Power of Two Choices in Randomized Load Balancing (2001)
  12. Karger et al., Consistent Hashing and Random Trees (STOC 1997)
  13. DeCandia et al., Dynamo: Amazon's Highly Available Key-value Store (SOSP 2007) Consistent hashing with virtual nodes; sloppy quorums with N, R, W.
  14. Nishtala et al., Scaling Memcache at Facebook (NSDI 2013) Delete-on-write invalidation, leases against stale sets and thundering herds.
  15. RFC 9111: HTTP Caching
  16. Martin Fowler: TwoHardThings Phil Karlton's line about cache invalidation and naming things.
  17. Gilbert and Lynch, Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services (2002) The proof of CAP, with its precise definitions.
  18. Brewer, CAP Twelve Years Later: How the "Rules" Have Changed (2012)
  19. Abadi, Consistency Tradeoffs in Modern Distributed Database System Design (IEEE Computer, 2012) PACELC.
  20. Kleppmann, Please stop calling databases CP or AP (2015)
  21. Herlihy and Wing, Linearizability: A Correctness Condition for Concurrent Objects (1990)
  22. Brooker, Exponential Backoff And Jitter (AWS Architecture Blog)
  23. RFC 9110: HTTP Semantics, idempotent methods
  24. IETF draft: The Idempotency-Key HTTP Header Field
  25. Stripe API: Idempotent requests
  26. RFC 6585: 429 Too Many Requests
  27. Sigelman et al., Dapper, a Large-Scale Distributed Systems Tracing Infrastructure (Google, 2010)
  28. OpenTelemetry: signals (traces, metrics, logs)