Capacity planning for small teams: one page, one number per service, no crystal ball
Most small teams do capacity planning one of two ways: a spreadsheet so elaborate it needs its own owner, or nothing at all until the disk fills on a Sunday. Both fail the same way — neither produces a decision in advance. Capacity planning that works for a five-to-fifteen-person team fits on one page: for every service, the one resource that fails first, its hard limit, the peak it actually hit in the last four weeks, the headroom left, and the action you pre-agreed to take when headroom runs low. No forecasts by quarter. No utilization dashboards nobody reads. One row per service, one owner per row, one number that decides.
This note gives the template, the rule for picking the one metric that matters per service, an action ladder by threshold, the five traps that make capacity plans lie, and a one-afternoon build plan that needs only your monitoring history and a shared doc.
1. The template: one row per service
| Service | Fails first at | Limit | 4-wk peak | Headroom | Weeks left | Owner |
|---|---|---|---|---|---|---|
| API (app servers) | memory | 8 GB | 5.2 GB | 35% | 9 | Priya |
| Postgres primary | connections | 200 | 154 | 23% | 4 | Marco |
| Ingest queue | backlog depth | 50k msgs | 41k | 18% | 3 | Priya |
| Primary disk | disk | 500 GB | 415 GB | 17% | 2 | Sam |
| Background workers | CPU | 8 vCPU | 2.9 | 64% | >20 | Sam |
Each column is a decision, so each has a rule:
- Fails first at — the resource that saturates before anything else for this service. Every service has one: CPU, memory, connections, queue depth, disk, a provider rate limit. If you don't know which resource fails first, you have never actually watched the service fall over — check the last incident, or the golden signals you alert on.
- Limit — the ceiling where degradation starts, not the number on the spec sheet. Postgres at 100% connections is dead; at 90% it is timing out under load spikes. Set the limit where users feel it, usually 80–90% of spec.
- 4-wk peak — the p95 of daily peaks over the trailing four weeks. Peak, never average: the average hides the 4pm spike that is eating your headroom every single day. If your monitoring can't produce this, the number for this week is "unknown," which is itself the finding.
- Headroom —
(limit − peak) / limit. One percentage, one direction. It goes down as you grow and up when you fix things. - Weeks left — headroom divided by the weekly growth rate of the peak. This is the only forecast a small team needs; it turns capacity from an annual guessing ritual into a runway number you read like fuel.
- Owner — a person's name. "The platform team" is not an owner; a name with a calendar invite is.
2. One saturation metric per service, no more
The temptation is to track everything: CPU, RAM, disk, IOPS, p99 latency, GC pauses. A table with twelve columns per service gets abandoned in a month. The discipline is one column: the resource that fails first. Finding it takes one hour — list your last three capacity-flavored incidents (the disk-full, the connection-pool exhaustion, the queue that backed up), and for each, note which resource tripped first. That distribution is your metric choice. When the architecture changes (you add a queue, you shard the database), the row changes; the page does not grow.
Latency is not a capacity metric — it is the symptom of the one you missed. If your only warning is p99 climbing, you are planning with the fire alarm instead of the fuel gauge. The fix is upstream: pick the resource that runs out first and give it the row.
3. The action ladder: thresholds with pre-agreed moves
A number with no pre-agreed action is trivia. Every row gets a threshold, and every threshold gets a move the team agreed to before the pager went off:
- Green — headroom above 40%: nothing. The row is read weekly and that is all. Most rows live here, permanently.
- Amber — 20–40%: schedule the fix this sprint. The pre-agreed moves, in order of preference: delete (kill the job nobody owns), shed (rate-limit or queue the load), split (move the hot path to its own instance), buy (resize up). Buying is last not because it's bad but because it's the move that hides the next ceiling instead of the current one.
- Red — under 20%: the fix happens this week, and it goes on the weekly review agenda until closed. A red row with no date is how a drill becomes a Monday-night outage.
The ladder matters more than the numbers inside it. The failure mode it prevents is the meeting where everyone agrees the database is "getting close" and then agrees again next month, slightly louder.
4. The five traps
Averages instead of peaks. Average CPU of 30% with a daily 4pm spike at 96% is not 70% headroom; it is one marketing email away from an incident. Every number in the table is a peak. Averages are how capacity plans stay green until the day they don't.
Per-server math for a per-service ceiling. Six autoscaled web instances at 40% CPU feels healthy until the connection pool on the one database behind them saturates. Autoscaling hides vertical ceilings — the queue depth, the pool, the disk. The row belongs to the service, not the fleet.
Thresholds with no owner. "We'll keep an eye on it" is the sentence that precedes every saturation outage. An owner plus a threshold plus a pre-agreed move is the whole trick; remove any one and the row is decoration.
Planning only after an incident. If the capacity table gets touched only the week after something fell over, it is a post-mortem artifact, not a plan. The table is read in the meeting you already run — five minutes, scan for amber and red, done. Piggyback it on the severity ladder you already use so a red row gets the same urgency language as a P2.
Forecasting in quarters. Annual capacity plans assume traffic moves like tides; small-team products move like weather. A launch, one influencer, one enterprise customer with a batch job — any of these doubles the peak in a week. Plan in weeks-of-headroom, re-read weekly, and let the annual budget worry about money while the weekly table worries about saturation.
5. Worked example
An eight-person B2B analytics SaaS builds the table on a Tuesday afternoon (pulling peaks from the monitoring they already had). Two rows come back red: the ingest queue at 18% headroom, growing 6% a week — three weeks left — and the primary disk at 17% with a weekly growth rate that says two weeks. Everything else is green and stays untouched.
The pre-agreed move for queue depth is buy consumers: consumers go from 2 to 6 (worker CPU was loafing at 35%), the queue peak drops to 22k, headroom returns to 56% — green, one line in the weekly review, an extra $40/month. The disk row takes the split move: attachments move to object storage that week. Total planning effort: one afternoon to build, five minutes a week to read. The counter-example is the previous quarter, when the same team "did capacity planning" by estimating Q4 traffic in a spreadsheet, then got surprised by an ingest stall on a Monday night — fourteen hours of backlog, thirty-one apologetic conversations. The stall was visible in the queue-depth chart for six weeks. Nobody was reading a chart; there was no table, so there was no owner.
6. Metrics (for the metric itself)
- Every production service has exactly one row; the table fits on one screen. A row you cannot fill is a service you cannot yet see — that is a finding, not a failure.
- Peak = p95 of daily peaks, trailing four weeks, recomputed weekly in the meeting you already run.
- Every amber or red row has a named owner, a pre-agreed move, and a date. No anonymous thresholds.
- Weeks-of-headroom is the only forecast on the page; quarters are for budgets, not saturation.
- The table is reviewed even when — especially when — everything is green; the week you skip reading it is the week a growth spike spends your headroom.
From the HIVE80lab kit
- The First 30 Minutes — free incident quick-start
- Ops Starter Kit — incident response for small teams — $14
- Ops Starter Kit Vol. 2 — advanced incident response & communications — $27
- Ops Mega Bundle — all 5 kits in one download — $49
Related: golden signals alerting is where the peak numbers come from, the cloud cost spike runbook handles the morning after you buy headroom, and the disk-full incident runbook is what a missed row looks like at 2am.
Capacity effort follows importance, and importance is stored in the catalog. The service catalog template marks which services are tier 1, and the capacity planning template gives those rows the weekly peak, headroom, and weeks-left numbers first.