System designCore11 min

Throughput, utilization & queueing

How many requests a system can finish per second, why a server that is 90% busy answers ten times slower than the work itself takes, and why every healthy system keeps spare capacity on purpose.

1 · The idea

Throughput is a different kind of fast

Latency told you how long one request takes. Throughput is how many requests a system finishes per unit of time: 1,000 orders per second, 50 MB per second. A highway makes the difference plain: the speed limit is latency, the number of lanes is throughput. Adding lanes gets no single car home sooner; raising the speed limit does not let more cars through a toll booth. They are different kinds of fast, and a design can have one without the other.

Load balancer50,000/sAPI servers5,000/sOrders database1,000/severy orderLoad balancer50,000/sAPI servers5,000/sOrders database1,000/severy order

An order pipeline. Each stage has a capacity; the whole finishes at most 1,000 orders/s, the capacity of its slowest stage. Doubling the API servers changes nothing.

The slowest stage is the bottleneck, and a system is exactly as fast as its bottleneck. This is the theory of constraints: a system's throughput is limited by its constraint, and improving anything else is wasted effort until the constraint moves. So capacity work is always the same two questions: which stage is the bottleneck now, and what becomes the bottleneck once you fix it? What makes a stage hard to widen is the subject of Scalability.

2 · Little's law

Little's law: in flight = arrival rate × time inside

A café gets 2 customers a minute and each stays 5 minutes. How many people are inside? 2 × 5 = 10. That is the whole of Little's law, and it works for requests exactly as it works for customers:

in flight = arrival rate × time inside

The orders database takes 800 orders a second and each spends 25 ms inside: 800 × 0.025 = 20 orders in flight at any moment. You did not have to look. The law is about averages, and it asks for one thing only: the system must be stable, meaning what comes in eventually goes out. Bursty or steady traffic, one server or fifty, any order of service: it still holds. Any two of the three numbers give you the third, which answers four everyday questions:

The question

What Little's law says

How big must the connection pool be?

2,000 requests/s, each holding a connection for 25 ms: 2,000 × 0.025 = 50 in use. A pool of 40 will queue.

What if a dependency slows down?

100 messages/s at 0.1 s each needs 10 threads; at 10 s each, 1,000 threads for the same traffic.

How much can one server take?

200 requests at 0.1 s each: at most 200 ÷ 0.1 = 2,000/s. Hold more or finish sooner.

Traffic is flat, in-flight doubled. Why?

Time inside doubled. Something downstream got slow.

That is why it is the first sanity check in any capacity discussion. In textbooks you will see it written L = λ × W.

3 · The curve

A server that is 90% busy answers ten times slower

Utilization is the share of time a server is busy. Picture one worker that needs 10 ms per order. It can do 100 orders a second at most, so at 80 orders a second it is busy 80% of the time and idle the other 20%.

If it is idle a fifth of the time, why would any order wait? Because orders do not arrive evenly spaced. They come in clumps. An order that lands while the worker is busy has to wait, and the idle moment a second earlier cannot be saved up for it. The busier the worker, the fewer idle gaps are left to soak up the next clump. A supermarket cashier who is busy nine minutes in ten always has a line.

For one server and work that arrives at random, the average comes out as a simple formula: response time = work time ÷ (1 − utilization). With 10 ms of work per order:

How busy

Response time

Of which is waiting

50%

10 ÷ 0.5 = 20 ms

10 ms

80%

10 ÷ 0.2 = 50 ms

40 ms

90%

10 ÷ 0.1 = 100 ms

90 ms

95%

10 ÷ 0.05 = 200 ms

190 ms

  • 1 server
  • 4 servers, one queue
05010015020020406080Utilization (%)Response time (ms)the knee

Upper line: one server, response time = 10 ms ÷ (1 − utilization). Lower line: four servers sharing one queue, each just as busy. The bend is still there, but it comes later.

Look at the right-hand side. Going from 80% to 90% busy is only one-eighth more traffic, and the response time doubles. From 90% to 95% it doubles again. That bend is the knee. The common mistake is to think a server is fine until it reaches 100%. It is not: at 90% every order already takes ten times longer than the work itself.

The formula is a simplified model (textbooks call it M/M/1: one server, random arrivals, random job sizes). Real systems differ in the details, but every load test shows the same bend. One thing does move it: several servers sharing one queue absorb clumps far better than a single server. The lower line is four servers, each as busy as the single one: at 90% they answer in about 30 ms, not 100. Bigger pools can safely run hotter.

4 · The catch

Variability makes every wait longer

The simple formula already assumes random arrivals. Real traffic is often worse: cron jobs fire on the minute, retries arrive in waves, and a 2-second report export sits in the same queue as 10 ms reads.

Kingman's approximation says what that costs. In words: wait ≈ (busy ÷ idle) × variability × work time.

  • Busy ÷ idle is the knee from the last section: 80% busy gives 0.8 ÷ 0.2 = 4.
  • Variability measures how uneven the arrivals and the job sizes are: 1 for fully random traffic, less for steady traffic, more for bursty.

The two multiply, so uneven traffic makes a busy server much worse:

Traffic at 80% busy, 10 ms of work

Variability

Wait in queue

Response time

Steady arrivals, even jobs

