Leer en español · Lire en français · Auf Deutsch lesen
Section 2.4 of 2.8 · Intermediate · 24 min read
Performance and scalability: latency, throughput, percentiles and load
Video lesson
5 min · narrated, with captionsListen only (audio)
Read the transcript
Performance is the quality people mention first and specify worst. In this lesson you'll learn the handful of ideas and formulas that professionals use to get it right.
Picture a supermarket. How long you spend at the till is the latency: how long one piece of work takes. How many shoppers the tills serve in an hour is the throughput: how much work is done per unit of time. A good requirement states both, with the load at which they apply. Your time at the till is partly the scanning, the work itself, and partly the queue in front of you. When the tills are only half busy, you barely queue. When they're almost always busy, queues grow very long very quickly, even though each scan takes the same time.
Software works the same way. The response time a client sees is everything between sending a request and getting the answer. Part of it is waiting in a queue, because the worker is busy with someone else's request. Part of it is the service time: the work itself. Network time is added in both directions. The two parts need different remedies. A faster query cuts service time. Only more capacity or less load cuts waiting time, and under load, waiting usually dominates. A query might take five milliseconds, but wait three hundred for a free connection.
Here's why averages mislead. A search service logs a thousand response times. Nine hundred take eighty milliseconds, eighty take a hundred and fifty, fifteen take one thousand two hundred, and five take four thousand. The mean is a hundred and twenty two milliseconds, which looks fine. Professionals use percentiles instead. The fiftieth percentile, or median, is the value half of requests beat. Here it's eighty milliseconds. The ninety fifth percentile, p ninety five, is the value ninety five per cent of requests beat: a hundred and fifty milliseconds. But p ninety nine is one thousand two hundred milliseconds. And p ninety nine point nine is four thousand milliseconds. So two per cent of users wait more than a second. At three hundred searches a second, that's six people every second. A requirement written as an average would pass this system. One written in percentiles fails it, correctly.
The slow end of the distribution is called the tail. A user who loads forty resources to see one page waits for the slowest of those forty, not the average. So a rare one per cent event affects about a third of page views. In large systems, one request may fan out to a hundred servers and wait for them all. If each is slow one time in a hundred, the user's request is slow about sixty three per cent of the time.
Little's law is the most useful single formula in capacity planning. The number of requests in progress equals the arrival rate multiplied by the time each one spends inside. Let's use it. A ride-hailing trip service handles one thousand eight hundred requests a second at the evening peak. Each request keeps a database connection busy for forty milliseconds. So, on average, one thousand eight hundred times nought point nought four, which is seventy two connections, are busy at once. But six instances with ten connections each give only sixty. Requests will queue for connections, and response times will climb until timeouts start. To keep the pool at most about seventy per cent busy, you need seventy two divided by nought point seven, about a hundred and three connections, or about eighteen per instance.
Why keep that headroom? This curve shows a single-server queue with a service time of twenty milliseconds. Up to fifty per cent busy, the mean response time is just forty milliseconds. At eighty per cent busy, it's a hundred milliseconds. At ninety per cent, it doubles to two hundred. Beyond that, it rises almost vertically. Flat, then a knee, then a cliff. So never plan to run critical resources near full.
Scalability is the ability to handle more load by adding resources. Scaling up gives one machine more power. It's simple, but it runs into hard limits and rising prices. Scaling out adds more machines. There's no fixed ceiling, but the software must be designed for it. And parallelism has limits. Amdahl's law says that if ten per cent of the work must be done one step at a time, no number of processors can make the job more than ten times faster. Worse, keeping data consistent between nodes has a cost that grows quickly, so past some point, adding nodes actually reduces capacity. Look for the hidden serial parts, such as a single lock or a central coordinator.
The full section works through Amdahl's law and the Universal Scalability Law, and lists the main performance tactics. Next, in Section two point five, we look at availability and reliability. See you there.
What you need to know
- Latency is how long one piece of work takes; throughput is how much work is completed per unit of time. A performance requirement should state both, with the load at which they apply.
- Response time is service time plus waiting time (plus network time); under load, waiting time usually dominates.
- Specify and monitor percentiles such as p95 and p99, not averages; in fan-out systems a rare slow response at one server becomes common for users (Dean and Barroso, 2013).
- Little's law, L = λ × W, links request rate, response time and concurrency, and is the basis of sizing pools and limits.
- In a single-server queue, mean response time is S ÷ (1 − ρ), so it doubles between 80% and 90% utilisation; keep critical resources well below saturation.
- Amdahl's law limits speed-up to 1 ÷ s for serial fraction s; the Universal Scalability Law adds coherency costs that make capacity peak and then fall.
Two different questions: how fast, and how much
Performance is the quality people mention first and specify worst. It covers two different questions. Latency asks how long one piece of work takes: how many milliseconds between tapping 'Pay' and seeing 'Payment accepted'. Throughput asks how much work the system completes per unit of time: 1,200 payments per second, 40,000 orders per hour. The two are related but not interchangeable. A coach carrying 50 people from London to Oxford in 100 minutes has worse latency and much better throughput than a sports car carrying two people in 60 minutes.
In software the same tension appears everywhere. Batching database writes improves throughput but makes each individual write wait for its batch. Adding a cache improves both for repeated reads but does nothing for the first. A good performance requirement therefore says which question it is about, and usually states both: 'at 1,200 payments per second (throughput), 99% complete within 800 ms (latency)'. ISO/IEC 25010 files these under performance efficiency as time behaviour, resource utilisation and capacity (Section 2.2).
Response time = service time + waiting time
When you measure latency from the client's side you get the response time: everything that happened between sending the request and receiving the answer. It is useful to split it into parts. The service time is the time a resource actually spends working on your request: parsing it, running the query, rendering the result. The waiting time is the time your request spends in a queue because the resource is busy with someone else's. Network transfer time is added on top, in both directions.
The distinction matters because the two parts respond to different remedies. Service time falls when you make the work itself cheaper: a better index, a smaller payload, a faster algorithm. Waiting time falls when you add capacity or reduce load, and it is often much larger than the service time when a system is busy. Many 'slow database' investigations end with the discovery that each query takes 5 ms but waits 300 ms for a free connection in the pool.
Where the time goes
Percentiles: why averages mislead
Response times are never a single number; they form a distribution, and it is almost always skewed. Most requests are quick, and a minority are slow because they hit a cold cache, a garbage-collection pause, a lock, a retry or an overloaded neighbour. The arithmetic mean blends the two groups into a figure that describes nobody. Professionals use percentiles instead. The 50th percentile (p50, the median) is the value half of requests beat; p95 is the value 95% of requests beat; p99 and p99.9 describe the slowest 1% and 0.1%.
The slow end of the distribution is called the tail, and it matters more than it seems. The users in the tail are often your best customers: the ones with the biggest baskets, the longest histories and the most data. A user who loads 40 resources to view one page experiences the slowest of those 40, not the average. And the chance that a 40-resource page hits at least one p99 request is 1 − 0.9940 ≈ 33%: a 'rare' 1% event affects a third of page views.
Worked example
Worked example: the mean that hides the problem
A product-search API logs 1,000 response times in one minute: 900 at 80 ms, 80 at 150 ms, 15 at 1,200 ms (a cold cache) and 5 at 4,000 ms (a timeout and retry).
| Sum of all times | 900 × 80 + 80 × 150 + 15 × 1,200 + 5 × 4,000 = 72,000 + 12,000 + 18,000 + 20,000 = 122,000 ms |
|---|---|
| Mean | 122,000 ÷ 1,000 = 122 ms. Looks healthy against a 200 ms target. |
| p50 (median) | The 500th fastest request: 80 ms |
| p95 | The 950th fastest request falls inside the 150 ms group (requests 901 to 980): 150 ms |
| p99 | The 990th falls inside the 1,200 ms group (requests 981 to 995): 1,200 ms |
| p99.9 | The 999th is in the 4,000 ms group (996 to 1,000): 4,000 ms |
| Who is affected | 20 requests in 1,000 (2%) took over a second. At 300 searches per second that is 6 users every second, or about 21,600 an hour. |
The mean of 122 ms passes a 200 ms target while 2% of users wait more than a second. A scenario written as 'average below 200 ms' would pass this system.
Writing the requirement as 'p95 ≤ 200 ms and p99 ≤ 500 ms' fails it, correctly, and points the team at the cold-cache and retry paths.
Percentiles cannot be averaged. The p99 across ten servers is not the mean of their ten p99 values; it must be computed from the combined data, or approximated from merged histograms.
# Why the mean misleads: 1,000 response times from one minute of traffic
import statistics
times_ms = [80] * 900 + [150] * 80 + [1200] * 15 + [4000] * 5
mean = statistics.mean(times_ms)
cuts = statistics.quantiles(times_ms, n=1000, method="inclusive")
p50, p95, p99, p999 = cuts[499], cuts[949], cuts[989], cuts[998]
print(f"mean {mean:.0f} ms, p50 {p50:.0f}, p95 {p95:.0f}, p99 {p99:.0f}, p99.9 {p999:.0f}")
# mean 122 ms, p50 80, p95 150, p99 1200, p99.9 4000Tail latency at scale
The tail becomes the main event in large systems that use fan-out, where one user request is split into many parallel requests to other servers and the answer must wait for all of them. A search engine, a social feed or a recommendations page may consult tens or hundreds of back-end shards for every query. Jeffrey Dean and Luiz André Barroso of Google analysed this in The Tail at Scale (Communications of the ACM, 2013).
Their illustration is simple. Suppose each server answers in 10 ms typically but takes 1 second for one request in 100. A user request that waits for one server is slow 1% of the time. A user request that waits for 100 such servers is slow whenever any one of them is slow: 1 − 0.99100 ≈ 63% of the time. A rare event at one server becomes the common case for the user. Dean and Barroso report measurements from a Google service in which the 99th-percentile latency for a single leaf request was 10 ms, but the 99th percentile for waiting on all leaf requests was 140 ms.
Because you cannot eliminate every source of variability (shared hardware, background jobs, garbage collection, network queues), they argue for tail-tolerant designs, much as fault-tolerant designs accept that hardware fails. Their techniques include hedged requests (send a second copy to another replica if the first has not answered within, say, the 95th-percentile time, and use whichever returns first), tied requests that cancel each other, finer-grained partitions that can be rebalanced, temporarily excluding slow machines, and returning a good-enough result when most but not all shards have answered. In one benchmark they describe, hedging after 10 ms cut the 99.9th-percentile latency from 1,800 ms to 74 ms while sending only about 2% more requests.
Little's law: how many requests are in flight
Little's law, proved by John Little in 1961, is the most useful single formula in capacity planning. For any stable system, the average number of items inside it (L) equals the average arrival rate (λ, lambda) multiplied by the average time each item spends inside (W): L = λ × W. It holds regardless of the arrival pattern or the order of service, provided the system is stable (on average, as many items leave as arrive) and you use consistent units.
For software, L is the number of requests in progress at once (the concurrency), λ is the request rate and W is the response time. If a service receives 200 requests per second and each spends 0.25 seconds inside, then on average 200 × 0.25 = 50 requests are in progress at any moment. That number drives thread pools, connection pools, memory and licence counts. The law also works backwards: if you can hold at most 50 requests at once and each takes 0.25 s, you cannot sustain more than 50 ÷ 0.25 = 200 requests per second.
Worked example
Worked example: sizing a database connection pool with Little's law
A ride-hailing app's trip service handles 1,800 requests per second at the evening peak. Each request makes database calls that keep a connection busy for 40 ms in total. Each of the 6 service instances has a pool of 10 connections. Is that enough?
| Arrival rate λ | 1,800 requests per second |
|---|---|
| Time holding a connection W | 40 ms = 0.040 s |
| Connections in use L = λ × W | 1,800 × 0.040 = 72 connections busy on average |
| Connections available | 6 instances × 10 = 60 |
| Verdict | 72 > 60. Requests will queue for connections, waiting time will grow without limit at peak, and response times will climb until timeouts start. |
| Target with headroom | Aim to keep the pool at most about 70% utilised: 72 ÷ 0.7 ≈ 103 connections, so about 18 per instance |
| Check the database | 103 connections must also be acceptable to the database server; if not, reduce W (faster queries, fewer round trips) or the number of database calls per request |
| What if queries get faster? | Cutting W to 25 ms gives L = 1,800 × 0.025 = 45 connections, which fits in the existing 60 at 75% utilisation |
Little's law converts a throughput target and a latency into a concurrency figure that can be compared directly with a configured limit.
It gives averages. Bursts push the instantaneous number higher, which is why the example adds headroom rather than sizing to exactly 72.
Utilisation and queueing: why systems fall off a cliff
Utilisation (ρ, rho) is the fraction of time a resource is busy. A server that receives 40 requests per second, each needing 20 ms of service, is busy 40 × 0.020 = 0.8 of the time: 80% utilised. Intuition suggests that a resource 80% busy should be only a little slower than one 50% busy. Queueing theory says otherwise, because requests do not arrive evenly; they bunch together, and when they bunch, the later ones wait.
The simplest model is the M/M/1 queue: random arrivals, random service times and a single server. Its mean response time is W = S ÷ (1 − ρ), where S is the mean service time. At 50% utilisation, W = 2S. At 80%, W = 5S. At 90%, W = 10S. At 99%, W = 100S. The denominator shrinks towards zero as utilisation approaches 100%, so response time grows without limit. Real systems are not exactly M/M/1, and multi-server pools behave better at the same utilisation, but the shape is universal: flat, then a knee, then a cliff.
M/M/1 queue: mean response time for a 20 ms service time
| Utilisation ρ | W = S ÷ (1 − ρ) | Mean response time | Of which waiting |
|---|---|---|---|
| 50% | 20 ÷ 0.5 | 40 ms | 20 ms |
| 70% | 20 ÷ 0.3 | 67 ms | 47 ms |
| 80% | 20 ÷ 0.2 | 100 ms | 80 ms |
| 90% | 20 ÷ 0.1 | 200 ms | 180 ms |
| 95% | 20 ÷ 0.05 | 400 ms | 380 ms |
| 99% | 20 ÷ 0.01 | 2,000 ms | 1,980 ms |
The utilisation cliff
The practical lessons are direct. Do not plan to run critical resources near 100% busy; leave headroom so that bursts do not push you over the knee. Watch utilisation and queue length together (Brendan Gregg's USE method checks utilisation, saturation and errors for every resource). And remember that the cliff makes small changes dangerous: at 90% utilisation, a rise in traffic of only about 6% takes you to 95% and doubles the mean response time. Chapter 9 covers queues, back pressure and capacity planning in Asynchrony, queues and back pressure.
Scalability: load, elasticity, scaling up and out
Scalability is the ability to handle more load by adding resources, ideally with capacity growing in proportion and without redesign. It only means something once you say what 'load' is. Load parameters differ between systems: requests per second for an API, concurrent connections for a chat service, the ratio of reads to writes for a social network, data volume for an analytics platform, or the number of followers of the most-followed account for a feed. A scalability scenario names the load parameter, the growth, and the response that must be preserved: 'when daily orders grow from 50,000 to 500,000, p95 checkout latency stays below 1.5 s by adding instances, with cost per order no more than 20% higher'.
Elasticity adds time: the ability to add and remove capacity automatically as load rises and falls, so that a ticketing site can grow for a sale and shrink afterwards. It is a large part of the economic case for cloud platforms. There are two basic ways to add capacity. Vertical scaling (scaling up) gives one machine more CPU, memory or faster storage; it is simple, needs no change to the software, and runs into hard limits and rising prices. Horizontal scaling (scaling out) adds more machines and spreads the work; it has no fixed ceiling but requires the software to be designed for it, with stateless instances, load balancing and partitioned data. Chapter 9 covers the strategies in detail in Scaling strategies: vertical, horizontal and the scale cube.
Amdahl's law: the limit of parallelism
Adding processors or nodes only helps with work that can be done in parallel. Amdahl's law, from Gene Amdahl's 1967 paper on the limits of parallel computing, captures this. If a fraction s of the work is serial (it must be done one step at a time) and the rest, 1 − s, can be split perfectly across N processors, the best possible speed-up is speed-up(N) = 1 ÷ (s + (1 − s) ÷ N). As N grows without limit, the speed-up approaches 1 ÷ s. With 10% serial work, no number of processors can make the job more than ten times faster.
Worked example
Worked example: a nightly billing job
A telecoms company's nightly billing run takes 8 hours on one server. Profiling shows 10% of the time is serial (loading tariffs, then writing a single summary file); the other 90% rates calls independently per customer. The run must finish in under 1.5 hours. How many servers are needed?
| Serial fraction s | 0.10; parallel fraction 0.90 |
|---|---|
| Speed-up needed | 8 h ÷ 1.5 h ≈ 5.33 |
| N = 4 | 1 ÷ (0.10 + 0.90 ÷ 4) = 1 ÷ 0.325 ≈ 3.08; run time 8 ÷ 3.08 ≈ 2.6 h |
| N = 8 | 1 ÷ (0.10 + 0.1125) = 1 ÷ 0.2125 ≈ 4.71; run time ≈ 1.70 h |
| N = 16 | 1 ÷ (0.10 + 0.05625) = 1 ÷ 0.15625 = 6.4; run time = 1.25 h |
| Smallest N that meets the target | Need 0.10 + 0.90 ÷ N ≤ 1 ÷ 5.33 = 0.1875, so N ≥ 0.90 ÷ 0.0875 ≈ 10.3: 11 servers give 1 ÷ (0.10 + 0.0818) ≈ 5.50, run time ≈ 1.45 h |
| N = 64 | 1 ÷ (0.10 + 0.0141) ≈ 8.77; run time ≈ 0.91 h |
| Limit as N grows | 1 ÷ 0.10 = 10; run time never below 0.8 h |
| Alternative | Halve the serial part (s = 0.05), for example by loading tariffs in parallel. Then 8 servers give 1 ÷ (0.05 + 0.11875) ≈ 5.93, run time ≈ 1.35 h: the target met with 8 servers instead of 11. |
Going from 16 to 64 servers (four times the cost) cuts the run only from 1.25 h to 0.91 h. Returns diminish quickly.
Reducing the serial fraction is often far cheaper than adding hardware. Architects look for the hidden serial parts: a single lock, one database row everyone updates, a central coordinator.
The Universal Scalability Law
Amdahl's law assumes that extra processors never make things worse; the speed-up just levels off. Real distributed systems often do get worse: past some point, adding nodes reduces throughput. Neil Gunther's Universal Scalability Law (USL) models this with two penalties. Contention (α, alpha) is the cost of queueing for shared resources, the same effect as Amdahl's serial fraction. Coherency (β, beta) is the cost of keeping data consistent between nodes: every node must exchange information with every other, so this cost grows roughly with the square of the number of nodes.
The relative capacity is C(N) = N ÷ (1 + α(N − 1) + βN(N − 1)). With β = 0 it reduces to Amdahl's law. With any β above zero, capacity reaches a peak at about N = √((1 − α) ÷ β) and then falls. For α = 0.03 and β = 0.002, the peak is near √(0.97 ÷ 0.002) ≈ 22 nodes, at a capacity of about 8.6 times one node; at 32 nodes capacity has dropped back to about 8.2. Gunther describes the model in Guerrilla Capacity Planning (2007). Its practical value is that α and β can be fitted to a handful of load-test measurements, which lets you predict where scaling will stop paying off before you buy the hardware.
Linear, Amdahl and USL scaling
Tactics for performance: an overview
Bass, Clements and Kazman group performance tactics into two families. The first is to control resource demand: reduce how much work arrives or how much each piece costs. The second is to manage resources: make the available resources work harder or add more of them. Most real designs combine several tactics; each also has costs elsewhere, which is why they are architectural decisions.
Performance tactics and where the course covers them
| Tactic | What it does | Typical cost or risk | More in |
|---|---|---|---|
| Reduce demand: rate limiting, admission control, waiting rooms | Caps or smooths incoming load so the core stays below its knee | Some users are delayed or refused | Chapter 9: queues and back pressure |
| Reduce work per request: efficient algorithms, smaller payloads, fewer round trips | Cuts service time | Developer effort; sometimes less flexible code | Chapter 0: data and algorithms |
| Prioritise and schedule | Serves important work first (paid orders before reports) | Low-priority work may starve | Chapter 9: resilience patterns |
| Caching | Keeps results close to the user to avoid repeated work | Stale data, invalidation complexity, memory cost | Chapter 7: caching |
| Concurrency and asynchrony | Processes independent work in parallel; takes slow work off the request path | Harder reasoning, ordering and consistency issues | Chapter 6: communication |
| Replication | Adds copies of services or data to spread reads and load | Consistency between copies; cost | Chapter 7: replication and partitioning |
| Partitioning (sharding) | Splits data and work so each node handles a share | Cross-partition queries and rebalancing are hard | Chapter 7: replication and partitioning |
| Add resources: scale up or out | Raises capacity | Cost; limited by serial and coherency work | Chapter 9: scaling strategies |
Check your understanding
A service receives 500 requests per second and each takes 80 ms. How many are in progress on average?
By Little's law, L = 500 × 0.08 = 40 requests in progress.
Using the M/M/1 formula, what is the mean response time of a server with a 10 ms service time at 75% utilisation?
W = 10 ÷ (1 − 0.75) = 40 ms, of which 30 ms is waiting.
Why is p99 usually a better requirement measure than the mean?
Response-time distributions are skewed; the mean can look healthy while a meaningful share of users suffer very slow responses, and p99 exposes that tail.
If 20% of a job is serial, what is the maximum speed-up from adding processors?
1 ÷ 0.2 = 5 times, however many processors are added.
Sources and further reading
Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.
Ask: “Our API averages 150 ms but users still complain it is slow. How should I measure and set targets for its performance?”