Capacity planning built on nameplate numbers — "the box has 32 cores, so we can do 32 cores of work" — fails the moment reality introduces a lock, a cache miss, or a garbage collector. Plan from measured behavior instead.
Find the real ceiling
Every system has a saturation point where latency turns up sharply while throughput flattens. That knee, not the theoretical maximum, is your real ceiling. The only way to know where it sits is to watch latency against load in production.
metrics
| where __name__ in ("cpu_util", "request_p95")
| stats avg(request_p95) by bucket(cpu_util, 0.05)
| sort cpu_util asc
Forecast the driver, not the resource
Do not forecast CPU. Forecast the business metric that drives it — orders, active users, events per second — then map that to resources using the ratio you measured. When the driver doubles, you know what breaks first.
- Identify the leading business driver of load.
- Measure resource-per-unit-of-driver from real traffic.
- Project the driver forward and find which resource saturates first.
Plan for the spike, not the average
Averages plan for a day that never happens. Capacity has to survive the peak — the sale, the launch, the Monday morning login rush. We size to the 95th-percentile hour of the busiest expected week, then add margin for the failure of one zone.
Revisit when the shape changes
A capacity model is a snapshot of a system that keeps changing. A new feature, a caching layer, a dependency migration — each moves the knee. We re-derive the ratios every quarter, because a plan built on last quarter's system is planning for a system you no longer run.