Module 01 · Lesson 4 of 14Free
Latency and throughput: measuring speed and capacity
- Level
- Beginner
- Topic
- System design fundamentals
- Reading time
- 14 min
When you finish this page you can
- Distinguish request latency from completed throughput.
- Interpret p50, p95 and p99 without hiding slow requests.
- Calculate a request’s latency budget across sequential and parallel work.
- Explain why queues grow harder to clear near full utilization.
Builds on 1 lesson
Start with lesson 315 minVideo lesson · coming soon
During ’s festival sale, traffic jumps to 1,000 requests a second. The dashboard shows a product-page average of 138 ms. Yet Asha’s checkout spinner stays visible for 2 seconds.
Kartly still has one app server with 8 CPU cores and one PostgreSQL database. How can its speed measurements look healthy when a customer is stuck?
One request and many completions
is the elapsed time for one request, measured between a stated start and finish. is the number of requests completed per unit of time. A service can have high throughput and still have slow individual requests.1
For this lesson, server latency runs from request arrival to response completion. It includes waiting inside the server. The customer’s experience also includes network travel and browser work, outside this boundary.
Kartly completes 1,000 requests during one second. Must each request have taken 1 ms?Show answer
As Server concurrency showed, overlapping requests let a server work during other requests’ waits.
Suppose an illustrative sale window contains these completed product-page requests:
The average looks close to the fast group because that group is much larger. We need a measurement that exposes the slow group before choosing what to improve.
Averages hide the waiting customers
These are invented samples, not Kartly production measurements:
| Completed requests | Latency each |
|---|---|
| 960 | 60 ms |
| 30 | 1,000 ms |
| 10 | 5,000 ms |
Add the elapsed times
960 × 60 ms + 30 × 1,000 ms + 10 × 5,000 ms = 137,600 ms
Even a small group contributes substantial waiting time.
Calculate the mean
137,600 ms ÷ 1,000 requests = 137.6 ms per request
Rounded, the dashboard reports 138 ms. No request in this sample actually took that long.
A locates a position in sorted measurements. For this example, use the nearest-rank rule: multiply the sample size by the percentile fraction, round up, then read that position.
| Measurement | Sorted position | Result |
|---|---|---|
| p50 | 500th | 60 ms |
| p95 | 950th | 60 ms |
| p99 | 990th | 1,000 ms |
At least 99% of these observations finished within the p99 value. It is not the maximum: the last ten requests took longer.
The slow end of a distribution is called . Averages alone can hide it; recording latency distributions makes it visible.1
Which latency measurement should Kartly put beside throughput?
Show only the average.
It is easy to compare releases, but the large fast group can hide customers waiting seconds.
Show p50, p95 and p99, plus the sample count.
The typical experience stays visible, and the slower groups get their own measurements. Request counts give those measurements context.
Why this choice
Kartly needs to detect both widespread slowness and a smaller group of stuck customers. Keep the average as supporting information, not the only signal.
For a tiny sample, show individual durations too. A p99 based on a handful of completions cannot describe a stable pattern.
Keep product-page and checkout measurements separate. A large stream of quick product reads must not hide a smaller stream of slow purchases.
Percentiles reveal who is waiting. They do not yet reveal where the time went.
The dependency path sets the budget
A divides an allowed elapsed time among the work needed for a response.
Use Kartly’s earlier product-page example:
- Initial CPU work takes 2 ms.
- The database call takes 40 ms.
- The pricing call takes 50 ms.
- Building the response takes 3 ms.
These remain illustrative durations. For one request, sequential operations add. Independent operations started together overlap; waiting for all of them takes roughly the longest duration, plus coordination overhead.
Diagram not drawn yet: kartly-sequential-parallel-budget.svg
Two timing panels. Sequential: 2 ms of initial work, a 40 ms database call, a 50 ms pricing call and 3 ms of response work total 95 ms. Parallel: after initial work, both independent calls start together; response work follows the 50 ms pricing reply, totaling 55 ms before coordination overhead.
Keep the calls sequential
2 ms + 40 ms + 50 ms + 3 ms = 95 ms
The pricing request starts after the database reply.
Overlap independent calls
2 ms + max(40 ms, 50 ms) + 3 ms = 55 ms
This result assumes both calls can start immediately and their durations do not increase under the extra overlap.
The longest required chain is the path that determines completion. Making the 40 ms call faster would not improve this parallel example until it changes that path.
Should Kartly start the database and pricing calls together?
Keep the calls sequential.
The code follows a simple order and avoids unnecessary pricing calls when the database lookup fails, but elapsed waits add.
Start independent calls together.
Elapsed time falls when the calls are independent. Both dependencies receive work immediately, including work that may become unnecessary.
Why this choice
Choose parallel calls only if the incoming request already contains everything pricing needs. If pricing requires a seller ID returned by PostgreSQL, keep that dependency sequential.
Reconsider parallel execution if simultaneous calls overload a shared resource. A shorter does not create more CPU or database capacity.
For an illustrative checkout budget, reserve 300 ms inside the server:
| Required phase | Allowance |
|---|---|
| Validate the request | 10 ms |
| Check current prices and stock | 70 ms |
| Authorize payment | 150 ms |
| Save and build the response | 30 ms |
| Unallocated margin | 40 ms |
These are allowances, not measured percentiles or promises. Required sequential phases total 260 ms, leaving the margin shown above.
Do not give every downstream call the full request budget. That would let several sequential calls each consume time the customer does not have.
For a request that must finish within its budget, record elapsed time before starting each phase. Compare the remaining time with the work still required. A payment must not be presented as proof that no payment happened; safe retry behavior belongs in Idempotency.
Even independent calls introduce another problem: waiting for all of them exposes you to every call’s slow cases.
More branches expose more slow cases
means one incoming request starts several downstream operations. If every result is required, one slow operation can delay the entire response.
Large systems can turn occasional component delays into frequent user-visible delays. That is the problem described by The Tail at Scale.3
Consider a hypothetical Kartly product-page redesign. It waits for ten independent lookups. Assume each lookup has a 1% chance of exceeding 200 ms.
Find the chance that every lookup is fast
0.99¹⁰ ≈ 0.904 = 90.4%
Independence is an assumption for this calculation, not a guarantee about real dependencies.
Find the chance of at least one slow lookup
1 − 0.904 ≈ 0.096 = 9.6%
Almost one in ten pages waits beyond the threshold, despite each lookup usually meeting it.
A shared database or network can make these delays correlated. Then the independence calculation no longer applies.
For Kartly, an optional recommendation should not hold up the product’s price and purchase button. Keep required price and stock checks on the response path. Treat optional content separately when the product permits it.
Do not add downstream p99 values and call the result the page’s p99. Different calls can have their slowest observations on different requests. Measure complete requests to obtain the end-to-end percentile.
Reducing the number of required waits helps, but every remaining operation still competes for finite resources.
Nearly full leaves little recovery room
is the fraction of a resource’s available time spent busy. High utilization leaves fewer idle intervals to absorb uneven arrivals or unusually long work.
Latency can deteriorate before a resource reaches full utilization. Watch the constrained resource, not only the machine’s overall CPU percentage.1
Here is a deliberately simplified model, not a capacity estimate for Kartly. Imagine one busy resource that can complete 100 identical operations per second. An extra burst adds 20 operations.
Assume arrivals and completions behave as smooth rates during recovery.
Recover at half utilization
20 operations ÷ (100 − 50) operations/s = 0.4 s
Ordinary traffic leaves substantial room to remove the extra work.
Recover at ninety percent utilization
20 operations ÷ (100 − 90) operations/s = 2 s
The same burst now takes much longer to clear.
Recover at ninety-nine percent utilization
20 operations ÷ (100 − 99) operations/s = 20 s
Another burst can arrive before the previous one clears.
These are backlog recovery times, not individual request latencies. They show why small increases near full utilization have large consequences.
Requests arriving during recovery wait in a behind unfinished work. Their elapsed time includes that wait. If arrivals keep exceeding completions, the backlog cannot clear under these assumptions.
This is why “keep everything 100% busy” is a poor goal for an interactive checkout. Real arrivals are uneven. Without recovery room, one delay affects later customers.
Throughput may initially rise as Kartly accepts more concurrent work. Near a , completions stop rising proportionally, but waiting continues to increase. Request cost also matters: a checkout and a product read need not consume equal resources.2
The reverse tradeoff appears with . Processing several operations together can share setup work and improve throughput. Waiting to collect that batch adds latency before any operation starts. Choose that delay for background work only when its completion requirements permit it.
The useful operating point therefore depends on acceptable customer waiting, not the largest completion count alone.
Measure outcomes before declaring improvement
For Kartly’s next sale rehearsal, use the same request mix across runs. Increase arrivals gradually and compare:
| Measurement | Question it answers |
|---|---|
| Arrivals per second | How much work did clients offer? |
| Successful completions per second | How much useful work finished? |
| Errors and timeouts | Which attempts did not finish successfully? |
| Per-operation | Who waited, and for which action? |
| Queue wait and dependency durations | Where did elapsed time accumulate? |
Keep successful and failed response latencies distinct. Fast errors can otherwise make a broken service appear faster.1
If checkout slows, compare its complete duration with time spent waiting and time inside each dependency. Optimize the part that explains the extra elapsed time, rather than assuming the database is responsible.
After a mitigation, require both recovered latency and successful completions. A shorter queue caused by rejecting every customer is not a successful checkout recovery.
Where this shows up
- Design: a food delivery API: freshness targets and repeated status requests create competing demands.
- Design: a fast product catalog page: required dependencies determine when a page can respond.
Kartly now needs to decide which checkout actions must work, and how fast they must finish: the questions in Functional and non-functional requirements.
Common mistakes
- Calling arrivals throughput. Receiving work does not mean completing it.
- Treating p99 as the maximum. Some observations can take longer.
- Averaging percentiles across windows. Combine the underlying observations or distribution counts, then calculate the percentile.
- Parallelizing dependent work. Starting early cannot produce an input that does not exist yet.
- Reading latency without outcomes. A fast failure is still a failed purchase.
Check your understanding
Kartly receives 1,200 requests each second but completes 900. No requests are rejected or abandoned. What happens to unfinished work?Show answer
A dashboard shows p95 of 80 ms and p99 of 2 seconds for product pages. Can you conclude that nearly everyone has an acceptable experience?Show answer
Two independent calls take 40 ms and 70 ms. Response construction takes another 10 ms. What is the parallel path’s duration, ignoring overhead?Show answer
Checkout throughput stays flat as concurrency increases, but p99 rises. Should you immediately allow more requests in progress?Show answer
Summary
- Healthy-looking measurements can hide stuck customers: latency measures individual elapsed time, while throughput measures completions.
- Use p50, p95 and p99 with request counts and outcomes; an average can hide seconds of waiting.
- Sequential durations add; independent parallel work waits for the slowest required branch, plus overhead.
- Fan-out exposes a response to more slow cases. Measure complete requests rather than adding component percentiles.
- Near full utilization, little recovery room remains. Judge improvements by successful completions and customer waiting together.