Utilities

Prometheus Cardinality Estimator

Estimate Prometheus series count, head-block memory, and disk usage from a metric name and label dimensions. Spot the high-cardinality offender before it OOM-kills your Prometheus.

Labels

Label nameEstimated unique values
Getting big480.0k series

Watch the highest-cardinality labels — instance, user_id, request_id are common offenders. Aim to keep total series under 1M per Prometheus.

Total active series

480.0k

480,000

Memory (head block)

1.4 GB

~3 KB per active series

Disk (15d retention)

50.2 GB

@ 15s scrape interval

Highest-cardinality label

instance has 200 unique values and is multiplying everything else by that factor.

Dropping this label (via metric_relabel_configs in your scrape config) would reduce series from 480.0k to 2.4k.

metric_relabel_configs:
  - source_labels: [__name__]
    regex: http_requests_total
    action: replace
    target_label: instance
    replacement: ''

Automate your incident response

Reduce MTTR by 90% with AI-powered root cause analysis. Free to start.

Try Uptimes.ai Free

Why cardinality is the #1 Prometheus pitfall

Prometheus stores one time series per unique combination of label values. That sounds harmless until you realize the math is multiplicative. Three labels with 50, 200, and 8 unique values is not 258 series — it is 50 × 200 × 8 = 80,000 series. Add a single high-cardinality label (user_id with 100k values) and you are at 8 billion. That is when the OOM-killer comes for your Prometheus pod.

The labels you should never label-ify

  • user_id, session_id, request_id — unbounded values that grow with traffic
  • raw URL paths — every dynamic segment becomes a new value (use route patterns instead: /users/:id)
  • error messages with embedded data — strip the dynamic parts before using as a label
  • IP addresses, user agents — practically unbounded
  • timestamps in any form — never a label, always a sample value

Bucket instead of label

For continuous values (latency, response size, queue depth), use histogram buckets rather than raw values as labels. A latency histogram with 10 buckets has 10x fewer series than labeling raw latency values. Prometheus client libraries make this easy with Histogram or Summary types.

From metrics to incidents

Cardinality discipline keeps Prometheus fast and your bill manageable — but observability metrics are only useful if you can reason across them during an incident. Uptimes.ai is an AI SRE agent that queries Prometheus, Datadog, eBPF, and Kubernetes state simultaneously when an alert fires, producing a root cause report in under three minutes. The same multi-source reasoning a senior SRE does manually, automated.

Frequently Asked Questions

What is cardinality and why does it matter?+
Cardinality is the total number of unique time series for a metric. Prometheus stores one series per unique combination of label values, so cardinality grows multiplicatively. A counter with three labels at 100, 50, and 20 unique values produces 100,000 series. Each series consumes head-block memory (~3 KB) and disk (~7-10 KB per day at 15s scrape). High-cardinality metrics are the #1 cause of Prometheus OOMs and the #1 cost driver in hosted offerings like Grafana Cloud or Datadog.
What is a safe cardinality limit?+
A single Prometheus instance handles up to about 10M active series comfortably on a 16-32 GB host, but most teams aim to stay under 1M-2M per Prometheus to leave headroom. Per metric, anything above 100k starts to deserve attention; above 1M is almost always a bug or a high-cardinality label slipping into an instrumentation library.
What labels usually cause cardinality explosions?+
The classic culprits are unbounded values: user_id, session_id, request_id, raw URL paths, error messages with embedded IDs, IP addresses. Always ask "is this value drawn from a fixed set?" before you label-ify it. Bucket continuous values (latency_bucket="le-100ms") instead of leaking raw numbers as labels.
How do I drop a high-cardinality label without losing the metric?+
Use metric_relabel_configs in your Prometheus scrape config. The tool above generates a snippet that replaces a specific label with an empty value (effectively merging all values into one). For more nuanced control, use action: drop on the specific label, or recording rules to aggregate the metric without the label.
Is the memory estimate accurate?+
It is a rule-of-thumb based on the Prometheus TSDB head-block representation: roughly 3 KB per active series, including the in-memory chunk. Real-world numbers vary by 1.5-3x depending on label string length, sample frequency, and whether you count series-only memory vs total Prometheus RSS. For capacity planning, treat this as a lower bound — actual RSS is typically 1.5-2x.