In this lesson, we’ll take a one-line product idea and turn it into requests per second, terabytes, dollars, and a machine count, using arithmetic simple enough to do in your head.
Every such estimate is a chain of assumptions multiplied together. One link in that chain almost always dominates the answer, and naming that link is the real deliverable. By the end you’ll be able to produce an estimate, spot the single assumption it is most sensitive to, and check the result for plausibility even when you have nothing to check it against.
The input is a small set of assumptions: how many people use the thing, how often each does something, how many bytes one of those somethings is, and how long you keep it. The output is one number plus the binding constraint: the resource that runs out first and therefore decides the design.
flowchart LR
A["Assumptions<br/>users · actions/day · bytes · retention"] --> P["Multiply<br/>everything is a product"]
P --> N["One number<br/>1 significant figure"]
N --> C["Binding constraint<br/>the resource that runs out first"]
“1.5 PB per year” on its own is not the deliverable. “1.5 PB per year, so a single machine is out by three orders of magnitude and the design question is really sharding, splitting the data across many machines” is. An estimate that produces a figure and no constraint has done nothing.
The Scaling up chapter describes the architecture these numbers price, if you want that context.
The words and units every line below leans on
These terms and units appear on nearly every line below, so we fix them once here.
People and traffic:
- DAU: daily active users, how many distinct people use the product on a given day.
- QPS: queries per second, the rate at which requests arrive. “Queries” is historical; it counts every request, not only the ones that reach a database.
- Latency is how long one request takes. Throughput is how many requests finish per second. A system can be good at one and bad at the other.
- The average rate is the day’s total divided by the day’s seconds. The peak rate is what arrives in the busiest second. You buy machines for the peak.
- Replication factor is how many complete copies of the data you keep on different machines. Retention is how long you keep it before deleting it.
Units of size. Byte units go up in thousands throughout this chapter, never 1,024.
| Unit | Bytes |
|---|---|
| kilobyte (KB) | 1e3 |
| megabyte (MB) | 1e6 |
| gigabyte (GB) | 1e9 |
| terabyte (TB) | 1e12 |
| petabyte (PB) | 1e15 |
| exabyte (EB) | 1e18 |
Units of time. us is the plain-ASCII spelling of the microsecond symbol.
| Unit | Seconds |
|---|---|
| millisecond (ms) | 1e-3 |
| microsecond (us) | 1e-6 |
| nanosecond (ns) | 1e-9 |
Bits are not bytes. Network links and video streams are rated in bits per second: Mbps is megabits, Gbps is gigabits, Tbps is terabits. One byte is 8 bits, so divide by 8 to convert a bit rate into a byte rate and multiply by 8 to go back. Forgetting that factor of 8 is the most common units slip in this chapter, and the video-streaming estimate below is built on getting it right.
Notation. 1e5 is Python’s spelling of 1 x 10^5 = 100,000. This chapter uses it throughout because it is harder to miscount than a row of zeros. 100 k means 100,000.
Four templates cover almost every estimate
Almost every estimate you will be asked for is one of these templates, or a product of two of them. Name which one you are computing before you compute it, because they answer different questions.
| Template | Formula | What it decides |
|---|---|---|
| QPS | DAU x actions/day / 1e5, then a peak multiplier | Fleet size, whether you need a queue |
| Storage | items/day x bytes/item x retention x replication | One box vs sharding vs tiering |
| Bandwidth | QPS x bytes/response | CDN, egress cost, saturating a network card |
| Memory | working set x bytes/entry | Does the cache fit? |
Three things in that table need unpacking:
-
The
1e5in the QPS row is the 86,400 seconds in a day rounded to one significant figure. Dividing by it turns a per-day count into a per-second rate by moving the decimal point five places, which you can do in your head. Its error is bounded and discussed under Rounding discipline below. -
Shard and tier are the two answers the storage template usually points at. To shard is to split one dataset across several machines that each hold a disjoint slice. To tier is to move rarely read data onto cheaper, slower storage.
-
The working set in the memory row is the portion of the data actually being read at any moment. That template answers “does the cache fit?” and is worked as a coda to the cache estimate below.
Every estimate is a product of assumptions
One idea underlies everything else here: an estimate is not a measurement with error bars, it is a product of guesses, and your job is to know which guess is carrying the answer.
All four templates are products: DAU x actions/day, or items/day x bytes/item x retention x replication. Nothing is added; everything is multiplied. That single fact gives you the sensitivity analysis for free, because in a product, a factor that is 2x too high makes the answer exactly 2x too high, no matter how large or small that factor is in absolute terms. A million-fold number you know precisely damages the answer less than a small number you guessed.
So rank your assumptions not by size but by spread: the ratio between the highest and lowest value you would defend. The widest spread is the assumption that dominates. Take the photo-storage estimate below, with an honest low/high range on each input:
| Assumption | Low → high | Spread |
|---|---|---|
| uploads/user/day | 0.05 → 1.0 | 20x |
| users | 2.5e8 → 1.0e9 | 4x |
| bytes/upload | 1e6 → 4e6 | 4x |
| retention days | 1,825 (stated) | 1x |
| replicas | 3 (a design choice) | 1x |
The uploads-per-user figure is twenty times softer than anything else in the chain, so it is the number the estimate is really about. Retention and replicas contribute nothing to the uncertainty because they are stated or chosen, not guessed.
Multiply the spreads and the end-to-end estimate spans 20 x 4 x 4 = 320x, which sounds like it invalidates the exercise. It does not. The conclusion (that this is an object-storage problem with a small metadata database beside it, and that keeping three full copies of cold data is indefensible) is true at every point across that range. Check the endpoints:
low corner 2.5e8 users x 0.05 uploads x 1e6 B x 1,825 days x 3 copies = 68 PB
high corner 1.0e9 users x 1.0 uploads x 4e6 B x 1,825 days x 3 copies = 22 EB
Object storage with tiering is the answer at both ends. An estimate is robust when its conclusion survives its own uncertainty, not when its number is accurate.
Two habits follow. State the widest assumption together with its range (“I’ll say 0.2 uploads per user per day; that could easily be 1, and if it is, everything below moves by 5x”). And never quote more than one significant figure: a chain with 320x of spread cannot support “1.095 EB”, only “about an exabyte”.
QPS, worked end to end
Every problem in the track opens with the QPS template. The assumptions are the entire input; everything after them is arithmetic.
- a photo feed
- 200 M daily active users
- each user opens the app 5 times a day
- each user posts 0.1 times a day (one post every ten days)
- peak traffic is 2x the average
The DAU figure is the kind of thing a company knows; openings per day is a guess that could be 3 or 10, so openings per day is the dominant assumption.
reads 2e8 users x 5 opens = 1e9 requests/day -> / 1e5 s = 10,000 /s average
writes 2e8 users x 0.1 posts = 2e7 requests/day -> / 1e5 s = 200 /s average
peak 2x reads 10,000 /s x 2 = 20,000 /s
writes 200 /s x 2 = 400 /s
read:write 1e9 / 2e7 = 50:1
Three findings fall out, and the ratio is the one that matters:
- 50:1 read-heavy, so the design question is caching and read fan-out, not write throughput. That single ratio decides more of the architecture than either absolute number.
- 400 writes/s is nothing. One Postgres primary handles it, so stop sizing the write path.
- 20,000 reads/s is a fleet, not a box, so the interesting question is what sits in front of the database.
Before you provision anything, redo the division exactly. The 1e5 shortcut is for deciding; 86,400 is for buying:
1e9 requests / 86,400 s = 11,574 /s average -> x2 = 23,148 /s peak
The peak multiplier is a stated assumption, not a derived constant. It says how much busier the busiest second is than the average. Use 2x for smooth consumer traffic with one diurnal hump (one broad rise and fall across the waking day). Use 3x for event-driven or bursty workloads, where a notification or broadcast concentrates arrivals into minutes.
Some systems skip the multiplier because a tier behind a queue absorbs spikes by design. The video-streaming chapter drops it for its transcoding tier and still provisions at roughly 1.5x the mean. The multiplier is also not a substitute for autoscaling: autoscaling reacts in minutes while spikes arrive in seconds, and stateful tiers (databases, connection holders, warm caches) cannot scale on that timescale at all, so the peak sizes your headroom, not your steady state.
Rounding discipline
You cannot do 500,000,000 x 287 on a whiteboard. Some approximations cost almost nothing, one carries most of the leverage, and in exactly one place rounding is forbidden.
A significant figure is a digit that carries information. Rounding to one means keeping the leading digit and zeroing the rest: 287 becomes 300, 86,400 becomes 100,000. The rule is to round every input to one significant figure. The substitutions to know by reflex:
seconds/day 86,400 -> 1e5 (16% high; note it once, move on)
seconds/month 2.6e6 -> 2.5e6 (two figures kept on purpose: 3e6 would be
16% high, 2.5e6 is only 3.5% low)
1 million/day 10/s (exact 11.6)
1 billion/day 10,000/s (exact 11,574)
1 KB = 1e3 B, 1 MB = 1e6 B, 1 GB = 1e9 B (never 1,024 in an estimate)
1e5 seconds/day is the approximation you will use most. Its error is 1e5 / 86,400 = 1.16, so the divisor you used is 16% larger than the real one, far below the error in your input assumptions.
When to convert back
1e5 is a drill convention, not a reporting convention. Divide by 1e5 while you are thinking. Divide by 86,400 the moment a number becomes a result: a fleet size, a monthly bill, a capacity headroom.
Watch which 16% is which. The 16% is the error on the divisor; the shortfall on the quotient is its reciprocal, 10,000 / 11,574 = 0.864. So 1e5 understates load, and a fleet sized on it is 13.6% short, not 16%. That is fine for “do I need a queue?” and not fine for “how many boxes do I buy?”
The one place not to round
The exception is powers of two, when the answer is a bit width: how many binary digits you allocate to a field in an identifier. Those are not estimates; they are exact capacities, and being 16% off changes whether a design works. 2^32 is 4.3 billion, so a 32-bit counter overflows after 4.3 billion values. 2^41 milliseconds is 69.7 years, so 41 bits of millisecond timestamp last a human lifetime. The ID-generator chapter uses these when it packs a unique identifier into 64 bits.
2^10 ~ 1e3 2^20 ~ 1e6 2^30 ~ 1e9 2^32 = 4.3e9
2^40 ~ 1e12 2^41 ms = 69.7 years 2^64 = 1.8e19
Latency numbers, and what each one forbids
This table is the standing set of hardware timings the chapter uses. What matters is the third column, the design rule each row implies, not the raw numbers. Each row is an assumption about hardware, kept separate from the workload assumptions each estimate states for itself. RTT is round-trip time, one message sent plus its answer coming back.
The numbers descend from Jeff Dean’s 2009 figures. The ones set by physics (the speed of memory, the speed of light in fibre) have not moved since. Two rows, marked ⚠, are routinely misread and are explained below the table.
| Operation | Time | What it forbids |
|---|---|---|
| L1 cache reference | 1 ns | — |
| Branch mispredict | 3 ns | — |
| Main memory reference | 100 ns | 1e7/s per core, so in-memory is ~free at any web scale |
| Compress 1 KB (snappy) | 2 us | Compress before crossing a network, always |
| Send 1 KB over 1 Gbps | 10 us | — |
| SSD random read (4 KB) | 100 us ⚠ | The latency of one read. Not a device ceiling — see below |
| Read 1 MB sequential from memory | 250 us | — |
| Round trip within a datacenter | 500 us | ~2,000 sequential hops/s. A chain of 10 internal calls costs 5 ms before any work |
| Read 1 MB sequential from SSD | 1 ms | |
| Disk seek (spinning disk only) | 10 ms ⚠ | 100/s per spindle. HDD only; an SSD has no seek |
| Read 1 MB sequential from disk | 20 ms | |
| Round trip, US-East ↔ Europe | ~75 ms | The short transatlantic path |
| Packet CA → Netherlands → CA | 150 ms | You cannot serve Europe from California. This forces multi-region |
The last two rows are different routes, not one route and its double. The ~75 ms is a round trip on the short transatlantic path. The 150 ms is a round trip from California, which crosses the continent before it crosses the ocean. Their ratio is about 2x by coincidence of geography.
A B-tree is the balanced, high-fanout index a relational database keeps on disk, and a lookup walks it root to leaf, so its depth is the number of seeks one row costs, which is why 10 ms per seek shows up in query latency (see the database-internals chapter).
The two rows that will burn you
Both ⚠ rows get misread in the same direction: they turn a workable design into an apparently impossible one:
-
100 usis a latency, not a device ceiling. Dividing 1 into it gives 10,000 IOPS (input/output operations per second), but that is a queue-depth-1 number, roughly 50x below a real NVMe drive. NVMe is the modern interface by which solid-state drives attach directly to the processor bus; such a drive does 500 k–1 M random 4 KB IOPS because it services hundreds of requests at once. The per-request latency stays around 100 us while throughput scales with queue depth (how many requests the device works on concurrently). Use100 usfor “how long does one read take?” and never for “how many can this box do?” -
10 msis a seek, and only a spinning hard disk seeks. A seek is the physical movement of a read head to a track on a rotating platter. A solid-state drive has no head and no platter, so quoting 10 ms for SSD-backed storage inflates every I/O argument by two orders of magnitude.
Two conclusions worth memorizing as sentences:
- Memory is 100,000x faster than a disk seek (100 ns against 10 ms), which is why every read-heavy design ends in a cache.
- A cross-continent round trip is 150 ms and the speed of light is not negotiable, so the only fix is to put data closer, which is what a CDN and multi-region are for.
Sequential versus random, and why block size matters
Sequential means reading bytes next to each other on the device. Random means jumping to an unrelated location for each read. Sequential wins, but by how much depends on the block size, the number of bytes one random read fetches. Sequential throughput comes off the table’s rows; random throughput is one block per operation, so it is block size / latency.
| Sequential | Random, 1 KB blocks | Random, 4 KB blocks | |
|---|---|---|---|
| SSD | 1 MB in 1 ms = 1,000 MB/s | 10,000 IOPS x 1 KB = 10 MB/s | 10,000 IOPS x 4 KB = 40 MB/s |
| HDD | 1 MB in 20 ms = 50 MB/s | 100 IOPS x 1 KB = 0.1 MB/s | 100 IOPS x 4 KB = 0.4 MB/s |
| SSD advantage | — | 100x | 25x |
| HDD advantage | — | 500x | 125x |
Quote the block size or the ratio is meaningless: moving from a 1 KB block to a 4 KB page carries four times the bytes per operation, cutting the sequential advantage by 4x on both devices. Either way the direction of the inequality is why log-structured storage engines exist: engines that never update a page in place but append every write to the end of a file and merge later (LSM-trees vs B-trees), since they convert random writes into sequential ones on purpose.
Six worked estimations
Six estimates, each a different shape, each ending in a named constraint, not a number. Between them they exercise all four templates. Every one follows the same three parts: the assumptions, the arithmetic (rounded ruthlessly, with units carried on every line), and the finding: which resource binds, and therefore what the design conversation is actually about. Each also names the assumption whose range is widest.
| Estimation | Template |
|---|---|
| 1 — Twitter-scale tweet storage | storage |
| 2 — Photo service, 5-year storage | storage |
| 3 — Chat: connection memory | memory |
| 4 — Video streaming egress | QPS and bandwidth |
| 5 — A cache’s hit-rate economics | memory (in the “does it fit?” coda) |
| 6 — ID generation rate | QPS |
Estimation 1 — Twitter-scale tweet storage
How many bytes does a text-and-image social product accumulate? “How much storage” is almost never a question about the text.
Assumptions:
- 500 million tweets a day
- 300 bytes of text per tweet
- 1 tweet in 10 carries a 1 MB image
The image fraction dominates: it could be 5% or 30%, a 6x range, where a published tweet count is good to within 2x and the text size moves nothing. Since media turns out to be 99.7% of the total, whatever happens to that fraction happens to the answer.
text 5e8 tweets x 300 B = 1.5e11 B = 150 GB/day
media 5e8 tweets x 0.10 x 1e6 B = 5.0e13 B = 50 TB/day
total 5.015e13 B = 50.2 TB/day
retention 5 yr x 365 = 1,825 days
50.2 TB/day x 1,825 x 3 copies = 2.75e17 B = 275 PB
Finding: the text is 0.3% of the bytes. This is an object-storage problem with a small metadata database bolted on, not a database problem. The media goes to blob storage (a service that keeps whole files under a name, cheap per byte) with a CDN (a content delivery network, a rented fleet of servers worldwide that caches files near users) in front of it. Only the URL lives in the database.
Check by a different route: 50 TB/day x 365 = 18 PB/year, x 5 years = 90 PB, x 3 copies = 270 PB. Landing within 2% of the long form by a different route is what makes the number safe to say.
Estimation 2 — Photo service, 5-year storage
The same storage question at exabyte scale. The useful part is the follow-up about how you store it, not the total.
Assumptions:
- 500 million users
- 0.2 uploads per user per day
- 2 MB per photo after transcoding
- five years of retention
- three replicas
Uploads per user per day dominates, by 20x, as the sensitivity table above showed.
per day 5e8 users x 0.2 uploads x 2e6 B = 2.0e14 B = 200 TB/day
per year 200 TB/day x 365 = 7.3e16 B = 73 PB/year
5 years 73 PB/year x 5 = 3.65e17 B = 365 PB
3 replicas 365 PB x 3 = 1.095e18 B = 1.1 EB
Finding: an exabyte, and the follow-up is the real question. You do not keep 3 full replicas of cold photos.
Erasure coding is the alternative: cut each object into fragments, compute a few extra parity fragments, and spread the set across machines so any sufficiently large subset reconstructs the original. It survives the same number of failures as replication while storing roughly 1.5x the original bytes instead of 3x. The saving is on the multiplied figure, not the raw one:
3 replicas 365 PB x 3 = 1,095 PB
1.5x erasure 365 PB x 1.5 = 547.5 PB
saved 547.5 PB
Tiering, moving rarely read data into a cheaper, slower class, saves more again, though which tier is a real decision. Cold storage costs about a fifth as much per byte but charges a fee on every read-back, so it wins only below 0.4 reads per PB per month (the object-storage chapter derives that threshold). The estimate’s job was to make tiering obviously necessary.
Check by turning the exabyte into a physical object, since nobody has intuition for that unit:
drives 1.095e18 B / 2e13 B per drive = 54,750 drives (~20 TB/drive)
racks 54,750 / 600 per rack = ~91 racks (~600 drives/rack)
Ninety racks is one row of a data hall: plausible for a global photo service, implausible for a startup. That judgement is what the number was computed to support. Carry the conversion constant, not just the unit: “a thousand racks” for 55,000 drives would imply 55 drives per rack, a tenth of what a storage rack holds, and an order-of-magnitude error in a sanity check is worse than no check.
Estimation 3 — Chat: connection memory, and a trap
This estimate deliberately produces a wrong answer, to show what is often the most useful thing an estimate can do: prove that the resource you measured is not the one that sizes the tier.
Assumptions:
- 20 million simultaneously open connections
- about 10 KB of memory each (OS socket buffers, encryption state, per-user data)
The 10 KB is firm to about 2x, and stops mattering once you learn memory is not the constraint. The dominant assumption is connections per box, which plausibly spans 100 k to 500 k, a 5x range that sets the fleet outright.
2e7 connections x 1e4 B = 2e11 B = 200 GB
200 GB / 64 GB per box = 3.1 -> ~3 boxes
That 64 GB is the bottom of the commodity range, not the 128 GB default, so note it; at the default it is 200 / 128 = 1.6, call it 2 boxes.
Finding: three boxes is the wrong answer, and knowing why is the point. Memory is not the binding constraint on a tier that holds connections open. Four other things are, each a real ceiling:
- File descriptors. A file descriptor is the small integer the OS hands out for every open socket; both the process and the kernel cap how many can exist.
- Kernel memory. The kernel spends its own memory and scheduling attention per socket, over and above the application’s 10 KB.
- TLS handshake CPU. Setting up each encrypted connection costs processor time (TLS is transport layer security, the encryption behind HTTPS), and reconnections arrive in floods.
- Blast radius. A box holding 6.7 M live connections (
20 M / 3) drops all of them at once when it dies, and they all reconnect at once, a self-inflicted flood.
Real deployments land at 100 k–500 k connections per box, which is the number that sets this fleet:
20 M / 500 k per box = 40 boxes
20 M / 100 k per box = 200 boxes
The estimate told you which resource is not the constraint, which is often more useful than a fleet size: “memory says three boxes, so memory isn’t what sizes this tier; connection-handling and failure domain are.”
Estimation 4 — Video streaming egress
At video scale the bandwidth bill is not a line item in the business, it is the business. Bitrate is how many bits of video are consumed per second of playback. Egress is bytes leaving your network, the direction cloud and CDN providers charge for.
Assumptions:
- 1 billion views a day
- 5 minutes of average watch time per view
- 3 Mbps average bitrate
Watch time dominates: view counts are usually known, but “5 minutes” could be 2 or 15, and it multiplies the whole answer. Watch the / 8, since bitrate is in bits and everything downstream is in bytes.
bytes/view 5 min x 60 = 300 s; 300 s x 3e6 bits/s = 9e8 bits; / 8 = 110 MB
per day 1e9 views x 1.1e8 B = 1.1e17 B = 110 PB/day
CDN egress 1.1e8 GB x $0.02/GB = $2.2 M/day = $800 M/year
same total via the bandwidth template, QPS x bytes/response:
1e9 views/day / 1e5 s = 10,000 views/s
10,000 /s x 1.1e8 B x 8 bits/B = 8.8 Tbps average
Running it through both templates answers different questions. The per-day route gives the bill. The QPS x bytes/response route gives the pipe, and 8.8 Tbps sustained is 880 fully saturated 10 Gbps network cards before any peak multiplier: the arithmetic proof that the origin cannot serve this and the CDN is not an optimization.
Finding: egress is the business. A 10% bitrate reduction from a better codec (the algorithm that compresses and decompresses video) is worth $80 M a year, which is why video companies employ codec teams. It also reframes the design: the origin serves almost nothing, the CDN serves everything, and cache hit ratio at the CDN edge is the most valuable metric in the system.
Check. 110 PB is 110 million GB, and 110 million times two cents is 2.2 million dollars: one multiplication, and it is where a units slip would hide. The 1.1e8 above is a rounded-down 1.125e8, so every figure is 2.2% low and the unrounded chain gives $821 M/year, a gap well inside the spread on watch time.
Estimation 5 — A cache’s hit-rate economics
A cache is a small, fast store holding recently requested answers. A hit is a request it can answer; a miss falls through to the database. Write the hit rate as h, a fraction between 0 and 1.
Assumptions:
- 100,000 reads per second
- 10 ms for a database read
- 0.5 ms for a cache read
The hit rate itself dominates, which is why the estimate is run at three values of it. The 10 ms is a stated assumption, not the disk-seek row reused: a database read is a buffer-pool miss (the buffer pool being the database’s in-memory cache of disk pages) plus a network hop plus query execution on a loaded primary. On an SSD-backed primary with a warm buffer pool it could be 1 ms, a 10x range that makes it the widest assumption after the hit rate.
Mean latency is the hit path and the miss path weighted by how often each happens:
flowchart LR
R["Read request"] --> C{"In cache?"}
C -->|"hit, fraction h"| H["Cache read<br/>0.5 ms"]
C -->|"miss, fraction 1-h"| D["Database read<br/>10 ms"]
H --> M["Mean latency<br/>h(0.5) + (1-h)(10)"]
D --> M
hit rate 0.80: mean = 0.8(0.5) + 0.2(10) = 2.4 ms ; DB load = 100k x 0.20 = 20 k QPS
hit rate 0.90: mean = 0.9(0.5) + 0.1(10) = 1.45 ms ; DB load = 100k x 0.10 = 10 k QPS
hit rate 0.99: mean = 0.99(0.5)+0.01(10) = 0.6 ms ; DB load = 100k x 0.01 = 1 k QPS
Both columns are straight lines in h: latency is 10 - 9.5h and load is 1e5 - 1e5h. “Linear in the hit rate” and “linear in the miss rate” are the same claim about the same line, so neither is the finding.
Finding: the asymmetry is between the load and the reduction factor. Load is QPS x (1-h), a line that falls to zero. The reduction factor is 1/(1-h), a hyperbola that diverges: at h = 0.80 the cache does 5x, at 0.99 it does 100x, at 0.999 it does 1,000x. As h -> 1 the factor goes to infinity while the thing it multiplies goes to zero, which is why the last fraction of a percent is simultaneously the most valuable and the least material. The clean way to say it is in nines:
h = 0.80 -> miss 0.200 -> 20,000 QPS
h = 0.90 -> miss 0.100 -> 10,000 QPS
h = 0.99 -> miss 0.010 -> 1,000 QPS
h = 0.999 -> miss 0.001 -> 100 QPS
Each additional nine of hit rate divides database load by ten. That is also why a cache stampede, in which a popular key expires and every request for it misses at once (see the Scaling up chapter), is so violent: dropping from 99% to 90% is one nine backwards and 10x the database load instantly.
And does it fit?
The economics above price a hit rate you have not yet shown you can buy. The memory template, working set x bytes/entry, answers that.
Assumptions: Take the product of Estimation 1: 500 M items a day, and a hot window of one day, since almost all reads on a feed are of recent items.
working set 5e8 entries x 1e3 B = 5e11 B = 500 GB (~1 KB each: 300 B text
plus key, pointers, overhead)
usable/box 128 GB x 0.70 = 89.6 GB (70% full for eviction headroom)
boxes 500 / 89.6 = 5.6 -> 6 boxes
Finding: six boxes, so it fits, which means the 99% hit rate above is a real option, not an aspiration. The dominant assumption is the hot window: one day against one week is a 7x swing straight into the box count, where the 1 KB is firm to about 2x. Note the roughly 3x between the 300 bytes an item weighs on disk and the kilobyte it weighs in the cache; keys, pointers and serialization overhead are the term people leave out, and leaving it out is how a cache sized to fit does not. The general shape: if the working set fits, the cache is a hit-rate problem; if it does not, it is a sharding problem, and no amount of eviction tuning converts the second into the first.
Estimation 6 — ID generation rate
The shortest estimate here, and it shows an under-used outcome: proving something is not worth designing. The assumptions come from the Snowflake identifier scheme, in which each machine stamps every identifier with a millisecond timestamp plus a 12-bit counter that resets each millisecond.
- each node mints
2^12= 4,096 identifiers per millisecond - the scheme allows 1,024 nodes
Both are exact by construction, so this estimate has no soft assumption at all.
4,096 /ms x 1,000 ms/s = 4.1 M IDs/s/node
4.1 M/s x 1,024 nodes = 4.2 B IDs/s
Finding: 4.2 billion IDs per second is roughly 42,000x the 100,000/s peak this track’s busiest designs assume, and 4,190x even the 1 million messages a second of the message-queue chapter. The ID generator will not be a bottleneck by four orders of magnitude. The full 64-bit layout is derived in the ID-generator chapter. When an estimate comes out four orders of magnitude clear, the correct output is a sentence, not a design.
The estimation checklist
Everything above compresses into the order you execute it in, plus one skill: deciding whether an answer is plausible when there is nothing to compare it against.
flowchart TD
A["What am I sizing?<br/>QPS · storage · bandwidth · memory"] --> B["State each assumption<br/>DAU, actions/day, bytes/item"]
B --> B2["Give each a plausible low and high<br/>widest ratio owns the answer"]
B2 --> C["Round to 1 significant figure"]
C --> D["Compute per-day, then / 1e5 for per-second"]
D --> E["Apply a stated peak multiplier<br/>2x smooth · 3x bursty · none if queued"]
E --> E2["Reporting a fleet or a bill?<br/>redo the division at 86,400"]
E2 --> F["Apply replication and retention"]
F --> G{"Does the answer<br/>change the design?"}
G -->|no| H["Say so and move on;<br/>do not polish it"]
G -->|yes| I["Name the constraint it just exposed"]
Two boxes carry the whole method and are the ones people skip. Giving each assumption a low and a high turns a list of numbers into a sensitivity analysis for about ten seconds of work, because you are ranking ratios, not computing anything. And “does the answer change the design?” is the only box that asks about you, not about arithmetic: if no, say so and move on; if yes, name the constraint, because that name, not the number, is the output of the whole procedure.
The most common failure is not arithmetic: it is computing a number nobody needed. If the storage estimate would lead to the same architecture at 10 TB and at 100 TB, do not compute it; say “storage is not the constraint, the read QPS is” and spend the time there. The second most common failure is silently inventing an assumption: a number never made explicit makes every figure after it unfalsifiable.
The candidate constraints to walk every time
The last box says “name the constraint”, and a list of four answers from one estimate is not a method. The method is a fixed walk you run every time regardless of which resource the question asked about. Each line is a candidate resource, the arithmetic that sizes it, and the ceiling to compare against:
CPU cycles per request x QPS, against cores
memory working set x bytes/entry, against RAM per box
network bytes QPS x bytes/response, against the network card
disk bytes items/day x bytes x retention x replication, against capacity
disk IOPS random reads/s, against 100/s per spindle or 500 k on NVMe
descriptors/connections concurrent sockets, against the per-box ceiling
blast radius what one machine's death takes with it
Seven lines, each one of the templates already drilled pointed at a different resource. The binding constraint is whichever your arithmetic exhausts first, and it is very often not the one the question named, exactly the shape of Estimation 3. The last two lines are the ones people omit because they are not throughput at all: a descriptor limit is a configured ceiling, and blast radius is a reliability budget that can force a fleet three times larger than any capacity number justifies.
Sanity-checking a number you cannot verify
At some point you will have an answer and no way to look it up. The check has to come from somewhere other than the source of the number. Four methods cover almost every case:
-
Recompute it by a different route. Arithmetic that shares no step with the original. Estimation 1 reached 275 PB through a five-year daily total; the check went 50 TB/day → 18 PB/year → 90 PB → 270 PB. Two routes that agree are very unlikely to be wrong in the same direction.
-
Convert the answer into a physical object. Exabytes and petabytes carry no intuition, so trade them for drives, racks, buildings, dollars, or people. 1.1 EB became about 55,000 drives and about 90 racks, one row of a data hall. $2.2 M a day became $800 M a year, a number you can compare against a company’s revenue. If the object is absurd, the arithmetic or an assumption is wrong. Carry the conversion constant, not just the unit: “racks” only checks anything if you know how many drives are in one.
-
Check against a bound you already believe. A commodity machine has tens of gigabytes of memory; one database primary handles thousands of writes a second; a human types a few characters a second. Any estimate that quietly requires a machine to do a hundred times more than one of those bounds is wrong, and you can say which bound it violated.
-
Check the direction of every ratio before the magnitude. Reads outnumber writes on a feed. Media bytes dwarf text bytes. Storage grows and never shrinks. A peak exceeds an average. A ratio pointing the wrong way is a units slip or an inverted division, and it is far more common than a mis-multiplication.
The failure all four guard against is the units slip: megabytes written where gigabytes were meant, bits where bytes were meant, a per-day figure compared against a per-second capacity. Each produces a confident, well-formatted, thousand-fold-wrong answer that no amount of re-multiplying catches. Carry the units on every line, the way each block above writes 1.1e17 B before it writes 110 PB, and the slip becomes impossible, not merely unlikely.
Numbers worth memorizing cold
Every constant the chapter spends, gathered so you can drill it. Each is a physical fact, an exact conversion, or a stated default. Where it is a default, the row says so, because a default silently taken is how two people estimating the same system reach different fleet sizes.
| Quantity | Value |
|---|---|
| Seconds per day | 86,400 -> 1e5 to reason, 86,400 to provision |
| Seconds per month | 2.6e6 -> 2.5e6 to reason (3e6 would be 16% high) |
| 1 M/day | 10/s via 1e5 · 11.6/s exact |
| 1 B/day | 10,000/s via 1e5 · 11,574/s exact |
| Memory reference | 100 ns |
| SSD random read | 100 us latency. Device throughput is 500 k–1 M IOPS at depth, not 1e4 |
| Datacenter round trip | 500 us |
| Disk seek (HDD only) | 10 ms -> 100/s per spindle. An SSD does not seek |
| Same-continent RTT | ~30–80 ms |
| Transatlantic RTT, US-East ↔ Europe | ~75 ms, the short crossing. It sits inside the same-continent band, so name the route, not the number |
| Cross-continent RTT, California ↔ Europe | 150 ms, a different route from the row above, not that row doubled |
2^32 | 4.3e9 |
2^41 ms | 69.7 years |
| Char (ASCII) | 1 B |
| UUID | 16 B binary, 36 B as text |
| Typical web page | ~2 MB |
| 1080p video bitrate | ~3–5 Mbps. Default to 3 Mbps and note it |
| CDN egress | ~$0.02/GB |
| Commodity box | 64–256 GB RAM, 8–64 cores, 1–10 Gbps card. Default to 128 GB / 16 cores / 10 Gbps |
| Hard drive | ~20 TB per drive. A stated default, moves with the year |
| Rack of drives | ~600 drives fully populated (42U of 4U, 60-bay enclosures). A stated default |
| Cache read, same datacenter | ~0.5 ms, mostly the round trip, not the lookup |
| Database read, indexed row | ~10 ms on a loaded primary; ~1 ms on SSD with a warm buffer pool. A stated assumption, not the 10 ms disk-seek row |
The rows below are workload defaults, not physical facts. They exist because the hardest part of a first estimate is inventing an input you can defend. Each is an order-of-magnitude anchor to be stated and abandoned the moment a real number appears.
| Workload default | Value |
|---|---|
| DAU as a fraction of registered users | 10–30% for a consumer product, default 20% |
| Sessions per user per day (consumer) | 3–10, default 5. The QPS estimate is usually most sensitive to this |
| Creation actions per user per day | 0.05–1, default 0.1 — people read one to two orders of magnitude more than they write, which is where the 50:1 to 100:1 read:write ratio comes from |
| Concurrent connections per box (long-lived sockets) | 100 k–500 k. A stated default, a 5x range that sets a connection tier’s fleet outright |
| B2B tool (sold to companies) | DAU 50–70% of seats, 1–2 long sessions/day, peak multiplier 3x or more (the day’s traffic lands in an 8-hour window in a few time zones) |
Cheat sheet
| Four templates | QPS = DAU x actions / 1e5 · storage = items/day x bytes x days x replicas · bandwidth = QPS x bytes/response · memory = working set x bytes/entry |
| Defaults when you have nothing | DAU 20% of registered · 5 sessions/day · 0.1 creates/user/day · 128 GB/box · 20 TB/drive · 600 drives/rack |
| The key approximation | 86,400 -> 1e5 to reason, 86,400 to provision |
| Peak | a stated assumption: 2x smooth, 3x bursty, none if a queue absorbs it. Sizes headroom, not steady state |
| The three latencies | memory 100 ns · datacenter round trip 500 us · cross-continent 150 ms |
| Cache leverage | DB load QPS x (1-h) falls linearly to zero; the reduction factor 1/(1-h) diverges. Each extra nine divides load by ten |
| Binding constraint | walk CPU · memory · network · disk bytes · disk IOPS · descriptors · blast radius, take whichever runs out first |
| Every estimate is a product | a factor 2x wrong makes the answer 2x wrong. Rank assumptions by high-to-low spread, name the widest |
| Robustness | an estimate is sound when its conclusion survives its own uncertainty, not when its number is accurate |
| Sanity check | redo by a different route · turn it into drives, racks or dollars · test against a bound you believe · check every ratio points the right way |
| The real output | not a number — the name of the binding constraint |
| Biggest mistake | computing something that does not change the design |
Conclusion
An estimate is a product of a handful of stated assumptions, and its value is not the figure it produces but the constraint that figure names. Round every input to one significant figure, keep 1e5 seconds per day for thinking and 86,400 for provisioning, and never round a power of two that fixes a bit width. Rank your assumptions by spread, because the widest one owns the answer, and quote only one significant figure to match. Carry units on every line so a bits-for-bytes or MB-for-GB slip cannot survive, and check any answer you cannot look up by recomputing it a different way or turning it into a physical object. When the number would not change the design, the correct output is one sentence saying so.
One line to remember: the output of an estimate is never the number, it is the name of the resource that runs out first.
Further reading
- Jeff Dean, Latency numbers every programmer should know: the Stanford talk the latency table descends from.
- The widely circulated latency-numbers gist that popularized those figures.
- Colin Scott, Latency numbers every programmer should know, by year: an interactive version showing how each number has moved over time.
- Martin Kleppmann, Designing Data-Intensive Applications: chapters on storage engines and replication expand on the B-tree, LSM-tree, replication, and erasure-coding tradeoffs touched on here.
Next: how consistent hashing works, where these numbers start shaping a real design.