Cruxly

Cruxly › System design › Foundations of Data Systems

Reliable, Scalable, Maintainable: Measuring a System

three adjectives that mean nothing until each one has a number, a distribution and an owner

Open the interactive lesson

Reliable, scalable and maintainable are the three words every design review starts with and almost none ends with a measurement. This lesson turns each into something you can write on a whiteboard: a fault budget, a load parameter, a latency percentile, a fan-out calculation and a list of the work that will page someone at 3am. It builds on Estimating the Load and Designing for Observability and does not repeat them.

Three words that each need a number

An adjective in a design review is a request for a measurement that nobody has made yet. "It must be reliable" can be argued for an hour. "99.9% of requests succeed in under 300 ms, measured at the edge, over 28 days" can be tested on Monday.

QualityThe vague versionThe measurable versionWho owns the number
Reliable"It should not go down"An availability target per request class, an error budget, and a list of the faults it must surviveProduct and engineering together
Scalable"It should handle growth"One or two named load parameters, a growth rate, and the latency percentile that must hold while they growEngineering, from the traffic forecast
Maintainable"It should be easy to run"A cap on toil, a time-to-diagnose target, and the number of independent things a new engineer must learn before the first safe changeThe team that is on call

Two points keep this honest.

First, the three pull against each other. Redundancy raises reliability and adds moving parts to maintain. Sharding raises scalability and removes the ability to run a transaction anywhere. A simpler system is easier to run and often worse at both of the others. Your job in the room is to say which one you are spending, not to claim all three are free.

Second, 100% is not a target. The Google SRE book's chapter Embracing Risk says 100% is probably never the right reliability target, because it is impossible to reach and usually more than users want or can notice. It adds that an incremental improvement in reliability may cost 100 times more than the previous increment, and that a user on a smartphone with 99% reliability cannot tell the difference between 99.99% and 99.999% service. Other components dominate what that user sees.

:::key Verified November 2026 Google SRE book, Embracing Risk: "100% is probably never the right reliability target", an incremental improvement "may cost 100x more than the previous increment", and the smartphone example above. The same chapter works a request-based example: a service handling 2.5 million requests a day against a 99.99% target can return up to 250 errors and still meet it. :::

Worked arithmetic, not a source's figure. The downtime a target allows is just the miss rate times the window. 99.9% over 28 days leaves 0.001 x 28 x 24 x 60 = 40.3 minutes. 99.99% over a 365-day year leaves 0.0001 x 525,600 = 52.6 minutes. 99.9% over the same year leaves 525.6 minutes, about 8.8 hours. Each extra nine divides the allowance by ten, and by the SRE book's account it can multiply the cost far more than that.

What a staff engineer says in the room. "Which of the three are we optimising for this quarter, and what are we willing to make worse?" What they check before agreeing to a number: whether it is measured at the edge where the user is, whether the window is rolling, and whether anyone has the authority to stop releases when the budget is gone. Designing for Observability covers the SLI, SLO and error-budget mechanics; this lesson uses them as inputs.

Faults versus failures, and why you design for faults

A fault is one component deviating from its spec; a failure is the system as a whole no longer delivering its service. The goal of reliable design is to keep faults from becoming failures, which means you cannot get there by preventing every fault. Disks fail, processes get killed by out-of-memory handlers, deploys ship bugs, operators mistype, a cable gets unplugged. At enough scale something is always broken somewhere, so the design question is what the system does while it is.

Dean and Barroso make the same move for latency in The Tail at Scale: just as fault-tolerant design builds a reliable whole from unreliable parts, they propose "latency tail-tolerant" services that give predictable responses from less predictable components. The idea carries across the whole lesson. You do not eliminate variability in the parts; you build a whole that absorbs it.

Three families of fault, with different defences

Correlation is the number that matters. Two independent replicas each 99.9% available give, in the simplest model, 1 - 0.001 x 0.001 = 99.9999% for the pair (worked arithmetic, assuming independence). If a shared dependency has its own 99.9%, the pair is capped by it: the system cannot be more available than the thing both replicas need. This is why a design with "three nines, three times" can still be a three-nines system.

Chaos and drills are how you learn what the fault actually does. A failover path that has never run is a hypothesis. A staff engineer asks when each safety mechanism last fired for real or in a rehearsal, because untested recovery code is itself a likely source of the next incident.

