Part II · Chapter 2 · One Service · lesson 5
From "we have 50,000 users" to requests per second
13 min
Builds on Will it fit on one box?
Part II · Chapter 2 · One Service · lesson 5
13 min
Builds on Will it fit on one box?
The deck says 50,000 users. The architecture review is on Thursday and somebody has to say what that means in requests a second, because every other number on the page depends on it.
Nobody in the room has an access log. You have four steps and about ninety seconds.
What is the peak request rate for 50,000 daily users of an ordinary web product? Commit to a figure.
Predict
Four steps, and the answer is linear in all of them.
One. Daily active users. Not registered users, which can be ten times higher and is the number that appears on decks.
Two. Actions per user per day, where an action is one request that reaches your server rather than one click. This is the guess.
Three. Multiply, then divide by 86,400. Now you have the flat average.
Four. Multiply by the peak-to-average ratio. Six is the honest default for a product used during waking hours in one or two time zones.
Then split the result into reads and writes, because they size different things: peak reads size your serving tier and your cache, peak writes size the primary database and set the floor on replication lag.
Everything after this in a capacity estimate is arithmetic on step two.
QPS from usage
Hypothetical model, not a forecast
Converts daily active users and actions per day into average and peak requests per second, split into reads and writes. Rows sweep the user count from zero up to the chosen figure, so period is users rather than time.
It assumes
It ignores
Hold the fifty thousand users still. Find a combination that takes the peak rate past 500 requests a second, then check what the reads-per-write slider did to that total on the way.
Zero means every action writes. Most consumer products sit between 10 and 100.
| Daily active users | Peak reads per second | Peak writes per second |
|---|---|---|
| 0 | 0/s | 0/s |
| 2500 | 6.61/s | 0.331/s |
| 5000 | 13.23/s | 0.661/s |
| 7500 | 19.84/s | 0.992/s |
| 10000 | 26.46/s | 1.32/s |
| 12500 | 33.07/s | 1.65/s |
| 15000 | 39.68/s | 1.98/s |
| 17500 | 46.3/s | 2.31/s |
| 20000 | 52.91/s | 2.65/s |
| 22500 | 59.52/s | 2.98/s |
| 25000 | 66.14/s | 3.31/s |
| 27500 | 72.75/s | 3.64/s |
| 30000 | 79.37/s | 3.97/s |
| 32500 | 85.98/s | 4.3/s |
| 35000 | 92.59/s | 4.63/s |
| 37500 | 99.21/s | 4.96/s |
| 40000 | 106/s | 5.29/s |
| 42500 | 112/s | 5.62/s |
| 45000 | 119/s | 5.95/s |
| 47500 | 126/s | 6.28/s |
| 50000 | 132/s | 6.61/s |
Not there yet — keep moving the controls.
Two million actions a day. 23 requests a second average, 139 at peak. Of that peak, 132 are reads and 6.6 are writes.
Sit with the write figure for a second, because it is the one that decides your database. The model turns it round for you: at this load the average gap between writes is 907 milliseconds. A service where a write arrives roughly once a second does not need a distributed datastore, a write-optimised engine, or anything else that exists to absorb writes. It needs one Postgres.
That conversion — a rate into the gap between events — is worth doing every time, because "6.6 writes a second" and "a write every nine-tenths of a second" produce visibly different architecture conversations in the same room about the same number.
If the service exists, do not estimate. The number is already recorded in three places and all of them are quicker than the arithmetic.
The load balancer's own metric. RequestCount on an AWS Application Load
Balancer, or the equivalent on any managed load balancer, at one-minute
resolution. Take the maximum one-minute bucket over the last month, divide by
60, and you have the measured peak rather than an assumed one.
The access log. One line per request with a timestamp, which means
awk '{print $4}' access.log | uniq -c | sort -rn | head gets you the busiest
second of the day in one command. This also gives you the real peak-to-average
ratio, which is the input most worth replacing with a measurement.
The database. pg_stat_database has xact_commit; the difference between
two samples a minute apart, divided by 60, is your write rate including the
writes your application does without anybody asking it to.
The arithmetic is for the service that does not exist yet. For one that does, an estimate that disagrees with the log is not a second opinion.
Check
The condition is sharp and it arrives often: when traffic peaks far more sharply than six times the average.
Six assumes human beings spreading their activity over waking hours. These things do not:
A push notification to every user. The resulting burst lands in the first thirty seconds and is a peak-to-average ratio in the hundreds, not the single digits.
A cron job everyone wrote for midnight. Your own traffic, self-inflicted, all at once.
A market open, a ticket release, a flash sale. Load arrives on a clock somebody else controls.
Bots and crawlers, which do not sleep and flatten your daily curve while adding to it.
When any of those is in the picture, the daily total is the wrong input entirely. Size the burst: how many events arrive, over how many seconds, and what happens to the ones you cannot serve inside that window. The answer is usually a queue and a stated drain time rather than a bigger serving tier, which is a different lesson and a different model.