Photo by Luke Chesser on Unsplash
Observability Cardinality: Why High-Dimensional Metrics Break Systems
Modern observability platforms promise complete visibility into system behavior. Instrument everything, the vendor pitch goes, and you’ll understand every request, every user, every transaction. But teams that follow this advice often hit a wall: their monitoring system slows to a crawl, query timeouts multiply, and monthly bills spike into five figures. The culprit is usually cardinality.
What Cardinality Means in Observability
Cardinality describes the number of unique values a dimension can take. A region label with four possible values (us-east, us-west, eu-west, ap-south) has low cardinality. A user_id label with millions of active users has high cardinality. A request_id label that’s unique for every API call has explosive cardinality.
Time-series databases store metrics as streams of timestamped values, indexed by label combinations. When you record http_requests_total with labels for method, status, endpoint, and user_id, the database creates a separate series for each unique combination. Four methods, ten status codes, fifty endpoints, and a million users yield 2 billion potential series. Even if only a fraction are active, the index overhead becomes unmanageable.
Why High Cardinality Breaks Things
Time-series databases like Prometheus, VictoriaMetrics, and Mimir keep an in-memory index mapping label sets to storage locations. Every query scans this index to find matching series. As cardinality grows, both index size and query time explode. A query that took milliseconds with 10,000 series can take seconds with 10 million.
Storage costs scale with active series count, not just with data volume. Each series consumes memory for metadata, even if it only records a handful of data points. A metric tagged with request IDs or session tokens generates millions of short-lived series that clutter the index and thrash the write path.
Cloud observability vendors charge by series count or ingestion volume, often with exponential pricing tiers. Adding a single high-cardinality label can increase costs by orders of magnitude. Teams discover this the hard way when a routine deploy includes user IDs in trace tags and the monthly bill jumps from $2,000 to $40,000.
Where Cardinality Explosions Hide
High-cardinality labels sneak in through well-intentioned instrumentation. Timestamps in metric names, IP addresses as labels, full URLs with query parameters, container IDs, autogenerated pod names with random suffixes, customer tenant IDs in multi-tenant systems—all create sprawl.
Kubernetes environments are especially prone to this. Each pod gets a unique name. Each deployment revision spawns new pods. Without careful label hygiene, a metric tagged with pod_name accumulates thousands of stale series as pods churn. The series remain in the index long after the pods are gone, degrading performance for live queries.
Strategies to Control Cardinality
The first defense is label discipline. Avoid labels that identify individual requests, users, or ephemeral resources. Reserve labels for dimensions you’ll actually aggregate or filter on: service name, environment, region, HTTP method, status code family (2xx, 4xx, 5xx rather than 200, 201, 400, 404). If you need per-user analysis, use logs or traces, not metrics.
For Kubernetes, normalize pod names to deployment or stateful set names. Use namespace, deployment, and container labels; drop pod unless debugging requires it. Relabel or drop metrics at the scrape boundary before they reach storage.
Cardinality limits at the ingestion layer provide guardrails. Prometheus supports relabel rules to drop high-cardinality labels or reject entire metrics. Vendors like Grafana Cloud enforce per-metric cardinality caps. Set budgets and alert when a metric approaches its limit, rather than discovering the breach during an outage.
The Trade-Off Between Detail and Scale
Observability is about answering questions. Low-cardinality metrics answer “How many requests failed in us-east?” High-cardinality dimensions answer “Which user experienced errors?” Metrics excel at the first question. For the second, structured logs and distributed traces are more efficient.
The ideal observability stack uses metrics for aggregate trends, logs for event detail, and traces for request-level debugging. Each tool has different cardinality economics. Logs can include user IDs and session tokens because log storage scales with event count, not unique dimension combinations. Traces carry detailed context because trace backends are optimized for high-dimensional lookups.
Teams that treat metrics as a catch-all telemetry sink end up with systems that can’t answer any question well. Choosing the right tool for the cardinality profile of each question keeps costs reasonable and queries fast. Observability isn’t about capturing everything. It’s about capturing the right things in the right place.