What experts say in the room. "What are the faults we are designing for, and which of them are correlated?" The failure story is nearly always a correlated one. What a senior checks: the list of shared dependencies behind the "redundant" components (DNS, config service, certificate expiry, a single deploy pipeline), and the last time failover was exercised.

Load parameters: what 'scale' means for this system

Scalability is not a property of a system; it is a property of a system under a named kind of growth. "Does it scale?" has no answer until you say which number grows. The number is a load parameter, and the right one depends on the design: requests per second to a web tier, the read-to-write ratio on a database, simultaneous open connections for a chat gateway, the size of the largest fan-out for a feed, the hit rate for a cache, the number of keys in the hottest partition.

The same product has several candidates, and they stress different parts:

SystemCandidate load parametersWhat each one breaks first
Photo-sharing feedPosts written per second; followers per account; reads per user per dayWrite path if the system fans out on write; read path if it assembles on read; a single celebrity account is a distribution problem, not an average one
Chat gatewayConcurrent connections; messages per connection per minuteMemory per connection and file descriptors long before CPU
Payments ledgerTransactions per second; the share touching one hot accountLock contention on the hot row, not total throughput
SearchQueries per second; documents indexed; query complexityShard count and merge cost, as in Elasticsearch and OpenSearch: shards, refresh, translog and shard sizing

The distribution matters as much as the total. An average of 100 followers per account hides the account with 50 million. Two systems with the same requests per second can behave completely differently if one has uniform keys and the other a hot key. Always ask for the shape of the load, not just the sum. The SRE book's Service Level Objectives chapter makes the same point about throughput: a system serving 200 requests a second in alternate seconds and nothing in between has the same average as one serving a steady 100, but twice the instantaneous load at peak.

Two ways to answer growth

Linear scaling is a claim you test, not assume. If doubling machines doubles throughput, you have linear scaling for that load parameter. Shared bottlenecks (a single leader, a coordination service, a lock, a shared cache) bend the curve. The honest statement is "this design scales out on reads up to the leader's write capacity, about X writes a second, then we need to partition". That sentence names the load parameter, the bottleneck and the next move.

Capacity has a safety margin. The SRE book's Monitoring Distributed Systems chapter lists saturation, how "full" the service is, among its four golden signals, next to latency, traffic and errors. Many services degrade sharply past some load, so you plan for peak plus the loss of a failure domain, not for the average.

What experts say in the room. "What are the one or two load parameters, what is their growth over the next 12 months, and what is the headroom?" What a senior checks: the saturation signal for each tier (CPU, connections, queue depth, disk), and which tier saturates first under the forecast mix. Estimating the Load gives the arithmetic for turning users into requests per second; this section is about picking which number to estimate.

Latency percentiles and why the mean lies

The mean is the one summary of a latency distribution that no user ever experiences. Latency distributions are skewed: most requests are fast and a few are very slow, so the mean sits somewhere between and describes neither. The SRE book's Service Level Objectives chapter says plainly that averaging request latencies may seem attractive but obscures an important detail, and that you cannot assume the mean and the median are the same, or even close.

Worked arithmetic, not a source's figure. Take 100 requests, 99 at 50 ms and 1 at 5,000 ms. The mean is (99 x 50 + 5,000) / 100 = 99.5 ms, double the typical request, and no request took 99.5 ms. The median is 50 ms and the slowest 1% is 100 times slower. Whichever you alert on tells a different story.

The vocabulary

:::key Verified November 2026 Google SRE book, Monitoring Distributed Systems: "If you run a web service with an average latency of 100 ms at 1,000 requests per second, 1% of requests might easily take 5 seconds." The same chapter says to collect request counts bucketed by latency, suitable for a histogram, with boundaries spaced roughly exponentially (it uses factors of about 3), instead of storing every latency. Service Level Objectives gives an illustrative percentile SLO: 90% of Get calls under 1 ms, 99% under 10 ms, 99.9% under 100 ms. :::

Rules a senior holds to

Coordinated omission is the measurement bug interviewers like. If a load generator waits for each response before sending the next request, a stall removes all the requests that would have been sent during the stall, and the recorded tail looks far better than users would have seen. Send on a schedule, record from the intended send time. The term is Gil Tene's, and this is load-testing practice rather than a claim from the sources above.

