The spreadsheet says you have headroom. The telemetry says you tip over at 70% CPU because of a lock you forgot about. Plan from what the system does, not what it should do.

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.