AnyLearn
All lessons
Programmingintermediate

Percentiles and Cardinality: The Two Numbers That Run Your Bill

Two pieces of arithmetic decide whether your telemetry is useful and affordable. Percentiles, because averages hide exactly the users who are suffering, and you cannot average a p99. Cardinality, because metric cost is not per event but per label combination, and one careless label can multiply your bill by the size of your user base. This lesson does both mechanisms by hand.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 7

The average is a hiding place

Latency data is brutally skewed. Most requests are fast, a few are slow, and a handful are catastrophically slow. Averaging that shape produces a number that describes nobody.

Take ten requests: nine complete in 100 ms, one takes 4 seconds. The average is 490 ms, a latency no actual request experienced. Meanwhile the summary "average 490 ms" both understates the disaster, someone waited 4 seconds, and slanders the healthy majority, who got 100 ms.

Percentiles describe the distribution instead of collapsing it. The p50, the median, is the experience of the typical request. The p95 and p99 are the experience of the unluckiest 5 and 1 percent. "p50 of 100 ms, p99 of 4 s" tells the true story the average buried.

Key idea: the tail is not an edge case; it is your best customers. The users who hit p99 most often are the ones making the most requests, with the fullest carts and the biggest accounts. High-traffic pages compose many backend calls, so one slow dependency in a hundred touches almost every page load. Amazon and Google engineers have written for years about tail latency for exactly this reason.

Full lesson text

All 7 steps on one page, for reading, reference, and search.

Show

1. The average is a hiding place

Latency data is brutally skewed. Most requests are fast, a few are slow, and a handful are catastrophically slow. Averaging that shape produces a number that describes nobody.

Take ten requests: nine complete in 100 ms, one takes 4 seconds. The average is 490 ms, a latency no actual request experienced. Meanwhile the summary "average 490 ms" both understates the disaster, someone waited 4 seconds, and slanders the healthy majority, who got 100 ms.

Percentiles describe the distribution instead of collapsing it. The p50, the median, is the experience of the typical request. The p95 and p99 are the experience of the unluckiest 5 and 1 percent. "p50 of 100 ms, p99 of 4 s" tells the true story the average buried.

Key idea: the tail is not an edge case; it is your best customers. The users who hit p99 most often are the ones making the most requests, with the fullest carts and the biggest accounts. High-traffic pages compose many backend calls, so one slow dependency in a hundred touches almost every page load. Amazon and Google engineers have written for years about tail latency for exactly this reason.

2. Why you cannot average percentiles

The most common percentile mistake is not measurement but arithmetic done afterwards.

Predict first

Service A reports a p99 of 100 ms. Service B reports a p99 of 300 ms. A dashboard shows their combined p99 as the average, 200 ms. What is wrong?

The correct machinery is the histogram: count observations into latency buckets, which can be summed across services, hosts and time windows, because counts add even though percentiles do not. Then compute the percentile once, at query time, from the merged buckets. This is precisely why Prometheus-style systems ship histograms and compute quantiles in the query language rather than letting services report their own p99s upward.

3. What a series actually is

To understand metric cost you need one definition exactly right.

Definition: a time series is one uniquely-labelled stream of values: the metric name plus one specific combination of label values. http_requests_total{route="/checkout", method="POST", status="500"} is one series. Change any label value and you have a different series.

The storage engine keeps every series it has ever seen in the retention window: an index entry, an in-memory presence, compressed chunks on disk. The number of concurrent series, the cardinality, is the real capacity dimension of a metrics system, the thing vendor pricing tiers and self-hosted memory limits are actually measuring.

The multiplication is where intuition fails. Cardinality is the product of each label's distinct values:

series=ilabeli\text{series} = \prod_{i} \lvert \text{label}_i \rvert

A metric with 30 routes, 5 methods, 8 status codes and 50 pods is not 93 of anything: it is up to 30 times 5 times 8 times 50, which is 60,000 series, for one metric name. Add one more label and you multiply again. This is why cardinality problems arrive suddenly: growth is multiplicative, and each individual label looked innocent.

4. The label that ends the party

Every observability team has a version of this story, so it is worth running the arithmetic before living it.

Predict first

The metric above sits at 60,000 series. A well-meaning engineer adds a user_id label to correlate errors with customers. The service has 100,000 active users. What is the new ceiling?

The rule that falls out: label values must come from small, closed sets you can enumerate. Routes, methods, status classes, regions, yes. Anything unbounded belongs in logs and traces, which are built to carry per-event identity, and the correlation the engineer wanted is exactly what exemplar links from histogram buckets to traces exist for.

5. Budgeting cardinality like an engineer

Cardinality is manageable the way any resource is: with a budget, an audit, and controls at the border.

PracticeWhat it does
Label allow-listsInstrumentation review rejects labels that are not from closed sets
Top-k cardinality auditRegularly list the biggest metrics by series count; the top ten usually hold 80 percent of the waste
Relabeling at the pipelineThe collector drops or rewrites offending labels before storage, a stopgap that buys time
Route templating/users/123/orders must be recorded as /users/:id/orders, or the URL space becomes an unbounded label
Recording rulesPre-aggregate expensive queries into new cheap series, trading a little storage for query speed

