Metrics and label cardinality

Observability and operations Mid-level Python 3.13+ PostgreSQL 17+ Reviewed By Best Latest Apps

Predict the output

What do the two lines print?

Python 3.13
endpoints = 40
status_codes = 8
regions = 4
series = endpoints * status_codes * regions
print(series)
users = 100_000
print(series * users)  # after adding a user_id label

Options
Hint 1 of 1

Combinations multiply.

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A 1280 then 128000000

Correct. 40 × 8 × 4 series, then multiplied by 100,000 users.

Every distinct combination of label values is its own time series, so the counts multiply: 40 × 8 × 4 = 1,280. A user_id label multiplies that by every user.

Why not the others?
  • B 52 then 100052. Label values multiply; they don't add.
  • C 1280 then 1280. Each user id makes new series.
  • D 1280 then 100000. Every existing combination is split per user.

30-second answer

Each unique combination of a metric's labels is a separate time series that the system stores and indexes. Labels like endpoint, status and region multiply into a manageable number, but adding an unbounded label such as user id, request id or full URL multiplies series by the number of distinct values, blowing up memory, cost and query time. Keep labels bounded and low-cardinality; put per-user detail in logs or traces instead.

Practise saying it · 30 s. No recording, just a timer

Explain cardinality.

Say your answer out loud, then check which of these points you covered:

  • Series = label combinations
  • Unbounded labels explode
  • Route templates
  • Detail in logs/traces
  • Histograms

Deep dive

Counters for events, gauges for current values, histograms for latency distributions.

Use route templates (/orders/{id}), not raw paths, as labels.

Histograms with fixed buckets aggregate across instances; summaries don't.

Watch the series count of your metrics backend itself.

Series multiply with each label Ran on Python 3.13.5 · mypy --strict
Python 3.13
endpoints = 40
status_codes = 8
regions = 4
series = endpoints * status_codes * regions
print(series)
users = 100_000
print(series * users)  # after adding a user_id label

Common mistakes

  • user_id or email as a label.
  • Raw URLs as labels.
  • Averages instead of histograms for latency.

Senior take & follow-ups

Define a series, show the multiplication, then the rules: bounded labels, templates, histograms.

Follow-up questions

Think of your answer first, then open the hint.

  1. Where does per-user detail belong?

    Show hint

    Logs and traces, which handle high cardinality.

  2. Why histograms?

    Show hint

    They aggregate across instances and give percentiles.

Suggest a fix if something here is wrong or out of date.