Designing for Scale · Requirements to Architecture
From a one-line prompt to a defensible architecture: separating functional from non-functional requirements, quantifying who/what/how-many, and refusing to draw a single box until the constraints are on the board.
The problem this post solves
Most bad architectures are not bad because someone chose the wrong database. They are bad because the design started before anyone agreed on what was being built, for how many people, with what tolerance for staleness, and at what cost of being wrong.
"Design a system to share short notes" is not a specification. It is a prompt. Between that prompt and a defensible architecture sits a step that is routinely skipped: converting an ambiguous sentence into a bounded problem with numbers attached. This post is about that step, and about why drawing boxes first is the most expensive habit in system design.
The claim is simple. The architecture is a consequence of the constraints. If the constraints are not written down, the architecture is a guess wearing a diagram.
First principles
Start from what a system actually is: a machine that moves data between people and storage, under limits.
- 1A design is a set of choices between alternatives.forced by · Cache or no cache, sync or async, one region or many — each is a fork.
- 2A choice is only correct relative to a constraint.forced by · Async is right when the caller can tolerate delay, and wrong when it cannot. Nothing about the box tells you which.
- 3Therefore an unstated constraint makes the choice unfalsifiable.forced by · If nobody wrote down the latency target, no reviewer can say the choice is wrong — or right.
- 4Unfalsifiable designs fail late.forced by · They fail in load testing, or in production, rather than at the whiteboard where fixing them is free.
There are exactly two families of requirement, and conflating them is the most common early error.
Functional requirements describe what the system does. They are verbs. A user creates a note. A user retrieves a note by link. A note expires. You can write a test for each one, and the test passes or fails.
Non-functional requirements describe the conditions under which it must keep doing those things. They are adjectives and numbers. Ten thousand reads per second. Under two hundred milliseconds at the ninety-ninth percentile. Available when one region is on fire. Durable enough that losing a note is a serious incident rather than an annoyance.
Functional requirements determine what components exist. Non-functional requirements determine what shape they take. A note store and a note store that serves a hundred thousand reads per second across three continents contain the same nouns and almost none of the same architecture.
The core model
The four-step frame below is not a trick. It is the same skeleton mature design documents follow, compressed into something that fits on a whiteboard.
The dotted lines matter as much as the solid ones. Discovering during capacity estimation that a feature implies a petabyte of hot storage is not a failure of the process; it is the process working. The cheapest place to discover an impossible requirement is immediately after writing it down.
Step one in practice: bounding the problem
Three questions convert a prompt into a problem.
Who uses it, and how many are there? Total registered users is nearly useless. Daily active users is the number that generates load. The ratio between them varies enormously by product and is the single most common place where an estimate goes wrong by an order of magnitude.
What do they do, and in what proportion? Every system has a read/write ratio, and it dictates the architecture more than almost anything else. A read-heavy system wants caching, read replicas and denormalisation. A write-heavy system wants partitioning, batching and back-pressure. A system where both are high wants a much larger budget.
What must never happen? This is the question people skip, and it is where durability and consistency requirements live. Losing a draft is bad. Losing a payment is unacceptable. Showing a stale follower count is fine. Showing a stale account balance is a regulatory event. The severity of the worst case sets the floor for how much machinery is justified.
- Population growth is usually the slowest and most predictable corner.
- Frequency is where product changes ambush you — a new notification can multiply it overnight.
- Payload is where media features ambush you — text to images to video is three orders of magnitude.
Writing requirements that constrain
A requirement that cannot be violated is not a requirement. Compare:
"The system should be fast." This constrains nothing. Every design satisfies it, so it cannot be used to reject any design.
"Reads return in under 200 ms at p99, measured at the edge, for the top 90 percent of keys by traffic." This rejects designs. It rules out a cold read path that crosses an ocean. It implies a cache. It admits that the long tail is allowed to be slower, which is what makes it affordable.
The second version does three things the first does not: it names a percentile rather than an average, it names a measurement point, and it scopes itself to a subset of traffic. Each of those is a place where a vague target quietly becomes a much more expensive one.
Percentiles deserve particular care. An average latency target is nearly meaningless for user experience, because a page that makes twenty backend calls will hit the tail on most requests. If each call independently meets a p99 of 200 ms, the probability that a twenty-call page avoids the tail entirely is roughly 0.99 to the twentieth power — about 82 percent. Put differently, nearly one page load in five hits at least one slow call. Tail latency compounds; averages hide it.
Capacity and constraints
Capacity estimation earns its place only when a number changes a decision. An estimate that decorates the design without ruling anything out is wasted time, and reviewers can tell.
The useful outputs are few. Requests per second, derived from daily actions divided by seconds in a day, then multiplied by a peak factor because traffic is never flat. Storage growth per year, derived from writes per day times bytes per write times retention. Bandwidth, derived from reads per second times response size. Working set, meaning the portion of data that is hot enough to justify keeping in memory.
Each of those has a decision hanging off it. Requests per second decides whether one machine can serve the traffic. Storage growth decides whether the data fits on one node or must be partitioned. Bandwidth decides whether a content delivery network is a nice-to-have or load-bearing. Working set decides the cache tier size, and therefore much of the cost.
The arithmetic should stay symbolic and round. Precision is false comfort here: the input assumptions carry far more error than the multiplication does. What matters is the order of magnitude and the decision it forces.
Architecture
Only now do the boxes appear, and they appear as answers to the constraints rather than as a remembered template.
Every element in that diagram should be traceable to a requirement. The edge exists because of a latency target and a geographic spread of users. The cache exists because of a read/write ratio and a working set that fits in memory. The queue exists because some work was identified as tolerating delay, which is a statement about requirements, not about technology. The replicas exist because of an availability target and a read volume that exceeds one node.
If a component cannot be traced back to a constraint, it is decoration, and it should be removed. Extra components are not free: each one adds a failure mode, an operational burden and a consistency question.
The reverse check is equally important. Walk the non-functional list and ask which part of the diagram satisfies each entry. A durability requirement with no replication or backup answering it is a hole. An availability requirement with a single point of failure in the path is a hole. Holes found at the whiteboard cost nothing.
Choosing the deep dive
A design review has time for one component to be examined properly. Choosing which one is itself a judged decision, and the right choice is the component where the design is most likely to be wrong — usually the one under the most extreme constraint, or the one where a trade-off was asserted without justification.
The wrong choice is the component the designer happens to find most interesting.
- Separate functional from non-functional requirements and explain why the latter drive structure.
- Convert a vague prompt into bounded requirements using population, frequency and payload.
- State a latency requirement that can actually reject a design.
- Derive requests per second, storage growth, bandwidth and working set — and name the decision each one forces.
- Trace every component in a diagram back to the constraint that justifies it, and delete the ones that fail the trace.
Failure modes
Designing from a remembered template. Producing the same diagram regardless of the prompt is the clearest signal that requirements were never engaged with. The tell is a component that cannot be justified when questioned.
Averages instead of percentiles. A design that meets an average latency target can still be slow for most users of a multi-call page, because tail latency compounds across calls.
Confusing registered users with active users. This single substitution routinely produces estimates that are wrong by one or two orders of magnitude, in whichever direction is least convenient.
Treating peak as average. Traffic is not flat. Designing to the daily mean guarantees the system is under-provisioned at exactly the moment it matters most.
Accepting an unbounded requirement. "All historical data must be instantly queryable" and "the system must never lose a write" are not requirements until someone attaches a retention window and a durability target. Unbounded requirements silently import unbounded cost.
Deferring the consistency question. Whether a read may return stale data is a product decision that determines replication strategy, cache invalidation and multi-region topology. Leaving it implicit means it gets decided by accident, usually by whichever component was built first.
Trade-offs that matter
The recurring tension in this phase is between bounding the problem tightly enough to design against, and bounding it so tightly that the design cannot survive a plausible change in the product.
Over-constraining produces a system exquisitely fitted to today's numbers that requires rearchitecting when one assumption shifts. Under-constraining produces a system with no defensible choices in it at all, which is worse: at least the over-fitted system works today.
The practical resolution is to state which assumptions are load-bearing. If the design depends on the read/write ratio staying above roughly one hundred to one, say so explicitly. That sentence turns a hidden fragility into a monitored one, and it tells whoever inherits the system which number to watch.
Similarly, requirements should record what was deliberately excluded. A design that says "cross-region strong consistency is out of scope; regional reads may be up to a few seconds stale" is stronger than one that quietly omits the question, because the omission is now a decision someone can revisit rather than a landmine someone will discover.
What to carry forward
The frame is small enough to hold without notes: bound the problem, quantify it, design against the quantities, then examine the weakest component properly.
The discipline underneath it is smaller still. Before drawing anything, write down the numbers the drawing has to satisfy. After drawing anything, check that every box traces back to one of them. Most design failures are a violation of one of those two sentences.
Capacity estimation, which is where the numbers actually come from, is involved enough to deserve its own treatment — including how to keep the arithmetic honest and how to tell an estimate that constrains from one that merely impresses.