Volume I · Part II · Chapter 4

The Simplest Practice

Timothy O’Brien 14 min read Free online

If you’re starting a capacity and cost practice, I would resist the temptation to begin by designing the process for the entire organization. You’ll need to understand that organization eventually, and it will need more than one approach. But there’s a more useful place to begin: find a decision somebody needs to make and stay involved long enough to see what it takes to make it well.

You might start with a service that’s about to grow. The engineering team can explain what it runs today, somebody in the business has a plan that will change the demand, and Finance wants to understand the cost of supporting that plan. Getting those people into the same discussion sounds straightforward until you discover that the growth estimate assumes a launch date, the launch date assumes available capacity, and the capacity depends on a purchasing decision that hasn’t been made. Now you have something worth working on.

That’s what I mean by the simplest practice: one decision, followed from its measurements and conversations to its consequences, then checked against what actually happened. None of the arithmetic is difficult. Decisions like this go wrong in the handoffs, when a number passes into somebody else’s plan without the condition that made it true, or a temporary arrangement is counted as though it will last until the permanent one arrives. Following one decision all the way through is how we find those handoffs, and it tells us what a larger practice will need to support.

How to read this chapter

This chapter walks one complete decision end to end — the smallest practice that still deserves the name.

  1. A portfolio small enough to follow — the tested rate, and what the service can promise after losing a failure group.
  2. What the measurements let us say — what the demand history can and cannot show.
  3. Preserve the baseline, then add the event — the forecast and the rollout scenarios, kept apart.
  4. Find a choice that can be ready — borrowed batch workers, a new failure group, and the failure arithmetic for each.
  5. Cost belongs in the same conversation — attribution, cash, and the options side by side.
  6. Borrowed capacity comes with a return date — the ten days the plan doesn’t cover.
  7. Make the decision legible — five commitments, and what reopens them.
  8. Run the complete example — the companion that reproduces the scenario.
  9. Come back after the launch — one outcome in which the conversation helped the forecast and one in which it hurt.

The runnable companion preserves the inputs and calculations so you can follow the decision and examine what changes when its assumptions fail.

A portfolio small enough to follow

We’ll use the constructed portfolio introduced earlier: a customer-facing web service and its data services, an AI support assistant funded by the support organization, a platform serving many small applications, and systems running in parallel during a migration.

For the first decision, we’ll concentrate on the web service and the measurement Chapter 2 gave it. Its operators have tested a particular request mix at the configurations we’re considering: one worker can complete 250 requests per second while holding ninety-fifth-percentile (p95) latency to 200 milliseconds and errors to no more than 0.1 percent of offered requests. That evidence exists because we’ve assumed it. In a real discussion, I’d want the operators to explain how they qualified the rate and what change in request mix, configuration, or downstream behavior would make it unreliable.

The service has 64 workers arranged in four independent failure groups of 16, providing a tested 16,000 requests per second when everything is available. The business has also agreed that the service must continue after losing any one group. That requirement changes the capacity we can promise:

(64−16)×250=12,000requests/second. (64-16)\times250=12{,}000\ \text{requests/second}.

Twelve thousand is the limit I’d use for this decision; as Chapter 2 showed, sixteen thousand answers a question about normal operation. Even the lower number covers only the loss of one independent group; we haven’t established what happens when an entire region fails.

The history gives us an expected peak of 10,000 requests per second, simply the most recent Monday’s peak carried forward; Volume II, Chapter 14 examines that seasonal-naive method. It leaves 2,000 requests per second of room under the failure condition. Then the product lead explains a rollout that could add 4,000, with a feasible staged version adding 2,000. No amount of studying the history will tell us which version the business will choose; it hasn’t chosen yet.

What the measurements let us say

Before relying on the demand number, I’d want to know how it was collected. Our historical fixture records each day’s highest one-minute mean offered request rate, and assumes those requests were served without rejection or accumulating queues. That’s a substantial qualification. If a service completes 10,000 requests a second while rejecting another 2,000, a report showing only completed work will understate what customers tried to do.

The interval needs similar care. A one-minute mean cannot describe a burst lasting a few seconds, however diligently we save the highest mean each day, so the operators still need evidence about how the service handles shorter bursts.

The rollout estimate needs someone else: whoever understands the activity that produces the requests. A small service doesn’t need a committee to establish that its product manager expects more customers next month, but we do need enough of the conversation written down to know which estimate we’re using, when it was made, and what still has to happen for it to be right, because that’s what we’ll check when the plan changes.

Preserve the baseline, then add the event

For the web service, I’d put the historical estimate and the two rollout choices beside each other:

View Peak requests/s What it assumes
Historical baseline 10,000 Recent pattern continues
Staged rollout 12,000 Baseline plus 2,000
Full rollout 14,000 Baseline plus 4,000

These are alternative views of demand, so they remain separate throughout the decision. The event record connects the increments to the product lead’s estimate: when it was supplied, when the rollout could begin, what it depends on, and when we’ll check it again. It also answers a question that’s easy to miss: how much of the rollout is already present in the baseline? For this example, none. Volume II, Chapter 16 works through a rollout whose first customers are already in the history; their contribution comes off the event’s total, and only the remainder is added to the baseline.

