Capacity Planning and Load Testing

Topics Covered

What Capacity Planning Actually Asks

Headroom is not the same as free resources

The three questions that are actually different

Demand is not one number

The unit of capacity is work, not requests

What autoscaling changes, and what it does not

The dependency that does not scale with you

Capacity planning sounds like an exercise in arithmetic: measure what you use, project it forward, buy that much. Teams that treat it that way are reliably surprised, because the question that matters is not how much you are using. It is how much more you could absorb before the system stops behaving, and that number is almost never proportional to the resources you have left.

The useful framing is a single question asked about a specific moment: if traffic doubled right now, what would break first? Everything else in this lesson exists to make that question answerable with evidence rather than opinion.

Headroom is not the same as free resources

A fleet at forty percent CPU looks like it has sixty percent of headroom left. It usually does not. Response time does not stay flat as utilisation rises and then jump at one hundred percent; it starts curving upward well before that, and past a certain point small increases in load produce large increases in latency. Where that point sits depends on how variable the work is, not on the hardware.

This is the single most important thing to internalise about capacity, and it is why experienced teams talk about the knee rather than about percentages. A service whose requests all take the same amount of time can run comfortably at high utilisation. A service where most requests are fast and a few are enormous will start queueing at a much lower average utilisation, because the long requests block the short ones behind them.

The practical consequence is that your safe operating point is a property you have to measure, not a number you can read off a convention. Sixty percent is a reasonable starting guess for a typical web service and it is only a guess.

Each point is one minute of production traffic. The curve is flat and then it is not, and the distance between those two states is smaller than anyone expects.

The three questions that are actually different

People say capacity planning when they mean one of three distinct things, and conflating them produces answers to questions nobody asked.

The first is how much do we need next quarter. This is a forecasting question, driven by product plans and growth rates, and the answer is a budget. It tolerates being wrong by twenty percent because it has months of lead time.

The second is how much can we take right now. This is a measurement question about the current system, and the answer is a number of requests per second with a stated latency objective. It has to be accurate, because the whole point of knowing it is to decide whether tonight's marketing email is safe.

The third is what happens when something fails. This is a structural question: when one of three zones goes away, the remaining two carry everything, and a fleet sized for comfortable steady state is a fleet that falls over in the failure case. Most capacity incidents are failures of this third question rather than of growth forecasting.

Demand is not one number

Aggregate requests per second is the metric everyone reports and it hides most of what matters. A system handling ten thousand requests a second where every request is a cached read is a different system from one handling ten thousand requests where five percent trigger a report generation.

Useful demand modelling breaks traffic into classes that consume resources differently, then tracks each class separately. For most products this is a small number of categories: cheap reads, expensive reads, writes, and whatever background or batch work shares the same capacity. The ratios between them are what change during an incident, and they are what a load test has to reproduce if it is going to mean anything.

The other dimension is shape. Daily peaks, weekly peaks, and the one annual event that is four times anything else all matter, and they matter differently: you size steady state for the daily peak, you plan the scaling behaviour for the weekly shape, and you plan the annual event as a project with its own load test.

Capacity was added twice and demand grew continuously. The line that matters is the lowest one, and it moved for reasons nobody logged.
Key Insight

The unit of capacity is work, not requests

A request is a convenient thing to count and a poor thing to plan against, because two requests to the same endpoint can differ in cost by three orders of magnitude. A search over a thousand item catalogue and a search over a two million item catalogue are the same request shape and completely different pieces of work.

This matters most when the system is growing along a dimension that is not traffic. A service whose request rate is flat but whose largest account has tripled in size is getting steadily closer to its limit, and nothing in a requests per second chart will show it. Data growth, result set sizes, average payload, fan out width and the depth of the most expensive query are all capacity drivers, and all of them move without the traffic graph moving at all.

The practical fix is to pick a unit that tracks cost rather than count. For a database that might be rows examined; for a rendering service, pixels or pages; for a search tier, index size times query rate. The unit does not have to be perfect, it has to correlate with the resource that actually runs out. Once it exists, the capacity statement becomes stable across changes in the traffic mix, which is exactly what makes it still true in six months.

The same choice determines whether your dashboards are useful during an incident. A fleet serving its normal request rate at twice its normal cost per request looks healthy on every graph the team has, right up until the moment it does not.

What autoscaling changes, and what it does not

Elastic infrastructure changes the shape of the problem rather than removing it. You still have to know your safe operating point, because that is what the scaling policy targets. You still have to know your demand classes, because a policy that scales on CPU will not react to a queue of slow writes. And you have gained a new failure mode, which is that scaling takes time and traffic sometimes does not wait.

The number that replaces headroom in an elastic system is time to capacity: how long from the decision to scale until the new capacity is serving. For a stateless container on warm infrastructure this is tens of seconds. For something that has to populate a cache before it is useful, it can be twenty minutes, during which the existing fleet carries everything and often fails doing it.

Scaling also has a floor and a ceiling that people forget to check. The floor is what you are willing to pay overnight; set it too low and the morning ramp arrives faster than the policy can react. The ceiling is usually a quota somewhere, and the time to discover that quota is not during the incident where you hit it.

The dependency that does not scale with you

Scaling the service you own moves the bottleneck somewhere else. The usual destinations are a database connection pool, a rate limited third party, a shared cache, or a queue consumer group with a fixed partition count. All of these have the property that adding instances of your service makes the situation worse rather than better.

This is why a capacity statement has to name the whole path. Saying the checkout service can handle four thousand requests per second is a claim about the service, not about checkout, and the useful version names the limiting resource: checkout handles four thousand per second until the payment provider's rate limit, which is at three thousand two hundred with the current retry policy.

Finding that limit is mostly bookkeeping. Every external dependency has a documented quota, every connection pool has a configured size, and every queue has a partition count. Writing them into one table, with the arithmetic that converts each into a requests per second number for your service, is unglamorous and is usually the highest value hour in the whole exercise.