01 · 5/6

Capacity Planning Without a Crystal Ball

Latency does not degrade gently as load rises, it collapses. Little's Law, why 80% utilization is the wall, and how much headroom to actually keep.

6 min readAug 22, 20261 code block2 figures

Every capacity incident sounds the same in the retrospective. Traffic went up 20%, and latency went up 900%. Nobody deployed anything. The graphs look like a cliff, and the obvious question is why the system did not simply get 20% slower.

The answer is queueing, and it is the one piece of theory in this module worth knowing precisely, because it explains the shape of nearly every performance incident you will ever attend.

Little's Law, and the one formula you need

For any stable system:

code
L = λ × W
 
L = concurrent requests in the system
λ = arrival rate (requests per second)
W = time each request spends in the system (seconds)

400 requests per second, each taking 250ms, means 100 requests are in flight at any moment, so you need 100 units of concurrency: threads, connections, workers, whatever your runtime calls them.

Little's Law drawn as a flow: 400 requests per second arriving, 100 requests in flight inside the service at 250 milliseconds each, 400 per second completing, with three readings underneath for sizing threads, a connection pool, and queue depth.

One formula, three questions. The unit changes; the arithmetic does not.

The connection-pool reading is the one that surprises people: 200 QPS with a 20ms query is four concurrent queries, so the right pool size is far smaller than teams guess, and a pool of 100 mostly buys you a queue inside the database. The queue-depth reading is the one that matters at 3am: if arrivals exceed the drain rate, W grows without bound, there is no steady state, and the only question left is where the backlog piles up.

Why latency collapses instead of degrading

Response time is service time plus queue time. Service time is roughly constant. Queue time depends on utilization, and it does not grow linearly.

For a simple queue, the waiting time scales with 1 / (1 - utilization). That factor is 1.25 at 20% utilization, 2 at 50%, 5 at 80%, 10 at 90%, and 100 at 99%.

A latency versus utilization curve that stays nearly flat from zero to sixty percent utilization, bends upward at seventy, and rises almost vertically past eighty-five percent, with a marked knee at eighty percent utilization.

The same service, at rising utilization. Nothing about the code changes; the queue in front of it does all the damage.

Two consequences follow, and they are the whole reason capacity planning exists.

The knee is around 70 to 80%. Below it, extra load is nearly free. Above it, small load increases produce large latency increases, so planning to run at 90% is planning to live on the steep part, where a 5% traffic bump is an incident.

Variability moves the knee left. That formula assumes smooth arrivals and identical work. Real traffic is bursty and a cache hit is not a cache miss, so the more variable the workload, the more headroom the same latency target needs.

This is also why backpressure matters more than capacity. Once you are past the knee, the only options are to shed work or to let the queue grow, and an unbounded queue converts a latency problem into an outage.

How much headroom, and why

"Keep 30% headroom" is a habit, not an answer. The real number comes from asking what you must survive without degrading.

What does headroom have to absorb?

  • Losing one instance of N1/N of capacity3 instances means 33% headroom, 10 instances means 10%
  • Losing an availability zone1/(number of zones)three zones means a third of capacity has to be spare
  • The daily peakpeak / averagesized for peak, so the average looks wastefully idle
  • A traffic spike or a retry storm2x, or shed loadpast a point, shedding is cheaper than provisioning

These stack, and the stacking is the part that gets missed. Surviving the loss of one zone during your daily peak means the peak-time load, after losing a third of the fleet, must still land below the knee. That is the design constraint, and it is considerably stricter than the average-utilization number on the dashboard.

Where autoscaling fits: it handles gradual, predictable changes well (the daily cycle) and abrupt ones badly, because instances take minutes to become useful and the spike takes seconds. So autoscaling manages your cost, and static headroom plus load shedding manages your incidents. Treating autoscaling as a spike defense is a common and expensive mistake.

Find the actual bottleneck first

Capacity work aimed at the wrong resource is a rounding error. Brendan Gregg's USE method locates it fastest: per resource, check utilization, saturation, and errors.

Saturation is the one people skip and usually where the answer is. CPU at 60% looks fine until the run queue is 12 deep. Same for a pool that is not "full" but has requests waiting on checkout, and a disk whose throughput is fine while its queue depth is not.

Check in this order, because each one masks the ones after it: thread and connection pools, then the database (usually the real ceiling: find the top query by total time), then CPU, then network and disk. In most request-serving systems the ceiling is a pool limit or the database, not raw compute.

The plan, in five lines

A capacity plan does not need to be a document. It needs these numbers written down somewhere a person will find them during an incident.

  1. Current peak QPS, and the peak-to-average ratio.
  2. Measured capacity per instance at your latency target, from a load test, not from arithmetic.
  3. Required headroom, derived from your failure domain and your peak.
  4. The trigger for adding capacity: a utilization or latency threshold, not a feeling.
  5. What gets shed first when you exceed all of the above, decided in advance.

Line 5 is the one that separates a plan from a wish. Every system eventually gets more load than it has capacity for, and having already decided which work degrades (search suggestions, recommendation widgets, non-critical writes) is the difference between a slow page and an outage.

Next, the capstone runs every number in this module end to end on one brief: users to QPS, QPS to instances, an SLO, and the headroom to defend it.

Key takeaways

  • Little's Law (L = λ × W) sizes pools, workers, and instance counts, and it is the same formula every time.
  • Queue time scales as 1/(1 - utilization): 2x at 50% utilization, 5x at 80%, 10x at 90%. That is why latency collapses rather than degrading.
  • The knee is at roughly 70 to 80% utilization, and workload variability moves it left. Plan to sit below it.
  • Derive headroom from what you must survive (an instance, a zone, the daily peak) and remember those constraints stack.
  • Autoscaling controls cost, not spikes. Static headroom plus load shedding controls incidents.
  • Find the real bottleneck with utilization, saturation, and errors. Saturation is usually where the answer is, and the database is usually the ceiling.
  • Decide in advance what gets shed first. That decision is the plan.

Checkpoint · lesson 5 of 6

You can now:

  • Use Little's Law to size a pool, a worker fleet, or an instance count
  • Explain why latency collapses near saturation instead of degrading linearly
  • Choose a headroom target from your failure domain, not from a habit