I’d also keep the original baseline after choosing the staged 12,000 estimate. When actual demand arrives, I want to see whether the conversation helped. It would be convenient for the argument of this book if every relationship produced better information, but that’s something we need to examine, including the occasions when a reasonable-sounding adjustment made the forecast worse.

Staged and full are the business’s choices, not outcomes to weight with probabilities. Each also carries an estimate: the 2,000 is the product lead’s view of what staging adds, and nothing holds demand there unless the rollout includes a control that can.

Find a choice that can be ready

Staging alone would fit the current service exactly, 12,000 against 12,000 after losing a group, so any demand above the estimate would come straight out of the failure allowance. I’d want more room than that.

Elsewhere in the portfolio, eight compatible workers run batch work that can be deferred. They could provide that room if the batch owner can accept the delay and explain how the work will catch up. The workers also need two days of preparation and testing before they’re useful to the web service. Neither condition is visible in the inventory.

Suppose we can meet them and distribute two workers into each existing failure group. We now have four groups of 18, so the one-group-loss calculation becomes:

(72−18)×250=13,500requests/second. (72-18)\times250=13{,}500\ \text{requests/second}.

Losing a group now costs 18 workers rather than 16, because the placement changed what a failure takes with it. Staging leaves 1,500 requests per second of measured room; the full rollout exceeds degraded capacity by 500.

We can also request 16 new workers in a fifth independent group. The estimated time to qualified readiness in this constructed case is six weeks, including delivery, preparation, and qualification. It is a scenario assumption, not a stated Azure VM provisioning time or a supplier service-level commitment. Once the group is ready, we’d return the eight batch workers. Five groups of 16 would provide

(80−16)×250=16,000requests/second (80-16)\times250=16{,}000\ \text{requests/second}

after losing one group, leaving 2,000 above the full rollout’s 14,000. Spreading the same sixteen workers across the existing groups would leave only (80 − 20) × 250 = 15,000, because a failure would still take a quarter of the service rather than a fifth. That advantage depends on the fifth group being genuinely independent of the other four; Volume II, Chapter 18 works through the failure groups.

The fifth group gives the product team a path to the larger launch, but not by next week: approving the purchase doesn’t shorten the six-week estimate.

I’d take this back as a proposal: stage the launch once the transferred workers are qualified, pursue the new group, and expand when it’s ready. The arithmetic shows why that could work if the tested rate really holds at 72 and 80 workers, including at the data services behind them. That’s the first assumption I’d ask the operators and database specialists to confirm.

Cost belongs in the same conversation

Chapter 1 priced both changes at its constructed all-in charge of $40 per worker-day. The batch workers are already paid for, so lending them for 30 days moves 8 × $40 × 30 = $9,600 of attribution from batch to the web service without any new enterprise payment. As Chapter 1 warned, that doesn’t make the loan free: if batch can only catch up afterward by renting extra workers, the transfer has a cash cost after all, arriving later in somebody else’s budget.

The new group is a purchase: 16 × $40 = $640 a day, $19,200 per 30 days, or $640 × 365 = $233,600 a year for as long as we keep it. Before anyone calls that an obvious increase, or offsets it against resources released elsewhere, I’d want Finance to examine it the way the operators examined the service rate: when the charge starts, and what the agreement binds us to. If metering begins at provisioning, we pay for part of those six weeks before the group carries any traffic; Azure, for example, bills a virtual machine’s compute by its power state, not by whether it’s serving anyone.1 A commitment that lowers the daily rate can outlast the launch that justified it, and released resources offset the purchase only if their agreement lets us stop paying for them.

Side by side, the options look like this:

Option After losing one group Room at staged 12,000 Room at full 14,000 Usable New enterprise cost
Today: 64 workers, 4 × 16 12,000 0 −2,000 now none
Borrowed: 72 workers, 4 × 18 13,500 1,500 −500 day 2 until day 32 none; batch work waits
New group: 80 workers, 5 × 16 16,000 4,000 2,000 estimated day 42 quoted $640 a day

Read down the Usable column and the next problem appears.

Borrowed capacity comes with a return date

Start the planning clock at day zero. Suppose the batch owner agrees to lend the workers for 30 days, counted from day two, when they’re ready; the loan ends at the start of day 32. The new group’s estimated qualified-ready date is day 42, and that assumes the order goes in on day zero. Every day the approval waits pushes it later.

We have a ten-day hole in the plan.

The temporary transfer expires on day 32; the new group’s qualified readiness is only estimated for day 42. The dotted bridge requires a further batch agreement.Open full-size diagram

Batch has agreed to a month, the supplier has estimated six weeks, and the web service needs capacity throughout. Nobody’s individual statement is unreasonable, but together they leave a period we haven’t covered. I want that gap in the first discussion, while the batch owner and the product lead still have time to consider alternatives.