Two of these deserve emphasis. Route templating is non-negotiable and easy to get wrong in one forgotten handler, one 404 logger recording raw paths has melted many a metrics cluster. And the audit matters because cardinality regressions ship silently: a deploy adds a label, nothing breaks that day, and the series count compounds with every new deployment target until the bill arrives. Treat series count as a metric about your metrics, alert on its growth rate, and the explosion becomes a code review comment instead of an incident.

6. Histogram buckets: where percentiles meet cardinality

The two halves of this lesson collide inside the histogram, because buckets are labels too.

A classic Prometheus histogram stores one series per bucket boundary, per label combination. Give a latency histogram 12 buckets and attach it to the 60,000-series metric from earlier and you are storing 720,000 series before anyone asks a question. Choosing bucket boundaries is therefore a real design act: too few buckets and your p99 estimate is coarse, since classic histograms interpolate within buckets; too many and you multiplied your whole cardinality by the bucket count.

Practical guidance that has aged well:

  • Spend buckets where decisions live: near your SLO thresholds. An SLO at 300 ms wants boundaries like 250, 300, 350 ms, not a smooth geometric ladder that skips the region.
  • Keep bucket sets consistent across services, or cross-service aggregation quietly degrades.
  • Prefer native or exponential histograms where the stack supports them: they encode the distribution in a sparse, scale-invariant form that removes the manual boundary-picking and most of the bucket-cardinality tax.

The theme of the whole lesson in one line: every piece of measurement precision is bought with series, and good telemetry design is deciding where precision is worth it.

7. Reading a latency chart like a professional

Put the mechanics together and some professional reading habits fall out.

  • Always plot p50, p95 and p99 together. The gaps between them are diagnostic: a rising p99 with a flat p50 means a tail problem, retries, one slow shard, a garbage-collection pause, lock contention. Everything rising together means a systemic slowdown, saturation or a downstream dependency.
  • Distrust improvements during incidents. If timeouts are killing the slowest requests, they never complete, never get measured, and every percentile improves. Latency charts must be read next to error rates, always.
  • Check the sample count. A p99 computed from forty requests is four requests' worth of information; at low traffic, percentile charts are noise wearing a suit.
  • Window with care. A five-minute p99 of 2 seconds that never appears in the one-hour p99 is not a contradiction; it is averaging over time doing to your incident what averaging over requests did to your users.

None of this requires tooling: it requires knowing what the numbers are made of. That, plus a cardinality budget, is most of what separates teams whose telemetry answers questions from teams whose telemetry generates invoices.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. Nine requests take 100 ms and one takes 4 seconds. What does the 490 ms average latency tell you?
    • The typical request takes about half a second
    • A latency no request experienced: it hides both the healthy majority and the disaster in the tail
    • The p99 is approximately 490 ms
    • The distribution is roughly normal around 490 ms
  2. Why can't you combine two services' p99 values by averaging them?
    • Because p99 must always be computed on the client side
    • Because the two services may use different clock sources
    • Because percentiles do not compose: the combined p99 depends on both full distributions and traffic shares, so it must be computed from mergeable histogram buckets
    • Because averaging is only valid for p50
  3. A metric has 30 routes, 5 methods, 8 status codes and 50 pods as labels. Roughly how many series can it create?
    • 93, the sum of the label values
    • About 1,500
    • 30, one per route, with the rest as metadata
    • 60,000, the product of the label value counts
  4. Why do unbounded identifiers like user_id not belong in metric labels?
    • Each distinct value creates a permanent new series, so cost and memory scale with the identifier space
    • They make dashboards harder to read
    • Metric labels cannot contain numbers
    • They violate data protection rules automatically
  5. During an incident with aggressive timeouts, latency percentiles suddenly improve. What is the most likely explanation?
    • The system recovered on its own
    • The slowest requests are being killed before completing, so they never get measured
    • The histogram buckets were resized automatically
    • Traffic increased, diluting the tail

Related lessons

Programming
intermediate

Metrics, Logs, Traces: Three Signals, Three Cost Models

Observability is not a product you buy but a property your system has: can you explain a behaviour you did not predict? This lesson defines the three telemetry signals, what question each answers, why their costs grow along completely different axes, and why the difference between monitoring and observability is the difference between known and unknown failure modes.

8 steps·~12 min
Programming
intermediate

SLOs and Error Budgets: Turning Reliability Into a Number

How reliable should the service be? Wrong question: the right one is how much unreliability you can afford, spent deliberately. This lesson builds the SLI, SLO and error budget machinery from Google's SRE practice, does the arithmetic of nines, explains burn-rate alerting, and shows why 100 percent is the wrong target.

7 steps·~11 min
Programming
intermediate

Distributed Tracing and the Art of Throwing Data Away

Tracing every request through every service produces the most useful telemetry you have and more of it than anyone can afford. This lesson covers how context propagation actually stitches a trace together, the head versus tail sampling decision and why it determines which incidents you can debug, and the collector pipeline where all of it is enforced.

7 steps·~11 min
AI
intermediate

Memory, Identity, and Seeing What the Agent Did

Three services decide whether an agent survives contact with production: what it remembers between sessions, whose authority it acts with when it calls your systems, and whether you can reconstruct what it did after the fact. This lesson covers AgentCore Memory, Identity and Observability, and the delegation problem that makes agent authentication genuinely different.

7 steps·~11 min