What Is Cardinality in OpenTelemetry Metrics?
A friendly, step-by-step guide to the one number that quietly decides how much your metrics cost, how fast your dashboards load, and whether your monitoring survives a busy day.
Picture a small ice-cream shop. On day one, it sells three flavours in two cone sizes. The owner keeps a simple tally sheet with six boxes, one for every possible order, and puts a tick in a box each time someone buys. It is easy to read and easy to keep. Then the shop adds twenty flavours, five sizes, ten toppings, and starts writing the customer’s name on every order. Suddenly the tally sheet needs thousands of boxes, nobody can find anything, and the owner spends more time managing paper than selling ice cream.
Metrics in OpenTelemetry behave in exactly the same way. Every unique combination of details you attach to a number needs its own box in memory, its own line in the export, and its own row in your monitoring backend. The count of those boxes has a name: cardinality. In this guide you will learn what it is, how it grows, why OpenTelemetry protects you with a built-in limit, and how to keep your own metrics healthy. No prior knowledge is needed, and every new word is explained when it first appears.
1Starting With the Basics: What Is a Metric?
Before we can count combinations, we need to know what is being combined.
A metric is a number that describes the behaviour of a running system, measured again and again over time. How many requests did the server handle? How many items are waiting in the queue? How long did the checkout page take to respond? Each of these questions is answered with a number, and the number keeps changing as the system works. Collecting these numbers lets engineers draw graphs, set alerts, and notice trouble before customers do.
Think of the meter in a taxi. It does not record the story of the trip. It simply keeps one running number, the fare, and updates it as the car moves. A metric is a meter for software: small, cheap to read, and very good at showing the overall picture.
How OpenTelemetry captures metrics
OpenTelemetry is an open standard, supported by the Cloud Native Computing Foundation, that defines how applications produce telemetry data such as metrics, traces and logs. For metrics, your code uses a small set of tools called instruments. You pick the instrument that matches the kind of thing you are measuring, and then you record values through it.
Counter
A number that only goes up, such as total requests served or total bytes sent.
UpDownCounter
A number that can go up and down, such as the number of active connections right now.
Histogram
A collection of values grouped into ranges, ideal for response times and message sizes.
Gauge
A snapshot of the current value of something, such as room temperature or CPU load.
Observable (asynchronous) instruments
Instruments that do not get called by your code at every event. Instead, the SDK asks them for a reading at collection time.
Measurement
One single value recorded through an instrument, together with its attributes, at one moment.
Meet the attributes
A bare number is rarely enough. Knowing that the server handled one million requests is nice, but knowing that 900,000 were successful and 100,000 failed is far more useful. To make that possible, every measurement can carry attributes. An attribute is a simple key-value pair that describes the circumstances of the measurement: for example, the HTTP method is GET, the route is the checkout page, the status code is 200.
You may also hear these called labels (the word used by Prometheus, a popular monitoring system) or tags (used by some commercial tools). They all mean roughly the same thing. OpenTelemetry uses the word attributes, so we will use it throughout.
From measurements to data points to streams
Your application may record millions of measurements, but it does not send millions of numbers to the backend. Instead, a part of the OpenTelemetry software development kit (the SDK, which is the library running inside your application) aggregates them. Aggregating means combining many small values into one summary. A counter, for instance, simply adds up the values. A histogram counts how many values fell into each range.
The result of aggregation is a data point: one summary value for one unique set of attributes. All the data points that belong to one instrument (after any customisation) form a metric stream. Hold on to this idea, because the entire topic of cardinality lives right here: one data point exists for every unique set of attributes in a stream.
flowchart LR
A["Your code records a measurement"] --> B["Instrument"]
B --> C["SDK aggregation: one data point per unique attribute set"]
C --> D["Metric stream"]
D --> E["Exporter sends data"]
E --> F["Collector or backend"]
Measurements are cheap. Data points are what cost you. A million measurements with identical attributes still produce just one data point. A thousand measurements with a thousand different attribute sets produce a thousand data points.
2So What Exactly Is Cardinality?
Cardinality is simply the count of unique attribute combinations in a metric.
The word cardinality comes from mathematics, where it means the number of items in a set. In the metrics world, it means the number of different attribute combinations that have been reported for one metric. Because the SDK keeps a separate summary in memory for every unique combination, cardinality is the number that drives the memory cost of a metric.
A tiny worked example
Suppose you measure how long your web server takes to answer requests. You attach two attributes to every measurement. The first is the HTTP method, which can be GET or POST. The second is the status code, which in your small example can be 200 or 404. How many unique combinations can you see?
- GET with 200
- GET with 404
- POST with 200
- POST with 404
That is four combinations, so the cardinality of this metric stream is four. The SDK keeps four data points, the exporter sends four data points, and the backend stores four time series. Four is tiny and perfectly healthy.
Cardinality is like the number of different name tags at a conference. It does not matter how many conversations happen. It matters how many different people are wearing a tag. A thousand conversations between the same ten people need only ten tags.
What cardinality is not
Beginners often mix cardinality up with other numbers. It is worth separating them clearly, because each one answers a different question.
Cardinality is not the number of metrics you have. You can have a hundred metrics that are each tiny, or a single metric that is enormous. What matters is how many unique attribute combinations each one carries.
The order does not matter, the values do
An attribute set is a collection of pairs. Reporting method first and status second gives the same set as reporting status first and method second, so it is counted once. But the moment any single value is different, such as a new status code or a new route, you have a brand-new combination and a brand-new data point.
3The Multiplication Rule: How Cardinality Explodes
Attributes do not add up. They multiply, and that is where trouble begins.
If one attribute has 2 possible values and another has 2 possible values, you get 2 × 2 = 4 combinations. This is the multiplication rule, and it is the single most important idea for understanding cardinality. The theoretical maximum number of data points for a metric is the number of distinct values of the first attribute, multiplied by the number of distinct values of the second, multiplied by the third, and so on.
Imagine choosing an outfit. With 5 shirts and 4 pairs of trousers you have 20 outfits, not 9. Add 3 pairs of shoes and you have 60 outfits. Each new item you add does not add a few more choices; it multiplies all the choices you already had.
Watching the numbers grow
Let us follow a realistic web-service metric as a team keeps adding attributes with good intentions. Each row below adds one more detail to the same metric.
| Attribute added | Distinct values | Theoretical combinations |
|---|---|---|
| HTTP method | 5 | 5 |
| Route template | 40 | 200 |
| Status code | 10 | 2,000 |
| Region | 4 | 8,000 |
| Customer tier | 3 | 24,000 |
| User ID | 100,000 | 2,400,000,000 |
Look at how calm the first three rows are, and how violent the last one is. Method, route and status together give 2,000 combinations, which is manageable. Adding region and tier raises it to 24,000, which is still survivable for one metric. But adding a single user ID attribute turns a modest metric into a theoretical 2.4 billion data points. One careless attribute outweighed everything that came before it.
Theory versus reality
The multiplication gives you the upper bound, the most that could ever happen. In real life, not every combination occurs. Perhaps only 12 of your 40 routes ever return a 404, or only some regions serve some customer tiers. The actual cardinality is the count of combinations that really show up in recorded measurements. Still, the upper bound is a great way to reason about risk before you write any code, and a high-cardinality attribute like a user ID can get close to its upper bound surprisingly fast.
flowchart TD
A["Method: 5 values"] --> D["x"]
B["Route: 40 values"] --> D
C["Status: 10 values"] --> D
D --> E["2,000 data points"]
E --> F["Add region: x 4 = 8,000"]
F --> G["Add user ID: x 100,000 = 800 million"]
How to estimate cardinality before you ship
List every attribute you plan to record
Write them down one by one, including the ones added automatically by helper libraries.
Estimate the distinct values of each
Ask how many different values this attribute can take in a normal week, and whether that number is fixed or keeps growing.
Multiply them together
The product is your upper bound. If it is far above the SDK limit, you already know there is a design problem.
Adjust for reality
Remove combinations that cannot happen and note the ones that are rare. This gives you a realistic working number.
4Low, Medium and High Cardinality Attributes
Not all attributes are equal. Learning to recognise the dangerous ones is a core skill.
An attribute’s cardinality is the number of different values it can take. Engineers loosely group attributes into low, medium, high and unbounded. These labels are not official thresholds. They are a mental shortcut, and what counts as “high” depends on your limit and your budget. A good test is to ask: can I list every possible value of this attribute on a single page? If yes, it is probably safe. If the list would fill a phone book, it is not.
A handful of values
HTTP method, success or failure, environment name, protocol version. You could write all values on a sticky note.
Tens to hundreds
Route templates, status codes, region, service version, payment method. Fine, as long as the list is fixed and under your control.
Thousands or more
Customer ID, email address, session ID, device ID, full URL with parameters. These grow with your user base.
Never stops growing
Request ID, trace ID, timestamps, random tokens, free-text error messages. Every measurement may create a new value.
A practical comparison
| Attribute | Typical size | Verdict | Safer alternative |
|---|---|---|---|
| HTTP method | About 5 to 10 | Safe | Use as is |
| Route template such as /orders/{id} | Tens | Safe | Use the template, never the raw path |
| Status code | Tens at most | Safe | Or group into 2xx, 4xx, 5xx |
| Region or zone | A few to dozens | Usually safe | Keep it bounded |
| Tenant or customer ID | Dozens to millions | Risky | Customer tier or plan name |
| User ID, session ID | Millions | Dangerous | Put it on traces or logs |
| Full URL or query string | Unbounded | Dangerous | Route template |
| Error message text | Unbounded | Dangerous | A short error type or code |
A traffic light has three states, red, amber and green. A phone book has millions of names. If you wanted to paint a signal for every person in the city instead of every state of a traffic light, you would need far more paint than any city could afford. Choose attributes that behave like traffic lights, not like phone books.
Before adding any attribute, ask: is the set of possible values bounded and small? And will anyone actually filter or group a dashboard by this attribute? If the answer to either question is no, leave it out of the metric.
5Where Attributes Come From: Three Levels of Detail
Not every label on a metric is chosen by the developer who wrote the recording line.
When you look at a metric in a dashboard, you see a set of labels. They look like one flat list, but they actually come from three different places inside OpenTelemetry. Knowing which is which helps you understand what the SDK limit protects and what it does not.
| Level | What it describes | Examples | Who sets it |
|---|---|---|---|
| Resource attributes | The thing producing the data | Service name, service instance ID, pod name, cloud region | Configured once at SDK start-up |
| Instrumentation scope | The library that produced the metric | Name and version of the instrumentation library | Set when the meter is created |
| Measurement attributes | The circumstances of one measurement | Method, route, status code, payment method | Supplied by code each time a value is recorded |
Think of a parcel in the post. The sender’s address printed on the label stays the same for everything that sender ships; that is the resource. The note inside describing what is in this particular box changes each time; that is the measurement attribute. The courier company that handled it is like the instrumentation scope.
What the SDK limit actually covers
The SDK cardinality limit, which we meet in detail in Chapter 8, applies to the attributes your code supplies when it records measurements through the metrics API. Resource attributes do not count against it, because they are the same for every measurement from that process.
That does not mean resources are free. In your backend, every unique measurement attribute set is repeated for every resource that reports it. If one application runs on 200 pods, each pod has its own instance identity, so the backend sees 200 copies of every combination. A metric with 2,000 combinations per process can therefore become 400,000 series across the fleet. We will return to this fleet effect in Chapter 12.
When you estimate cardinality, remember the multiplier hiding outside your code: the number of instances, pods, hosts or containers that report the metric. A perfectly safe metric on one machine can become a very large one in production.
6Why Cardinality Matters: The Hidden Bill
High cardinality is rarely a bug that crashes something on day one. It is a slow leak that shows up as a surprising invoice or a sluggish dashboard.
It is tempting to think of a metric as a tiny, weightless thing. A single number sounds free. But the number is only free when it is alone. Every unique attribute combination turns into a separate piece of state that must be stored, moved, indexed and queried. Cardinality is therefore a cost multiplier that touches every stage of the metrics pipeline.
flowchart LR
A["Application SDK: memory per data point"] --> B["Network: bytes per export"]
B --> C["Collector: processing and memory"]
C --> D["Backend: storage and indexing"]
D --> E["Dashboards and alerts: query time"]
E --> F["Your invoice"]
The five places you pay
Application memory
The SDK holds one live summary in memory for each combination. Millions of combinations can quietly eat the memory your business code needs.
Network and export time
Each export cycle sends every active data point. More points means bigger payloads, longer export times and more chance of timeouts.
Collector and pipeline load
Intermediate components must receive, convert and forward all those points, which needs more CPU and memory as the count grows.
Backend storage and indexing
Time-series databases store and index every unique series. Many vendors also price by the number of active series, so cardinality directly sets the bill.
Query speed and usability
Graphs that must scan and combine huge numbers of series become slow, time out, or become unreadable because there are too many lines to see.
Imagine a library that decides to keep a separate shelf for every single reader’s favourite book. At first it is charming. Soon the building is full of shelves, the librarians cannot find anything, the rent triples, and almost every shelf holds a single book that nobody ever looks at. High cardinality is a library with a shelf per reader.
Why it often hides until it hurts
Cardinality problems are sneaky because they depend on real traffic. During development, you test with a handful of users, so a user ID attribute looks harmless: only a few values appear. After launch, thousands of real users arrive, and the number of combinations rises with them. Some teams discover the problem only when a billing alert fires, a dashboard stops loading, or an application starts using much more memory than before.
A metric that is healthy in a test environment can become unhealthy in production without a single line of code changing. The number of users, devices or customers grows, and the attribute that depended on them grows too.
7Temporality: Delta and Cumulative and Their Effect on Cardinality
How your metrics are reported over time changes how long each combination stays alive in memory.
When the SDK exports a metric, it has to decide what the numbers mean. There are two styles, called aggregation temporality. This sounds technical, but the idea is easy once you see it in everyday terms.
Cumulative temporality
Each report shows the running total since the process began. The number only grows, like a car’s odometer.
Delta temporality
Each report shows only what happened since the previous report, like a trip meter that you reset every time you look at it.
Your electricity meter shows a total that never resets: that is cumulative. Your monthly bill shows only this month’s usage: that is delta. Both describe the same electricity, but they carry different bookkeeping burdens.
Why this matters for cardinality
Under cumulative temporality, a running total must be continuously reported for every combination ever seen since start-up. If a combination appeared once an hour ago and never again, the SDK still has to keep its total and keep reporting it. Memory is therefore tied to everything the process has ever seen, which means old, no-longer-used combinations keep taking space.
Under delta temporality, the SDK only needs to report combinations that received measurements during the current interval. A combination that stops appearing can drop out of memory and out of the next export. Memory is therefore tied to what is active right now rather than to the full history.
| Question | Cumulative | Delta |
|---|---|---|
| What does each report show? | Total since the process began | Change since the last report |
| Do inactive combinations stay in memory? | Yes, they keep being reported | No, they can be released |
| What does cardinality measure here? | Everything ever seen in this process lifetime | Combinations active in the interval |
| Risk from changing identifiers | Grows steadily and is much more likely to hit limits | Lower, if the active set is bounded |
| Commonly preferred by | Prometheus-style systems | Some commercial backends |
A tenant or customer attribute that is safe under delta temporality, because only a few hundred tenants are active at once, can be a guaranteed path to overflow under cumulative temporality, because every tenant ever seen is kept forever. Always check which temporality your backend expects before choosing attributes.
sequenceDiagram
participant App as Application
participant SDK as SDK
participant Back as Backend
App->>SDK: Measurements with attribute set A
SDK->>Back: Interval 1 report: A
App->>SDK: Measurements with attribute set B only
SDK->>Back: Interval 2 delta report: B only
SDK->>Back: Interval 2 cumulative report: A and B
8The SDK Cardinality Limit: OpenTelemetry’s Safety Net
The metrics SDK protects your application by refusing to track an unlimited number of combinations.
Because every unique combination consumes memory, an unbounded attribute could in the worst case make an application run out of memory. To prevent that, the OpenTelemetry metrics SDK enforces a cardinality limit: a maximum number of unique attribute combinations that it will track for each metric stream. The default value is 2000, and it can be changed through a View, which we cover in the next chapter.
Picture a cloakroom with 2000 hooks. The first 2000 different coats each get their own hook. When coat number 2001 arrives, the attendant does not refuse the guest. Instead, the coat goes into a big box marked “Other”. The cloakroom never overflows, and nobody is turned away; some coats just lose their individual hook.
What happens when the limit is reached
Once a metric stream has reached its limit, new unique combinations are not tracked individually. Instead, their measurements are folded into one special overflow data point. This point carries a single attribute, otel.metric.overflow, set to true. Every language implementation uses the same marker, so one query in your backend can detect overflow across all your services, whatever language they are written in.
Normal operation
New attribute combinations arrive, and each is given its own data point until the stream has used up its budget.
Limit reached
The stream now holds the maximum number of data points. Existing combinations continue to be updated normally.
New combination arrives
The SDK sees a combination it has not tracked before and has no room left for a new data point.
Folded into overflow
The measurement is added to the single overflow data point, which carries only the overflow marker and none of the original attributes.
flowchart TD
A["Measurement arrives with attributes"] --> B{"Combination already tracked?"}
B -- Yes --> C["Update its data point"]
B -- No --> D{"Room left under the limit?"}
D -- Yes --> E["Create a new data point"]
D -- No --> F["Add to the overflow data point"]
What you keep and what you lose
The overflow design is deliberately gentle. Totals are preserved: the overflow point still adds up all the measurements that did not fit, so the metric’s overall sum stays correct. What you lose is detail. You can no longer say which route, user or tenant the folded measurements belonged to, because the overflow point has none of those attributes.
What the limit gives you
- Protection of application memory from runaway attributes
- Overall totals that stay correct even after overflow
- A common, easy-to-query marker across languages and backends
- Graceful behaviour instead of crashes or dropped data
- A visible signal that something needs fixing
What the limit cannot do
- It cannot give you detail for the folded measurements
- It does not choose which combinations are kept, as early arrivals fill the budget first
- It does not limit total series across a whole fleet in your backend
- It does not fix the underlying design problem
When a metric overflows, the combinations that arrived first keep their own lines, and everything that came later is hidden in the overflow point. A graph filtered on one specific route or customer may therefore undercount, because some of that route’s measurements ended up in overflow without its label. The total is right; the breakdown is not.
An important detail about what the limit covers
The limit applies to attributes supplied when recording measurements through the metrics API. Resource attributes, which describe the producing service, are not counted against it. That makes the limit a precise guard for the attributes your own code adds, which are the ones most likely to explode.
Support for cardinality limits can differ between language implementations and versions. If you are not sure whether your SDK enforces a limit, check its documentation. Where it does not, you should watch process memory carefully for any metric that carries attributes with many values.
9Taking Control With Views
A View lets you reshape a metric inside the SDK without touching the code that records it.
A View is a rule you give the SDK that says how a particular instrument’s data should be presented. Views are a powerful feature because they separate two jobs that are often tangled together: the people writing instrumentation decide what to record, and the people operating the system decide what actually leaves the application. You can use a View without changing a single line of the code that records measurements.
A View is like an editor at a newspaper. Reporters hand in long, detailed stories (the measurements). The editor trims, rewords and decides what actually gets printed. The reporters keep writing as before, but readers only see what the editor chose.
What a View can do for cardinality
Keep only chosen attributes
Specify an allow-list. Attributes not on the list are removed, and the measurements that differed only by those attributes merge together.
Change the cardinality limit
Raise or lower the number of combinations tracked for a specific metric when you have a deliberate reason.
Rename or describe a metric
Adjust the name, unit or description that is exported, helping consistency across teams.
Pick a different aggregation
Choose, for example, a different style of histogram, or drop a metric entirely if nobody needs it.
The power of removing an attribute
Dropping an attribute is the most effective way to reduce cardinality. Imagine a request metric carrying method, route, status and a noisy user-agent string. If you keep only method, route and status, all the user-agent variations collapse into the same data points. The totals stay exactly right, because the measurements are merged rather than discarded, and the number of data points can fall dramatically.
Removing an attribute on purpose is far better than overflowing by accident. When you choose which attributes to keep, you control what detail survives. When the limit forces a fold, the SDK keeps whichever combinations happened to arrive first and hides the rest.
Problem
A shared library records a request-duration metric with many attributes, and your team only needs a few of them. The extra attributes inflate cardinality and cost.
Why It Matters
You may not own the library, so editing its code is impractical, and different teams need different subsets of detail.
Correct Approach
Configure a View for that instrument that keeps only the attributes you truly use. The library continues to record as before, while your application exports a leaner stream.
10Detecting Cardinality Problems Before They Hurt
You cannot fix what you cannot see, so make overflow visible and watch the early warning signs.
The beauty of the shared overflow marker is that it turns a hidden problem into a searchable fact. Since every compliant SDK uses the same attribute, you can ask your backend a simple question: which metrics, from which services, have emitted an overflow data point recently? The answer is a ready-made list of metrics that need attention.
Early warning signs
Overflow data points appear
Any series carrying the overflow marker is a direct, reliable sign that some metric has reached its limit.
Active series count climbs steadily
Most backends report the number of active series. A line that rises week after week without matching traffic growth is a warning.
Application memory grows
A process whose memory rises over time while traffic is flat may be holding an ever-growing set of data points.
Exports get slow or large
Growing payload sizes and longer export durations suggest that the number of points per cycle is increasing.
Dashboards slow down
Queries that used to be quick now take noticeably longer, or graphs become cluttered with hundreds of lines.
Create an alert on the appearance of overflow data points, and review the list of top metrics by series count on a regular schedule. Treat overflow as a design ticket, not a nuisance: it tells you exactly where to look.
If you see no overflow, it may simply be that your SDK does not implement limits, that the event happened outside your retention window, or that your backend hides the attribute. Check these possibilities before concluding that everything is fine.
11Where Does the Detail Go? Metrics, Traces and Logs
If metrics cannot carry every detail, something else must. OpenTelemetry has three signals, and each has its own strength.
A common worry is: “But I really do need to know which user had the slow request.” That is a perfectly good need. The answer is not to push the detail into metrics, but to put it in the signal that was built to carry it. OpenTelemetry offers three main signals, and they work best as a team.
| Signal | What it is | Best at | Handles high-detail data? |
|---|---|---|---|
| Metrics | Summaries of numbers over time | Trends, alerts, dashboards, capacity | No, keep attributes few and bounded |
| Traces | The path of one request through the system | Finding where one request was slow or failed | Yes, each trace is its own record |
| Logs | Timestamped records of individual events | Detailed context and exact messages | Yes, each event stands alone |
A hospital uses a dashboard in the corridor showing how many patients are waiting (metrics), and it keeps a full medical file for each patient (traces and logs). Nobody tries to print every patient’s file on the corridor dashboard. The dashboard answers “how is the ward doing?” and the file answers “what happened to this person?”.
The two-step investigation
Spot the problem in metrics
A low-cardinality metric shows that errors for one route have jumped. It tells you that something is wrong and roughly where.
Dive into traces and logs
You then open example traces or logs for that route, where user IDs, order numbers and full error messages are safely available.
Some systems can link a metric data point to example traces recorded in the same time window. This lets you jump from a spike on a graph straight to a real, detailed example, giving you the benefit of rich detail without the cardinality cost.
12The Fleet Effect: Why the SDK Cap Is Not Enough
The limit protects one process. Your monitoring backend sees the whole fleet.
The SDK limit is enforced inside each running process, per metric stream. It is a guardrail for application memory, and it does its job well. But it does not cap the number of series that arrive in your backend, because many processes each send their own set of data points.
A fleet of one thousand pods, each allowed two thousand combinations, can in principle deliver up to two million series for a single metric. And with delta temporality, a leak of changing values can grow even larger over time in the backend, because new combinations keep appearing in different intervals while each individual process stays under its own limit.
Every guest at a wedding is allowed a plus-one. That sounds modest, but if two thousand guests each bring one, the venue still has to seat four thousand people. A rule that is reasonable for one guest can still overwhelm the hall when it is multiplied across everyone.
Helpful habits at fleet scale
Budget per metric
Give each important metric an agreed series budget, and multiply by the number of instances before approving new attributes.
Review instance identity
Ask whether every pod or container identity really needs to be a separate series, or whether a coarser grouping would do.
Use the pipeline
Intermediate collectors can often drop attributes, filter metrics or aggregate across instances before data reaches an expensive backend.
Watch backend usage
Track the active series count by metric name in your backend, so growth is noticed early and attributed to the right team.
The SDK limit is the last line of defence for memory, not a complete cost-control strategy. Good attribute design, Views, pipeline filtering and backend monitoring each cover gaps that the others leave open.
13Design Patterns and Anti-Patterns
Teams keep making the same handful of mistakes, and the same handful of fixes keep working.
Healthy design patterns
Pattern: Use templates, not raw values
Record the route template, such as an order page with a placeholder for the order number, instead of the real address containing the actual number. Thousands of distinct addresses collapse into a few dozen templates.
Pattern: Group into classes
Instead of recording every status code or every error text, record a class such as success, client error or server error. You keep the insight that matters while the value list stays tiny.
Pattern: Bucket continuous values
Numbers such as payload size or age can take endless values. Group them into ranges like small, medium and large, so the attribute has only a few possible values.
Pattern: Bounded allow-lists
When an attribute comes from outside input, accept only values from a known list and map anything else to a single value called other. This guarantees that outside users cannot inflate your cardinality.
Pattern: Tiers instead of identities
Report customer tier, plan or segment rather than customer ID. You can still compare how different groups experience your service, without a series per customer.
Anti-patterns to avoid
Problem
Putting identifiers such as user ID, session ID, request ID or trace ID on a metric as an attribute.
Why It’s Harmful
Each identifier is unique or nearly unique, so every measurement creates a new data point. Memory, network and storage costs grow with traffic, and the SDK limit is soon reached.
Correct Approach
Keep identifiers on traces and logs. In metrics, use bounded attributes such as tier or region.
Problem
Using full URLs, including paths and query strings, as an attribute.
Why It’s Harmful
Every distinct number, search term or token in the address becomes a new value, producing an unbounded set that grows as visitors behave differently.
Correct Approach
Use the route template that your web framework already knows, and discard the variable parts.
Problem
Recording raw error messages or exception text as an attribute.
Why It’s Harmful
Messages often contain numbers, names, timestamps or addresses, so two errors of the same kind look different. The set of values is effectively unlimited.
Correct Approach
Record a short, stable error type or code. Put the full message into a log entry or the trace.
Problem
Adding attributes “just in case” someone might want them later.
Why It’s Harmful
Unused attributes still multiply cardinality and cost, and removing them later is harder once dashboards and alerts depend on them.
Correct Approach
Add an attribute only when a named dashboard, alert or decision needs it, and review attributes regularly to retire unused ones.
Problem
Raising the cardinality limit to a very large number to make overflow go away.
Why It’s Harmful
The overflow was a symptom, and the root cause remains. A higher limit simply moves the memory and cost problem to a bigger scale.
Correct Approach
Find which attribute is responsible, remove or bound it, and raise the limit only with a clear, deliberate reason.
14When High Cardinality Is Actually Right
Avoiding high cardinality is the default advice, but it is not a law of nature.
Sometimes an operational need is real. A platform that bills each of a few hundred business customers may genuinely need per-customer metrics. A service that supports a bounded number of devices may want one series per device. The guidance is not “never”, but “only on purpose”.
The dimension is operationally necessary
Someone will actually filter, alert or bill on it. Curiosity alone is not enough.
The active set is bounded
You can name an upper limit on how many distinct values will be live at the same time, and it is not growing without end.
The limit is chosen deliberately
The cardinality limit is set on purpose, with the memory impact understood, rather than left to the default by accident.
Overflow is monitored
You have an alert on the overflow marker, so that growth beyond your expectation is noticed immediately.
Temporality has been considered
You have checked how your chosen temporality affects how long old combinations stay alive.
If you can answer “yes” to all five points above, a larger, deliberate cardinality is a design choice. If you cannot, treat it as a warning and look for a lower-cardinality alternative.
15Real-World Scenarios
Four everyday situations show how these ideas play out.
Scenario 1: The online shop with a customer attribute
An e-commerce team records order counts with attributes for payment method, country and customer ID. At launch it looks fine, but as the shopper base grows into hundreds of thousands, the customer ID drives cardinality far beyond the limit and overflow appears. The fix is to remove the customer ID from the metric, keep payment method and country, and rely on logs for per-customer investigation.
Scenario 2: The API gateway recording raw paths
A gateway records latency using the full request path. Requests such as product pages with different numbers produce a new value every time, and attackers scanning random addresses make it worse. Switching to the matched route template shrinks thousands of values to a few dozen and also makes the graphs readable.
Scenario 3: The multi-tenant platform
A software-as-a-service platform wants per-tenant error rates for its largest tenants. It keeps tenant as an attribute only for a bounded list of important tenants and maps all others to a single value called other. The metric stays informative where it matters and bounded everywhere else.
Scenario 4: The container fleet after an autoscaling event
A service scales from fifty to five hundred pods during a sale. Each pod reports the same moderately sized metric, and the backend suddenly sees a tenfold jump in series, even though no code changed. The team learns to budget series with the instance count in mind and to aggregate away pod identity in the pipeline for metrics that do not need it.
flowchart TD
A["Metric looks too expensive or overflows"] --> B["Find the attribute with the most values"]
B --> C{"Is it really needed for decisions?"}
C -- No --> D["Remove it with a View"]
C -- Yes --> E{"Can it be grouped or bounded?"}
E -- Yes --> F["Use template, class, tier or allow-list"]
E -- No --> G["Move detail to traces or logs"]
16Best Practices and Common Mistakes
A short checklist you can keep next to your editor.
Best practices
- Estimate cardinality by multiplying attribute values before shipping
- Prefer bounded attributes with small, known value lists
- Use route templates, error codes and classes instead of raw values
- Keep identifiers on traces and logs, not metrics
- Use Views to trim attributes you do not need
- Alert on the overflow marker and review top metrics by series count
- Check which temporality your backend expects
- Set a per-metric series budget that accounts for fleet size
Common mistakes
- Assuming a metric is free because it is just a number
- Testing only with a few users and trusting the result
- Letting outside input become an attribute value unchecked
- Raising the limit instead of fixing the attribute
- Forgetting that instances multiply series in the backend
- Reading breakdowns of an overflowed metric as if they were complete
- Never reviewing attributes that nobody uses any more
Before approving any new metric attribute, ask four things: how many values can it take, does that number stop growing, who will use it, and could a trace or log carry this detail instead? If all four answers are comfortable, go ahead.
17Frequently Asked Questions
Quick answers to the questions beginners ask most often.
No. Cardinality counts unique attribute combinations within one metric. You can have many metrics that are all tiny, or one metric that is huge.
Not by itself. A million measurements with identical attributes still produce a single data point. Cardinality grows only when new, different attribute combinations appear.
The default is 2000 unique attribute combinations per metric stream. It can be changed for a specific metric through a View.
You lose detail, not totals. Extra combinations are folded into a single overflow data point, so overall sums remain correct, but you can no longer tell which attributes those measurements had.
Look for data points carrying the attribute otel.metric.overflow set to true. All compliant SDKs use the same marker, so one query can find it across services.
No. The limit covers the attributes supplied with measurements through the metrics API. Resource attributes still multiply series in the backend across instances, so they matter for cost.
Delta is usually gentler, because inactive combinations can be released. Cumulative must keep reporting every combination seen since start. The right choice also depends on what your backend supports.
You can, but it usually hides the real problem and increases memory and cost. It is better to remove or bound the offending attribute and raise the limit only for a deliberate reason.
On traces and logs. Those signals are designed to carry detail for individual requests, while metrics are designed for summaries.
Only partly. It limits what each process tracks, but many processes together can still send far more series than any single limit suggests. Use Views, pipeline filtering and backend monitoring as well.
Support can vary by language and version. Check the documentation for your SDK, and watch process memory if you are unsure that a limit is applied.
18Summary and Key Takeaways
Cardinality is the number of unique attribute combinations a metric carries, and it is the quiet force behind the memory, network, storage and query cost of your metrics. Attributes multiply rather than add, so one careless identifier can turn a small, healthy metric into an enormous one. OpenTelemetry protects applications with a default limit of 2000 combinations per metric stream, folding any extras into a clearly marked overflow data point so totals stay correct while detail is lost. That safety net is helpful, but it is not a full cost strategy, so good attribute design, Views, temporality awareness, fleet-level budgeting and overflow alerts all deserve a place in your toolkit. Keep metrics for trends and put the rich, per-request detail where it belongs: in traces and logs.
Key Takeaways
- Cardinality counts combinations — it is the number of unique attribute sets for a metric, and each one costs memory and money.
- Attributes multiply — multiply the distinct values of each attribute to find the upper bound before you ship.
- Avoid unbounded values — user IDs, request IDs, raw URLs and error messages belong on traces and logs, not metrics.
- The SDK limit is a safety net — the default of 2000 per stream folds extras into an overflow point that preserves totals but loses detail.
- Temporality changes the picture — cumulative keeps every combination seen so far, while delta tracks what is active in the interval.
- Use Views to stay in control — trim attributes or tune limits without changing instrumentation code.
- Watch the fleet and the overflow marker — budget series across all instances and alert on overflow so problems are fixed early.