Skip to content
16px
Distributed SystemsPerformanceLatencyBackendReliability

When p99 Becomes the Normal User Experience

Why tail latency that looks rare on one backend becomes common when a request fans out across dozens or hundreds of servers, and how hedged requests help.

August 30, 20263 min read

A service can look perfectly healthy in isolation and still feel slow to users.

Assume one backend has:

  • normal latency: ~10ms
  • p99 latency: 1s

That sounds acceptable. Only 1% of requests are slow.

Now imagine one user request fans out to 100 backend servers in parallel and cannot finish until every response comes back.

The probability that all 100 servers respond inside their p99 is:

0.99^100 ≈ 0.366

Which means the probability that at least one server is slow is:

1 - 0.99^100 ≈ 63%

So a latency event that happens only 1% of the time on one machine now affects roughly 63% of user requests.

This is the core idea behind tail latency at scale.

Why fan-out makes tail latency worse

In distributed systems, latency is often determined by the slowest dependency.

Consider an API that does this:

request
   |
   +--> shard 1
   +--> shard 2
   +--> shard 3
   |
   ...
   |
   +--> shard 100

wait for all responses

Ninety-nine servers may respond in 10ms. If one server takes 1 second, the entire request takes 1 second.

The problem is not necessarily average latency. The problem is the probability that one component becomes a straggler. As fan-out increases, encountering a straggler becomes increasingly likely.

Hedged requests

One mitigation described in Google's The Tail at Scale paper is hedged requests.

Instead of immediately sending every request twice, you wait for some latency threshold:

send request -> replica A

wait 50ms

if response arrived:
    return it

otherwise:
    send same request -> replica B

take whichever response finishes first
cancel the other

The important part is the delay. If most requests finish quickly, no duplicate is sent. Only requests entering the latency tail create additional work.

You are not making the slow server faster. You are simply giving yourself another chance to avoid waiting for it.

What this looks like in Go

Conceptually, the client behaves like this:

go
1go request(replicaA)
2
3select {
4case result := <-response:
5    return result
6
7case <-time.After(hedgeDelay):
8    go request(replicaB)
9}
10
11return <-firstSuccessfulResponse

Production implementations need more care around cancellation, retries, timeouts, idempotency, and connection limits, but the idea is surprisingly small.

The trade-off

Hedging is not free. Duplicate requests increase backend traffic. If you hedge aggressively, a latency problem can turn into a load problem:

slow backend
   ->
more hedged requests
   ->
more backend load
   ->
backend becomes slower
   ->
even more hedging

That is why systems normally hedge only after something like the p95 or p99 latency threshold rather than duplicating every request immediately.

Hedging also works best for operations that are safe to repeat, particularly reads or properly idempotent requests.

The larger lesson

Distributed systems change probability.

A failure mode that looks rare on one machine may become normal once a request depends on dozens or hundreds of machines. That applies to more than latency:

more dependencies
    ->
more chances for one dependency to misbehave
    ->
worse end-to-end reliability

So when designing highly parallel systems, don't only ask:

What is the latency of one server?

Ask:

What happens to the user when their request depends on 100 of them?

That difference is where tail latency becomes a systems problem.

Bhupesh Kumar

Bhupesh Kumar

Backend engineer building scalable APIs and distributed systems with Node.js, TypeScript, and Go.