Dates make the gap harder to wave away. The companion puts day zero on June 28, 2026, so the loan runs from June 30 up to July 30, and supply is estimated for August 9. June 30 is a calendar quarter-end, and Chapter 1 shows these batch workers with month-end and quarter-end spikes; “deferrable” there meant jobs that could wait a day. Before anyone treats the workers as spare for a month, I’d ask the batch owner what has to run at the close.

Extending the transfer to the start of day 42, or to day 70 if supply slips, raises the attribution in proportion; Volume II, Chapter 18 prices both. The harder question is whether batch can tolerate another ten days of delay, which would also cover the July month-end, or another thirty-eight. We can calculate either extension immediately; agreeing to one depends on understanding what we’re asking that customer to give up.

If the workers return on day 32 as originally agreed, degraded web capacity falls back to 12,000, leaving no margin above the staged estimate. We need a renewed agreement, another qualified source, a way to reduce demand, or an authorized change to the service obligation. The capacity team can help develop those choices, but it can’t quietly extend someone else’s commitment because the alternative makes our plan look incomplete.

Make the decision legible

For this proposal to proceed, five of the people in Chapter 3’s authority map have to commit to something:

Who Commitment Needed before
Product lead Stage the rollout at the estimated 2,000 additional requests/s, and keep the ability to stop expansion the rollout
Batch owner Accept a bounded delay with a catch-up plan the transfer
Web operations lead Qualify the transferred workers, including their behavior when a group fails the rollout
Budget owner Authorize the $640-a-day purchase within a delegated limit, contingent on supply and the actual terms the purchase
Responsible business executive Accept the remaining exposure, or change the launch scope or date the rollout

Several may be settled in one conversation. They’re still five commitments, and the decision record should make a missing one obvious. If batch refuses the delay, we no longer have this option. If the business refuses staging, 14,000 still exceeds 13,500, and changing the failure requirement to make the arithmetic pass would mean offering a different service. We may need to discuss that possibility, but with the person entitled to change the obligation and with its consequence understood.

The agreement also needs a way to reopen the discussion before the next scheduled review. Demand running above the staged estimate, a changed request mix, a larger launch, an infeasible catch-up plan, or delayed delivery can each remove a condition we relied on. Waiting for the monthly meeting would only be sensible if we could afford to wait. Whoever learns about the change should know whom to contact and what decision it may affect.

Run the complete example

The companion requires Python 3.9 or later and uses only the standard library and constructed inputs; it needs no credentials and makes no API calls. It is published under the Apache License 2.0 at https://github.com/withoutsurprise/scale-without-surprise-companion. From the directory that contains examples/ in a clone or download of that repository, run:

python3 examples/capacity_portfolio/run.py

The command writes a readable report.md, a detailed report.json, and CSV files for forecasts, backtests, and decision responsibilities under examples/capacity_portfolio/output. The report counts an option as available only if its ready date has arrived and any transfer it relies on hasn’t expired, and decisions.csv holds the five commitments above. The fixture even contains a later revision of the rollout estimate that the earlier forecast is prevented from using; Volume II, Chapter 14 explains why that matters. The AI, long-tail, and index-rebuild examples stay in separate pools with their own units, and none of their unused capacity fills the web service’s 500-request-per-second gap. The program makes that arithmetic inspectable; it doesn’t measure a production service, reserve anything, or tell you that a plan has changed.

Come back after the launch

In the first constructed outcome, the staged launch peaks at 12,800 requests per second. The historical baseline missed by 2,800, whereas the event-adjusted estimate missed by 800. The conversation gave us a better forecast, but the more pressing result for the operators is that only 700 requests per second remain under the specified failure case, and that 700 depends on the borrowed workers. If they go back on day 32 with demand near this peak, losing a group would leave 12,000 against 12,800: 800 short of the promise, not merely out of margin. The extension, or one of its alternatives, has stopped being optional.

Before discussing another expansion, I’d want to know where the extra 800 came from, more customers than the staged scope intended or the intended customers doing more, and whether the request mix still supports our capacity calculation. Product’s ability to stop expansion is now doing real work.

Now consider a separate outcome in which the rollout is delayed and actual demand is 10,200. The baseline misses by 200; the adjusted estimate overstates demand by 1,800. The intelligence made the point forecast worse, and we need to examine that with the same interest. The forecast error isn’t the whole cost, either: if we learned of the delay only after the transfer, batch gave up its workers for room nobody used. A launch that moved after the forecast, an update we missed, and a change in the request mix each call for a different repair; Volume II, Chapter 14 takes them up.

Much of this reasoning depends on the measured service rate applying to the work that actually arrives. We need to get closer to that measurement before asking it to carry any more of the plan.

Notes and sources

  1. Microsoft Learn, “States and billing status of Azure Virtual Machines”, checked October 4, 2026. Compute is billed in the Starting, Running, Stopping, and Stopped (allocated) power states and not once the machine is deallocated, although disks and networking can still incur charges. The page doesn’t address how long a constrained configuration takes to obtain or qualify.↩︎