What experts say in the room. "p99 under 300 ms at the edge, p50 around 40, and I want the histogram, not the mean." What a senior checks: that percentiles are computed from merged histograms, that error latency is plotted, and that the SLO percentile matches what a user session actually experiences.

Tail latency under fan-out

Fan-out turns a rare slow response into the common case. This is the central result of Dean and Barroso's The Tail at Scale (Communications of the ACM, February 2013), and it is the reason percentile targets at the leaf are far stricter than percentile targets at the user.

The paper's example. Suppose each server typically answers in 10 ms but has a 99th-percentile latency of one second. If a request touches one such server, one user request in 100 is slow. If it must collect answers from 100 such servers in parallel, the paper states that 63% of user requests take more than one second. It adds a second case: with only one in 10,000 requests slower than a second at each server, a service of 2,000 such servers sees almost one in five user requests exceed a second.

The formula behind it (worked arithmetic). If each of N servers is slow with probability p, independently, the chance that a request waiting for all of them is slow is 1 - (1 - p)^N.

Servers called in parallelp = 1% slow per serverp = 0.01% slow per server
11.0%0.01%
109.6%0.10%
10063.4%1.0%
1,00099.996%9.5%
2,000about 100%18.1%

The 63% and the roughly 18% match the paper's figures. To keep the user-level p99 healthy with a fan-out of 100, the per-server slow rate has to be about 0.01% (a p99.99 target), because 1 - 0.99^(1/100) is about 0.0001. That is where the leaf's budget comes from.

:::key Verified November 2026 Dean and Barroso, Table 1, measured on a real Google service with a fan-out tree. The 99th-percentile latency for one random leaf to finish, measured at the root, is 10 ms. Waiting for 95% of leaf requests to finish: 70 ms at the 99th percentile. Waiting for all of them: 140 ms. The paper observes that waiting for the slowest 5% of leaf requests accounts for half of the total 99th-percentile latency. :::

The mitigations, and what each one costs

Where the arithmetic bites in your own design. A product page that calls 30 services serially and 6 in parallel, an API gateway that scatters a search to 200 shards, a cache cluster where a multi-get touches every node, all multiply tails. The cheap defences are fewer calls per request, a deadline propagated down the call chain, timeouts shorter than the caller's deadline, and returning partial results rather than waiting for the slowest. Rate Limiting and the load-shedding work in the reliability phase are the other half: hedging under overload adds load exactly when the system can least take it.

What experts say in the room. "With fan-out 100 the user p99 is the leaf p99.99, so I will hedge after the p95 and keep the extra load near 5%." What a senior checks: the fan-out per request in the busiest path, whether the operation is idempotent before hedging it, and whether a retry storm is possible when the slow component is also the overloaded one.

Throughput versus latency, and where they trade

Throughput is how much work per second; latency is how long one piece of work takes; and you cannot raise one forever without paying in the other. The SRE book's Service Level Objectives chapter notes that they are linked: higher request rates often increase latency, and many services have a performance cliff past some load threshold.

Little's law ties the two together. In a stable system, the average number of requests in flight equals the arrival rate times the average time each spends in the system: L = lambda x W. Worked arithmetic: a service taking 200 ms per request at 500 requests per second has, on average, 500 x 0.2 = 100 requests in flight. If each uses a worker thread, you need at least 100 threads, and if the pool is 64 the excess queues. This is the cheapest capacity calculation in the field and it works for connection pools, thread pools and database connections alike.

The queueing curve is the cliff. Utilisation is the share of capacity in use. With random arrivals, queues grow explosively as utilisation approaches 100%. In the simplest single-queue model (M/M/1: random arrivals, random service times, one server), average time in the system is the service time divided by (1 - utilisation). Worked arithmetic under that model:

UtilisationTime in system as a multiple of service time
50%2x
80%5x
90%10x
95%20x
99%100x

Real systems are not M/M/1, so the exact multiples differ, but the shape is general: latency is nearly flat until the knee and then climbs steeply. That is why running a tier at 90% CPU "because it is efficient" is a latency decision, not a cost decision, and why the tail suffers first. Dean and Barroso argue the opposite direction as well: tail-tolerance techniques are what let utilisation rise without lengthening the latency tail, so you avoid wasteful over-provisioning.

Where design choices trade one for the other

