infrastructure/percentiles-why-an-average-response-time-of-235-ms-can-hide-users-wait.md

Percentiles: why an average response time of 235 ms can hide users waiting 3 seconds

Infrastructure · 4 min read ·

Your dashboard says the average response time is 235 milliseconds, yet some users wait three seconds. Both can be true, because an average hides the slow requests.

Click squares to slow down requests and watch the mean, median, p95 and p99 react differently. Then run the same experiment in Python and learn which number to ask for next time.

100 small squares representing requests: 96 green fast ones and 4 orange slow ones, beside mean, p50, p95 and p99 values

Your monitoring dashboard says the average response time of your website is 235 milliseconds. That sounds fine. Yet a few users keep writing in to say the site "hangs for three seconds." Both things can be true at once, and the reason is the most useful idea in monitoring: an average hides the slow requests, and the slow requests are the ones people remember.

What an average does to a bad day

A request is one visit to a server: your browser asks for a page and waits. The time it waits is the latency, usually measured in milliseconds (ms, a thousandth of a second).

Imagine 100 requests. Ninety-six of them take between 90 and 150 ms. Four of them get stuck behind a slow database query and take 3,000 ms each. Add everything up and divide by 100, and you get an average (also called the mean) of about 235 ms. Not one request actually took 235 ms. The number describes nobody's real experience: it is too slow for the 96 happy users and far too fast for the 4 unhappy ones.

Percentiles: sort first, then look at a position

A percentile answers a different question: "what latency did most requests beat?" To find it, sort all the requests from fastest to slowest and look at a position in the line.

  • p50 (the median) is the middle of the line: half the requests were faster, half slower. It describes the typical visit.
  • p95 is the value 95% of the requests were at or under. Five in every hundred were slower than this.
  • p99 is the same idea at 99%: only one in a hundred was slower. It shows what your unluckiest regular users see.

Teams often write alerts and promises (a "service level objective") using these numbers, such as "p95 under 300 ms", because they track what users feel much better than a mean does.

Try it: click a request to make it slow

Below are 100 requests, one square each. Green ones take 90 to 150 ms. Click any square to make it a 3,000 ms request (it turns orange), and click again to fix it. Watch which numbers move, and which ones stay put.

Two things are worth noticing. First, the median barely moves, even with 20 slow requests: it only shifts a lot once slow requests are a large share of the line. Second, a single slow request moves the mean by about 30 ms but does not touch p95 or p99 at all. With 100 requests, p99 is the 99th value in the sorted line, and one slow request sits at position 100. That is why dashboards usually show the slowest value (the max) next to the percentiles.

The same thing in Python

Here is the experiment from the top of the page. We build 100 latencies, make four of them slow, then compute the mean and three percentiles. math.ceil rounds up to a whole number, which gives us the position in the sorted line.

import math

# 100 requests: most take 90-150 ms, four take 3000 ms
times = [90 + (i * 37) % 61 for i in range(100)]
for i in (7, 31, 58, 90):
    times[i] = 3000

def percentile(values, p):
    ordered = sorted(values)
    rank = math.ceil(p / 100 * len(ordered))   # nearest-rank method
    return ordered[rank - 1]

print("mean", sum(times) / len(times))
print("p50 ", percentile(times, 50))
print("p95 ", percentile(times, 95))
print("p99 ", percentile(times, 99))

Running it prints:

mean 234.85
p50  120
p95  150
p99  3000

The mean says 235, p50 and p95 say the site is quick, and only p99 notices the four bad requests. Each number answers a different question, which is why people read them together.

One more wrinkle: different tools, slightly different answers

The nearest-rank method above always returns a latency that really happened. Other tools, such as NumPy's percentile function or a monitoring system, may blend two neighbouring values (this is called interpolation), so the same data can give a slightly different p95. They agree on the idea; they differ at the edges. When you compare numbers between two dashboards, check how each one computes them.

What to take into your own code

When someone tells you "the average response time is fine", ask for the p95 and the p99. When you measure your own program, collect every timing and sort them rather than keeping only a running total. And if one in a hundred of your requests is slow, remember that a visitor who loads a page with 30 files makes 30 requests, so a rare slow case is not so rare for them. Most monitoring tools can chart p50, p95 and p99 for exactly this reason.

$ ls infrastructure/

see all →
infrastructure/exponential-backoff-why-waiting-longer-between-retries-keeps-a-struggl.md Exponential backoff: why waiting longer between retries keeps a struggling server alive When a server stumbles, every client that failed retries at the same instant, and the second wave can be bigger than the first. Exponential… 5 min read · 27 Oct infrastructure/environment-variables-how-the-same-code-talks-to-a-practice-database-o.md Environment variables: how the same code talks to a practice database on your laptop and the real one online The same program can talk to a practice database on your laptop and the real one on a live website, without a single edit to its code. The… 5 min read · 24 Oct infrastructure/ci-pipelines-how-a-computer-decides-a-change-is-safe-to-merge.md CI pipelines: how a computer decides a change is safe to merge Push a change and a green tick or a red cross appears a minute later. How does a machine decide? It does not understand your code. It reads… 5 min read · 18 Oct infrastructure/rolling-deploys-how-a-website-updates-without-going-offline.md Rolling deploys: how a website updates without going offline How does a busy website switch to a new version without going offline? Not by replacing everything at once. A rolling deploy updates one… 5 min read · 15 Oct infrastructure/cron-schedules-five-fields-that-tell-a-computer-when-to-run-a-job.md Cron schedules: five fields that tell a computer when to run a job Want a computer to run a job at 3 a.m. while you sleep? On Linux and macOS, a program called cron does it, and each job is described by… 5 min read · 12 Oct infrastructure/docker-build-cache-why-a-one-line-change-can-trigger-a-full-reinstall.md Docker build cache: why a one-line change can trigger a full reinstall You change one line of code, rebuild your Docker image, and wait while every library downloads again. Often the cause is just the order of… 4 min read · 8 Oct