How to handle high-cardinality metrics without exploding costs: A practical playbook

Measure cardinality, keep the dimensions that answer real questions, shape the rest at the collector, and price ingest by volume.

Axiom · · 11 min read

A metrics bill can rise without a matching rise in traffic. Someone traces the increase to a single label such as customer_id, and the proposed fix is to delete the label. Deleting it usually brings the bill down. The next incident is then harder to diagnose, because the dimension that separated affected tenants from healthy ones is gone.

Most observability stacks were not designed for the number of dimensions modern systems emit, so teams reduce the dimensions they keep in order to stay inside budget. This playbook covers how to measure cardinality, decide which dimensions justify their cost, shape the rest at the collector, and choose a store that prices ingest by volume rather than by series count.

Why high-cardinality metrics turn into a cost problem

Every unique tag combination is a new series

Cardinality is the number of unique combinations of attributes on a metric, and each combination is its own stored object. The Prometheus project's instrumentation and naming guidance describes the mechanism: every unique combination of key-value label pairs represents a new time series. Every one of those series usually has to be indexed, held in memory, and billed as its own object.

The multiplication is easy to underestimate. Take a single request counter tagged with 40 services, 25 routes, 4 regions, 8 status codes, and 500 tenants. Those five attributes describe one metric, and together they can produce up to 16 million distinct series. Adding a sixth attribute with ten values makes it 160 million.

The legacy metrics model assumed a small number of tags per series. Current systems often exceed that assumption. AI workloads add prompt IDs, model versions, and per-request token counts. Microservices add per-tenant labels, and per-request identifiers like trace IDs leak into metric labels along the way. Designs that were adequate at terabyte scale often fail at petabyte scale, which is the premise behind the modern machine data platform.

How per-series pricing charges for dimensions

Most metrics systems charge by active time series, so the more dimensions a team tracks, the more it pays. That pricing model encourages teams to pre-aggregate data, drop labels, and limit cardinality to control costs. Traditional systems often respond in the same way, either charging per dimension or silently dropping high-cardinality tags.

The short-term saving is usually temporary. Costs rise as volume rises, and engineers reduce instrumentation to keep the bill predictable. The missing label then becomes a problem during an incident, when it would have been the one that isolated the blast radius. At petabyte scale, ingestion becomes an architecture problem, and the economics follow from the architecture.

A practical playbook for high-cardinality metrics

Step 1: Measure cardinality before you cut it

Auditing dimensions is the step most teams skip, and it is also the least expensive. Start there, because a limit may already be trimming the data before anyone decides anything. The OpenTelemetry metrics SDK specification sets a default cardinality limit of 2,000 data points per instrument per collection cycle. Measurements past that limit collapse into a single synthetic series marked otel.metric.overflow. If a dashboard has looked oddly flat, check that limit before changing application code.

Start by ranking metrics by active series count, then attribute each metric's series count to the specific dimensions driving it. One or two attributes usually account for most of the total, which turns a vague cost problem into a short list.

Step 2: Keep the dimensions that answer a question

Triage each dimension against a question someone has asked in an incident review. Customer, route, region, model, and deployment version tend to carry the context needed to isolate impact, so they usually stay. A unique request identifier nobody groups by is a different case, and it usually belongs on a trace or an event rather than on a metric.

The test is whether removing the dimension would change an answer. If nobody can group by pod_name after a pod is gone, that attribute is stored for a question the team never asks. If tenant_id is how support confirms one customer is affected while everyone else is fine, cutting it turns a short check into a much longer investigation.

AI workloads sharpen this test. Token spend can't be attributed on an average. Knowing which model version, which tenant, and which feature drove yesterday's inference bill requires exactly the dimensions per-series pricing punishes. For LLM-backed products, per-model and per-tenant tags aren't nice-to-have diagnostics; they're literally the FinOps ledger.

Write the decision down per dimension. That record is what stops the same argument from restarting at the next budget review, and it gives the next engineer a reason for the decision.

Step 3: Shape metrics at the collector

Dimension shaping belongs in the OpenTelemetry Collector pipeline rather than scattered through service code, because the pipeline is one place you can change without a redeploy. The transform processor rewrites or removes attributes before export, and the filter processor drops whole metrics or data points. The OpenTelemetry specification also enforces cardinality limits after attribute filtering, which makes "views" the intended place to shed dimensions inside the SDK.

While you're in the pipeline, check the limits on ingested metrics you're exporting into. The 4 MiB maximum uncompressed request is the one to watch, so batching doesn't turn into 413 responses.

Step 4: Move what you keep onto a store that doesn't charge per dimension

Steps 1 through 3 reduce unused series. Once a team has cut everything nobody asks about, the remaining dimensions are the ones people use, and the question becomes what they cost. At that point the answer depends on how the destination prices metrics.

Axiom's metrics datastore is purpose-built for high-cardinality time series, and it treats high-cardinality tags as a design principle, not a cost-control penalty. In practice that means capturing every dimension your systems emit and tagging per request, per tenant, and per model.

Migration doesn't have to be a cutover. Most teams stand up the OpenTelemetry Collector first, run both stacks in parallel for one workload, then switch at the next renewal seam. Teams coming from a Prometheus-shaped stack can translate PromQL queries to MPL, Axiom's query language for metric series, with minimal changes.

What changes when high cardinality is a design principle

Placement follows series pressure

Cardinality still consumes real resources, so a store has to place that load somewhere. Axiom's architecture answers with per-metric adaptive placement inside MetricsDB, Axiom's metrics store. Each metric gets a "subring," which is a subset of ingest nodes sized to its active series.

As Dynamic subrings: Consistent hashing without the tradeoff describes, each subring grows or shrinks on the actual number of series. A 1,000-series metric and a 1,000,000-series metric each get the right spread automatically.

One usage-based dial across every signal

Pricing is the other change. As Metrics are generally available describes, Axiom prices metrics ingest per gigabyte, starting at $0.12/GB with volume discounts. There's no active time series count to track. That's one usage-based dial across logs, traces, metrics, and events.

Two consequences follow. First, you can query metrics in Axiom with MPL, the Metrics Processing Language, right next to your logs, events, and traces. A cardinality question and a log question live in the same investigation. Second, the dimensions you keep stay useful across signals, because logs, traces, metrics, and events on one platform meet in the same Console dashboard.

That shared surface comes with two trade-offs to plan for before a migration. MetricsDB truncates timestamps to second precision, and it flattens resource, scope, and metric tags into a single namespace.

Where agents fit in cardinality work

Cardinality auditing is repetitive work, which makes it a reasonable thing to hand to an agent. Axiom's metrics skill exposes high-cardinality metrics to agent reasoning in MPL. The Query metrics skill can rank series counts by dimension, or compare a metric's shape before and after a collector change. Axiom's MCP server gives that agent the same governed access a person has, under the permissions the caller carries.

An agent can report which five attributes produce most of a team's series. A person still decides which of those five to keep.

High-cardinality metrics turn into a cost problem because most systems charge for series identity. Dropping labels reduces the bill, and it also removes the dimension that later investigations need. The sequence that works is the same in every stack: measure which dimensions drive series counts, keep the ones that answer questions people ask, shape the rest in the OpenTelemetry Collector, and then compare how the store underneath charges.

When metrics are priced per gigabyte instead of per active time series, tagging per request, per tenant, and per model is an instrumentation choice rather than a budget choice.

FAQs

Related Reading

See it on your own cardinality

If you're deciding which dimensions to keep and which to drop because of cost, a walkthrough on a live metric will show the series count and the per-gigabyte ingest cost on the same data. Bring the tags you want to keep and see Axiom on your data.