lumo

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

  1. Distinguish request latency from completed throughput.
  2. Interpret p50, p95 and p99 without hiding slow requests.
  3. Calculate a request’s latency budget across sequential and parallel work.
  4. Explain why queues grow harder to clear near full utilization.
  1. Server concurrency: handling many requests at once15 min

Video 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.

Check yourselfAnswer in your head first, then reveal
Kartly completes 1,000 requests during one second. Must each request have taken 1 ms?Show answerHide answer
No. Those requests can overlap, and some may have started before that second. Completions per second do not tell you how long an individual request waited.

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:

One average describes the whole sample, but it does not describe the slow customers’ experience.

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.

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.

Parallel work shortens elapsed time only when neither operation needs the other’s result.

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.

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

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

Check yourselfAnswer in your head first, then reveal
Kartly receives 1,200 requests each second but completes 900. No requests are rejected or abandoned. What happens to unfinished work?Show answerHide answer
It grows by 300 requests each second. Throughput is 900 completions per second, not the arrival rate, and the extra unfinished work must remain somewhere.
Check yourselfAnswer in your head first, then reveal
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 answerHide answer
No. The measurements expose a slower group, and acceptability depends on the product’s target. Neither percentile tells you the maximum wait.
Check yourselfAnswer in your head first, then reveal
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 answerHide answer
About 80 ms: wait for the slower 70 ms call, then build the response. Not 120 ms, because the independent waits overlap.
Check yourselfAnswer in your head first, then reveal
Checkout throughput stays flat as concurrency increases, but p99 rises. Should you immediately allow more requests in progress?Show answerHide answer
No. That pattern suggests more waiting without more completions. Inspect queues and constrained resources before increasing concurrency.

Summary

  1. Healthy-looking measurements can hide stuck customers: latency measures individual elapsed time, while throughput measures completions.
  2. Use p50, p95 and p99 with request counts and outcomes; an average can hide seconds of waiting.
  3. Sequential durations add; independent parallel work waits for the slowest required branch, plus overhead.
  4. Fan-out exposes a response to more slow cases. Measure complete requests rather than adding component percentiles.
  5. Near full utilization, little recovery room remains. Judge improvements by successful completions and customer waiting together.

Report a mistake

Tell us what’s wrong in this lesson and we’ll fix it.