1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2026-06-03 18:35:08 +02:00

sdk/metric: apply default cardinality limit of 2000 (#8247)

Per
https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits:

> If none of the previous values are defined, the default value of 2000
SHOULD be used.
This commit is contained in:
Robert Pająk
2026-04-22 14:40:51 +02:00
committed by GitHub
parent c5045485b4
commit 5f80184b57
6 changed files with 56 additions and 27 deletions
+5 -2
View File
@@ -25,7 +25,7 @@ type config struct {
cardinalityLimit int
}
const defaultCardinalityLimit = 0
const defaultCardinalityLimit = 2000
// readerSignals returns a force-flush and shutdown function for a
// MeterProvider to call in their respective options. All Readers c contains
@@ -172,7 +172,10 @@ func WithExemplarFilter(filter exemplar.Filter) Option {
// The cardinality limit is the hard limit on the number of metric datapoints
// that can be collected for a single instrument in a single collect cycle.
//
// Setting this to a zero or negative value means no limit is applied.
// By default, if this option is not used, a limit of
// 2000 is applied.
//
// Setting this to a zero or negative means no limit is applied.
// This value applies to all instrument kinds, but can be overridden per kind by
// the reader's cardinality limit selector (see [WithCardinalityLimitSelector]).
func WithCardinalityLimit(limit int) Option {
+6
View File
@@ -338,6 +338,12 @@ func TestWithCardinalityLimit(t *testing.T) {
options: []Option{},
expectedLimit: 1234,
},
{
name: "zero cardinality limit from env disables limit",
envValue: "0",
options: []Option{},
expectedLimit: 0,
},
{
name: "invalid env value uses default",
envValue: "not-a-number",
+3 -5
View File
@@ -44,9 +44,7 @@
// Cardinality refers to the number of unique attributes collected. High cardinality can lead to
// excessive memory usage, increased storage costs, and backend performance issues.
//
// Currently, the OpenTelemetry Go Metric SDK does not enforce a cardinality limit by default
// (note that this may change in a future release). Use [WithCardinalityLimit] to set the
// cardinality limit as desired.
// By default, the OpenTelemetry Go Metric SDK enforces a cardinality limit of 2000.
//
// New attribute sets are dropped when the cardinality limit is reached. The measurement of
// these sets are aggregated into
@@ -57,8 +55,8 @@
//
// Recommendations:
//
// - Set the limit based on the theoretical maximum combinations or expected
// active combinations. The OpenTelemetry Specification recommends a default of 2000.
// - Tune the limit based on the theoretical maximum combinations or expected
// active combinations. The SDK default is 2000.
// - A too high of a limit increases worst-case memory overhead in the SDK and may cause downstream
// issues for databases that cannot handle high cardinality.
// - A too low of a limit causes loss of attribute detail as more data falls into overflow.
-1
View File
@@ -45,7 +45,6 @@ func Example() {
meterProvider := metric.NewMeterProvider(
metric.WithResource(res),
metric.WithReader(reader),
metric.WithCardinalityLimit(2000),
)
// Handle shutdown properly so that nothing leaks.
+36 -18
View File
@@ -176,32 +176,40 @@ func TestMeterProviderMixingOnRegisterErrors(t *testing.T) {
}
func TestMeterProviderCardinalityLimit(t *testing.T) {
const uniqueAttributesCount = 10
tests := []struct {
name string
options []Option
wantDataPoints int
name string
options []Option
uniqueAttributesCount int
wantDataPoints int
wantOverflowPoints int
}{
{
name: "no limit (default)",
options: nil,
wantDataPoints: uniqueAttributesCount,
name: "default limit",
options: nil,
uniqueAttributesCount: defaultCardinalityLimit + 5,
wantDataPoints: defaultCardinalityLimit,
wantOverflowPoints: 1,
},
{
name: "no limit (limit=0)",
options: []Option{WithCardinalityLimit(0)},
wantDataPoints: uniqueAttributesCount,
name: "no limit (limit=0)",
options: []Option{WithCardinalityLimit(0)},
uniqueAttributesCount: 10,
wantDataPoints: 10,
wantOverflowPoints: 0,
},
{
name: "no limit (negative)",
options: []Option{WithCardinalityLimit(-5)},
wantDataPoints: uniqueAttributesCount,
name: "no limit (negative)",
options: []Option{WithCardinalityLimit(-5)},
uniqueAttributesCount: 10,
wantDataPoints: 10,
wantOverflowPoints: 0,
},
{
name: "limit=5",
options: []Option{WithCardinalityLimit(5)},
wantDataPoints: 5,
name: "limit=5",
options: []Option{WithCardinalityLimit(5)},
uniqueAttributesCount: 10,
wantDataPoints: 5,
wantOverflowPoints: 1,
},
}
@@ -216,7 +224,7 @@ func TestMeterProviderCardinalityLimit(t *testing.T) {
counter, err := meter.Int64Counter("metric")
require.NoError(t, err, "failed to create counter")
for i := range uniqueAttributesCount {
for i := range tt.uniqueAttributesCount {
counter.Add(
t.Context(),
1,
@@ -241,6 +249,16 @@ func TestMeterProviderCardinalityLimit(t *testing.T) {
tt.wantDataPoints,
"unexpected number of data points",
)
overflow := attribute.NewSet(attribute.Bool("otel.metric.overflow", true))
var overflowPoints int
for _, dp := range sumData.DataPoints {
attrs := dp.Attributes
if attrs.Equals(&overflow) {
overflowPoints++
}
}
assert.Equal(t, tt.wantOverflowPoints, overflowPoints, "unexpected overflow data points")
})
}
}