System designLoading progress…

Capacity and headroom

Turn a product requirement into a workload model, measurable objectives and a justified initial capacity estimate.

Why you cannot run servers at 100%

A single instance with a measured service rate of 100 requests per second does not give good latency at 100 requests per second. As the load approaches capacity, requests start to queue and latency bends sharply upward. Below, the same instance is modelled with a simple queueing approximation. Drag utilization up and watch where the 50 ms target is lost.

0501001502000h4h8h12h16h20h23h50 ms targetmean latency (ms) vs load (requests/s, 0 to 98)
one instance, 100 req/s service rate
Mean latency here25.0 ms
Meets 50 ms target?yes

Model: mean latency = (1/rate) / (1 - utilization), with a 10 ms service time. It is a simplified queueing approximation, not a benchmark. Real services have their own curve, which you must measure. The shape is the lesson: latency is flat, then bends sharply.

Latency is flat, then it climbs. At 80% utilization the mean is exactly 50 ms in this model.

That is why a capacity plan uses a usable fraction of the measured capacity, the operating point you are willing to run at. It is a decision about risk, not a measurement.

Sizing, step by step

Divide the planning peak by usable capacity per instance, round up, then add a spare so that losing one instance does not break the objective.

  1. Planning peak from the workload model: 246.9 req/s.
  2. One instance is measured at 100 req/s. At 0.80 usable it contributes 80.0 req/s.
  3. Instances for the peak: ceil(246.9 / 80.0) = 4. Without a spare, lose one and capacity drops to 240.0 req/s, which is below the peak.
  4. With one spare (N+1): 5 instances. After losing one, 320.0 req/s remain, which covers the peak.

A lower usable fraction buys more headroom for variability and costs more instances. The fraction is a decision, and the 100 req/s is an assumed benchmark.

Change the usable fraction and see how many instances the arithmetic asks for.

Predict first. Four instances cover the planning peak exactly as the arithmetic says (at 80% usable). One of them fails during the busiest hour. What happens?

An estimate is not a benchmark

The 100 requests per second came from an assumed benchmark. In real work, you measure the per-instance capacity of your own code, with your own request mix, at the latency you promise, and you re-measure after big changes. Treat the estimate as a starting point to be checked by a load test.

Chapter context

System design starts with what the system must do and the conditions under which it must do it. A component diagram is only useful when its choices can be traced back to access patterns, data volume, latency targets and failure expectations.

Chapter reference: math, worked example & code

This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.

Math and notation

For U daily active users and a requests per user per day:

Ravg=Ua/86400,Rpeak=pRavg,n=⌈Rpeak/(cu)⌉.R_{avg}=Ua/86400,\quad R_{peak}=pR_{avg},\quad n=\lceil R_{peak}/(cu)\rceil.

Here p is a peak multiplier, c measured capacity per instance and u a chosen usable fraction. The multiplier and capacity are workload assumptions, not universal constants.

Worked example

Assume 100,000 daily users, 20 requests each and a peak multiplier of 10. Average demand is 2,000,000/86,400≈23.15 requests/s and estimated peak is 231.48 requests/s. An instance sustaining 100 requests/s with usable fraction 0.6 contributes 60 requests/s, so four instances cover the estimated peak. Five are needed to retain that same usable capacity after losing one instance.

Reference code

The following example uses the Python standard library and does not call a remote service.

from math import ceil,isclose
average=100_000*20/86400
peak=10*average
usable=100*.6
assert isclose(average,23.14814814814815)
assert ceil(peak/usable) == 4
assert (5-1)*usable >= peak
assert (4-1)*usable < peak

Python examples are provided for study and local execution; this page does not execute Python.

Common mistakes

Average traffic is not peak traffic. A headroom assumption is not measured performance. Durability, correctness and availability need separate objectives.