Goodput, not throughput. Throughput counts requests served; goodput counts requests served within their deadline that the user still wants. Past the knee, throughput can stay flat while goodput falls toward zero, because every response arrives too late to be used. Load shedding exists to protect goodput.

What experts say in the room. "I would not plan above roughly 70% utilisation on a latency-sensitive tier, and I would put a bound on every queue." The 70% is a common planning rule, not a number from the sources here; the table above is why a team would pick some number below the knee. What a senior checks: latency versus utilisation on a load test, the knee's location, queue depth and age, and what happens to goodput when the offered load is 150% of capacity.

Maintainability: operability, simplicity, evolvability

Most of a system's cost arrives after launch, as operations and change, which is why maintainability is a design property and not a coding-style one. It is the quality people skip because it has no obvious metric. It does have measurable proxies, and a staff engineer asks for them.

Three parts, each with something you can count

PartThe questionA measurable proxy
OperabilityCan the on-call engineer see what is wrong and fix it safely?Time to detect, time to diagnose, number of runbook steps done by hand, share of incidents needing a code change to mitigate
SimplicityCan a new engineer hold the design in their head?Number of distinct components, protocols and data stores; number of places one fact is stored
EvolvabilityCan we change it without a flag day?Whether a deploy can be rolled back; whether old and new versions can run side by side; time from decision to production for a schema change

Operability: count the toil. The SRE book's Eliminating Toil chapter defines toil as operational work that is manual, repetitive, automatable, tactical, without lasting value, and scaling linearly with the service. It states a target of keeping toil below 50% of each SRE's time, and warns that toil grows if unchecked and becomes toxic in large quantities. As a design review question: for each new component, which recurring manual task does it create (rotating certificates, resharding, rebuilding an index, replaying a dead-letter queue), and is there an owner and a plan to automate it? A design that needs a human every Friday to rebalance is one that has linear operating cost hidden inside it.

Monitoring is part of operability, and it should be simple. The Monitoring chapter says to design the monitoring system with an eye toward simplicity, that the rules catching real incidents most often should be as simple, predictable and reliable as possible, and that monitoring can become so complex it is fragile. It also says to spend much more effort catching symptoms than causes. Its framing question is what is broken, and why. The four golden signals (latency, traffic, errors, saturation) are the shortest answer for a user-facing service. See Designing for Observability for how to wire them.

Simplicity: essential versus accidental complexity. Some complexity comes from the problem, and some comes from choices. Each additional store, queue or service adds failure modes, a deployment, a dashboard, an upgrade path and a learning curve. The test is whether you can name what each component is for in one sentence and what you would lose if you deleted it. Good abstractions hide detail behind a stable interface so a change stays local; bad ones leak, and the leak is where incidents live. Boundaries Are Where Guarantees Die covers where to cut.

Evolvability: assume the requirements are wrong. They will change, so the design should make the common changes cheap.

Three questions to put on the whiteboard

  1. What is the cost to the team of running this for a year, in people and pages, not just in servers?
  2. What is the first thing a new on-call engineer would be confused by?
  3. Which decision here is hardest to reverse, and have we deferred it as long as we can?

A boring technology rule. Every novel component is a bet on the team's ability to operate it at 3am. Spend that novelty on the one place the product needs it, and use well-understood parts elsewhere.

Further reading. The framing of reliable, scalable and maintainable used here is the one Martin Kleppmann's Designing Data-Intensive Applications uses to open its first chapter; this lesson's explanations, examples and numbers are our own and come from the Google papers and SRE book chapters cited above. For the operational side in depth, read the free Google SRE book at sre.google, and for latency at scale, Dean and Barroso's The Tail at Scale.

What experts say in the room. "Who gets paged for this, what do they do first, and how many manual steps is that?" What a senior checks: the toil budget, rollback time, the number of data stores and queues versus the number of engineers who understand them, and whether a schema change can ship without downtime.

How to say it in the interview

"When someone says the system must scale, I ask scale in what: which load parameter, and at which percentile do we promise latency? The mean hides the slow requests, so I would commit to something like p99 under 300 ms and watch p99.9 as well. And I would do the fan-out arithmetic early: if one backend is slow on 1 request in 100 and a page calls 100 of them in parallel, 63% of pages hit a slow backend. Reliable then means tolerating faults, not preventing them, and maintainable means I can name the toil this design creates."

Checklist

Practice this interactively
Share this lessonWhatsAppLinkedInX

Related lessons