0.5

20 ms

30 ms

Random arrivals, random jobs (M/M/1)

1

40 ms

50 ms

Bursty arrivals

2

80 ms

90 ms

Very uneven jobs

5

200 ms

210 ms

Unevenness is easy to underestimate. One 2 s export in every hundred 10 ms reads pushes the variability to about 22, against 1 for ordinary random traffic: the reads behind the export wait for it, however idle the server is on average. So the fixes are about evenness as much as capacity:

  • Separate slow work. Give it its own pool or queue so it cannot sit in front of fast work (Concurrency covers pools).
  • Smooth arrivals. Add jitter to scheduled jobs and retries (see Timeouts, retries, backoff & jitter).
  • Cap job size. Paginate, for instance, so no request is a hundred times bigger than its neighbors.

Precisely, variability is the average of two squared ratios, standard deviation ÷ mean: one for the gaps between arrivals, one for the job sizes.

5 · The trap

An unbounded queue turns overload into latency, then into a crash

What happens when more arrives than the system can finish? The formula has no answer, and neither does the queue: it grows without limit. Say 1,200 orders/s arrive at the 1,000/s database:

  • The backlog grows by 200 every second.
  • After one minute 12,000 orders are waiting.
  • The newest one waits 12 s for its turn.

Nothing is broken, no error is logged, and every user is waiting. And working off the backlog takes far longer than building it did.

Then it gets worse:

  • Memory runs out. Each waiting request holds memory, so the process eventually crashes.
  • Work is wasted. A request that waited past its client's timeout is still served, and nobody is listening for the answer.
  • Retries pile on. The client that gave up tries again, adding load to a system that already cannot keep up.

This is why you watch goodput, the requests answered correctly and soon enough to be useful, and not just throughput: under an unbounded queue goodput falls to zero while throughput looks fine.

Clients1,200 orders/sBounded queueat most 100 waitingOrders database1,000/s1,200/s1,000/s200/s rejected fastClients1,200 orders/sBounded queueat most 100 waitingOrders database1,000/s1,200/s1,000/s200/s rejected fast

The same overload with a bounded queue: 1,000 orders/s are served with at most 100 ÷ 1,000 = 0.1 s of waiting, and 200/s get an immediate 503 with a Retry-After. Goodput stays at 1,000/s instead of falling to zero.

Three moves replace the unbounded queue:

  • Bound it, by length and by age. Throw out a request that has sat too long: a fresh one has a better chance of still being useful. A rule of thumb for steady traffic is a queue no longer than about half the thread pool.
  • Shed what does not fit. Reject fast, with a 429 or 503, so the requests you do accept stay fast.
  • Push back. Backpressure is the overloaded stage telling its producers to slow down, instead of failing or dropping work silently.

Backpressure is a choice, not a law. A load balancer can reject excess connections rather than queue them. An order pipeline may choose the opposite: accept every order even during a backlog, and sort out priorities behind the scenes.

6 · In practice

You run below the knee on purpose

Everything above points the same way: a resource that is nearly full has no room for a burst, and the queue that forms is the failure. Headroom is capacity you buy and deliberately do not use. Two calculations set how much. The first is the curve: at 60% busy a single server takes 2.5 times the work time, at 90% ten times. The second is what happens when a machine dies: its share of the traffic lands on the survivors.

Peak utilization, 4 servers

After losing one

Response time per server (10 ms of work, own queue)

50%

67%

30 ms

60%

80%

50 ms

70%

93%

150 ms

75%

100%

The queue never drains

Losing one of four servers multiplies each survivor's load by 4 ÷ 3:

  • Plan the peak at 75% and one failure puts you at 100%, the top of the curve.
  • Plan it at 60% and the same failure lands at 80%: on the knee, but alive.

Bigger pools can run hotter. They absorb bursts better (sixteen servers sharing a queue at 90% answer in about 14 ms, against 100 ms for one) and lose a smaller share when one dies. So there is no universal number: the right peak utilization comes from your pool size, how bursty your traffic is, and how fast you can add capacity, which is what Scalability is about.

Check yourself

5 questions

1. A request needs 10 ms of work and the server is 50% busy. In the M/M/1 model, what is the mean response time?
2. A database goes from 80% to 90% utilization, 12.5% more traffic. In the M/M/1 model, what happens to response time?
3. A service handles 500 requests/s and each takes 200 ms. How many are in flight on average?
4. The API tier can do 5,000 orders/s and the database behind it 1,000/s. You double the API servers. What is the pipeline's throughput?
5. Why reject requests when a queue is full instead of letting it grow?

Takeaways

Remember this

  • Throughput is requests per second and a system has exactly the throughput of its bottleneck; latency and throughput are different kinds of fast.
  • Little's law: in flight = arrival rate × time inside. It holds for any stable system, whatever the distributions.
  • In the single-server model response time is work time ÷ (1 − utilization): 2× the work at 50% busy, 5× at 80%, 10× at 90%. Variability multiplies the wait; bigger pools shift the knee right.
  • An unbounded queue hides overload as latency, then wastes work on timed-out requests, then crashes. Bound it, shed what does not fit, and push back on producers.
  • Run with headroom: choose peak utilization from the curve and from what one lost machine does to the survivors.