← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Cardinality in Metrics

Observability Intermediate
debt(d7/e5/b7/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints say automated=no and the only tool listed is Prometheus itself (via /api/v1/label/__name__/values). There is no static analysis or linter that flags high-cardinality label usage at write time; the code pattern 'labels.*user_id|labels.*session' requires manual review. The problem typically only surfaces at runtime when series counts explode or OOM occurs — not at commit time.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to replace unbounded label values with bounded sets and audit cardinality. In practice this means finding every instrumentation call that uses user_id, session_id, or raw URLs across the codebase, redesigning what labels are emitted, and potentially migrating or dropping existing time-series data. This is more than a single-line swap but typically scoped to one service's instrumentation layer, placing it at e5.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (b7). The applies_to covers web, cli, and queue-worker contexts — the full application surface. Every future label addition must be evaluated against cardinality budgets. A single past mistake can hold the entire metrics infrastructure hostage (OOM crash, degraded query performance), shaping all future instrumentation decisions and requiring ongoing governance. This is a persistent, cross-cutting structural concern.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception field states exactly: 'More labels = more useful metrics' — the intuitive developer belief is that adding labels increases observability value linearly. In reality each label multiplies series count multiplicatively, and using user_id or session_id (which feel like obvious debugging labels) can OOM Prometheus. This contradicts how labels appear to work (additive descriptors) and is a well-documented gotcha that surprises even experienced developers new to Prometheus.

About DEBT scoring →

TL;DR

Cardinality is the number of unique label value combinations — high cardinality (user_id, URL, session_id as labels) creates millions of time series and crashes Prometheus.

Explanation

Each unique label combination = one time series. 100 routes × 5 methods × 10 status codes = 5000 series (fine). 100 routes × 1M user_ids = 100M series (crash). High-cardinality labels: user_id, request_id, URL with query params, IP address, session_id. Solutions: don't use high-cardinality fields as labels (they belong in logs/traces). Aggregate high-cardinality dimensions in application code before recording. Prometheus native histograms (v0.16+): improve cardinality for latency. Grafana Tempo/Honeycomb: handle high cardinality natively. Use Prometheus for low-cardinality aggregate metrics; logs/traces for high-cardinality detail.

Common Misconception

More labels = more useful metrics — each additional label multiplies the series count. High-cardinality labels destroy Prometheus performance.

Why It Matters

A single high-cardinality label can take a Prometheus instance from healthy to OOM crash — understanding cardinality is critical for sustainable metrics infrastructure.

Common Mistakes

  • User ID or session ID as a Prometheus label.
  • Full URL (including query params) as label — unbounded.
  • Not auditing cardinality before adding new labels.

Code Examples

✗ Vulnerable
// HIGH cardinality — one series per user:
$histogram->labels([
    'user_id' => $userId, // Unbounded
    'url' => $request->fullUrl(), // Includes query params
])->observe($duration);
✓ Fixed
// LOW cardinality — bounded dimensions only:
$histogram->labels([
    'route' => $request->route()->getName(), // e.g. 'api.orders.show'
    'method' => $request->method(),
])->observe($duration);

// user_id → goes in logs and traces, not metrics

Added 23 Mar 2026
Views 48
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 3 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 1 ping S 2 pings M 1 ping T 1 ping W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 1 ping M 1 ping T 0 pings W
No pings yet today
SEMrush 1
Amazonbot 7 Google 4 Ahrefs 4 Perplexity 4 Scrapy 4 Unknown AI 3 Meta AI 2 ChatGPT 1 Claude 1 Bing 1 Sogou 1 PetalBot 1 SEMrush 1
crawler 32 crawler_json 1 pre-tracking 1
DEV INTEL Tools & Severity
🔴 Critical ⚙ Fix effort: Medium
⚡ Quick Fix
Never use user_id, session_id, or unbounded URLs as Prometheus labels. Cap label values to bounded sets (routes, methods, status codes). Audit cardinality with Prometheus /api/v1/label/__name__/values.
📦 Applies To
web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
labels.*user_id|labels.*session
Auto-detectable: ✗ No prometheus
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: File
CWE-400


✓ schema.org compliant