Skip to content

Commit dca32de

Browse files
authored
Merge pull request grafana#1328 from grafana/1325-cardinality-limit
Include metric name, label name, number of entries and limit in cardinality errors.
2 parents 7d8510e + 85b1e78 commit dca32de

File tree

5 files changed

+70
-22
lines changed

5 files changed

+70
-22
lines changed

chunk_store_test.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,35 +47,27 @@ var stores = []struct {
4747
{
4848
name: "store",
4949
configFn: func() StoreConfig {
50-
var (
51-
storeCfg StoreConfig
52-
)
50+
var storeCfg StoreConfig
5351
flagext.DefaultValues(&storeCfg)
5452
return storeCfg
5553
},
5654
},
5755
{
5856
name: "cached_store",
5957
configFn: func() StoreConfig {
60-
var (
61-
storeCfg StoreConfig
62-
)
58+
var storeCfg StoreConfig
6359
flagext.DefaultValues(&storeCfg)
64-
6560
storeCfg.WriteDedupeCacheConfig.Cache = cache.NewFifoCache("test", cache.FifoCacheConfig{
6661
Size: 500,
6762
})
68-
6963
return storeCfg
7064
},
7165
},
7266
}
7367

7468
// newTestStore creates a new Store for testing.
7569
func newTestChunkStore(t *testing.T, schemaName string) Store {
76-
var (
77-
storeCfg StoreConfig
78-
)
70+
var storeCfg StoreConfig
7971
flagext.DefaultValues(&storeCfg)
8072
return newTestChunkStoreConfig(t, schemaName, storeCfg)
8173
}

series_store.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"net/http"
88

99
"github.com/go-kit/kit/log/level"
10-
"github.com/pkg/errors"
1110
"github.com/prometheus/client_golang/prometheus"
1211
"github.com/prometheus/client_golang/prometheus/promauto"
1312
"github.com/prometheus/common/model"
@@ -22,11 +21,19 @@ import (
2221
"github.com/cortexproject/cortex/pkg/util/validation"
2322
)
2423

25-
var (
26-
// ErrCardinalityExceeded is returned when the user reads a row that
27-
// is too large.
28-
ErrCardinalityExceeded = errors.New("cardinality limit exceeded")
24+
// CardinalityExceededError is returned when the user reads a row that
25+
// is too large.
26+
type CardinalityExceededError struct {
27+
MetricName, LabelName string
28+
Size, Limit int32
29+
}
30+
31+
func (e CardinalityExceededError) Error() string {
32+
return fmt.Sprintf("cardinality limit exceeded for %s{%s}; %d entries, more than limit of %d",
33+
e.MetricName, e.LabelName, e.Size, e.Limit)
34+
}
2935

36+
var (
3037
indexLookupsPerQuery = promauto.NewHistogram(prometheus.HistogramOpts{
3138
Namespace: "cortex",
3239
Name: "chunk_store_index_lookups_per_query",
@@ -196,6 +203,7 @@ func (c *seriesStore) lookupSeriesByMetricNameMatchers(ctx context.Context, from
196203
var preIntersectionCount int
197204
var lastErr error
198205
var cardinalityExceededErrors int
206+
var cardinalityExceededError CardinalityExceededError
199207
for i := 0; i < len(matchers); i++ {
200208
select {
201209
case incoming := <-incomingIDs:
@@ -210,8 +218,9 @@ func (c *seriesStore) lookupSeriesByMetricNameMatchers(ctx context.Context, from
210218
// series and the other returns only 10 (a few), we don't lookup the first one at all.
211219
// We just manually filter through the 10 series again using "filterChunksByMatchers",
212220
// saving us from looking up and intersecting a lot of series.
213-
if err == ErrCardinalityExceeded {
221+
if e, ok := err.(CardinalityExceededError); ok {
214222
cardinalityExceededErrors++
223+
cardinalityExceededError = e
215224
} else {
216225
lastErr = err
217226
}
@@ -220,7 +229,7 @@ func (c *seriesStore) lookupSeriesByMetricNameMatchers(ctx context.Context, from
220229

221230
// But if every single matcher returns a lot of series, then it makes sense to abort the query.
222231
if cardinalityExceededErrors == len(matchers) {
223-
return nil, ErrCardinalityExceeded
232+
return nil, cardinalityExceededError
224233
} else if lastErr != nil {
225234
return nil, lastErr
226235
}
@@ -241,11 +250,14 @@ func (c *seriesStore) lookupSeriesByMetricNameMatcher(ctx context.Context, from,
241250
}
242251

243252
var queries []IndexQuery
253+
var labelName string
244254
if matcher == nil {
245255
queries, err = c.schema.GetReadQueriesForMetric(from, through, userID, model.LabelValue(metricName))
246256
} else if matcher.Type != labels.MatchEqual {
257+
labelName = matcher.Name
247258
queries, err = c.schema.GetReadQueriesForMetricLabel(from, through, userID, model.LabelValue(metricName), model.LabelName(matcher.Name))
248259
} else {
260+
labelName = matcher.Name
249261
queries, err = c.schema.GetReadQueriesForMetricLabelValue(from, through, userID, model.LabelValue(metricName), model.LabelName(matcher.Name), model.LabelValue(matcher.Value))
250262
}
251263
if err != nil {
@@ -254,7 +266,11 @@ func (c *seriesStore) lookupSeriesByMetricNameMatcher(ctx context.Context, from,
254266
level.Debug(log).Log("queries", len(queries))
255267

256268
entries, err := c.lookupEntriesByQueries(ctx, queries)
257-
if err != nil {
269+
if e, ok := err.(CardinalityExceededError); ok {
270+
e.MetricName = metricName
271+
e.LabelName = labelName
272+
return nil, e
273+
} else if err != nil {
258274
return nil, err
259275
}
260276
level.Debug(log).Log("entries", len(entries))

storage/caching_fixtures.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ var Fixtures = []testutils.Fixture{
4040
func defaultLimits() (*validation.Overrides, error) {
4141
var defaults validation.Limits
4242
flagext.DefaultValues(&defaults)
43+
defaults.CardinalityLimit = 5
4344
return validation.NewOverrides(defaults)
4445
}

storage/caching_index_client.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ func (s *cachingIndexClient) QueryPages(ctx context.Context, queries []chunk.Ind
8888
batches, misses := s.cacheFetch(ctx, keys)
8989
for _, batch := range batches {
9090
if cardinalityLimit > 0 && batch.Cardinality > cardinalityLimit {
91-
return chunk.ErrCardinalityExceeded
91+
return chunk.CardinalityExceededError{
92+
Size: batch.Cardinality,
93+
Limit: cardinalityLimit,
94+
}
9295
}
9396

9497
queries := queriesByKey[batch.Key]
@@ -156,7 +159,10 @@ func (s *cachingIndexClient) QueryPages(ctx context.Context, queries []chunk.Ind
156159
if cardinalityLimit > 0 && cardinality > cardinalityLimit {
157160
batch.Cardinality = cardinality
158161
batch.Entries = nil
159-
cardinalityErr = chunk.ErrCardinalityExceeded
162+
cardinalityErr = chunk.CardinalityExceededError{
163+
Size: cardinality,
164+
Limit: cardinalityLimit,
165+
}
160166
}
161167

162168
keys = append(keys, key)

storage/index_client_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package storage
22

33
import (
44
"fmt"
5+
"strconv"
56
"testing"
7+
"time"
68

7-
"github.com/cortexproject/cortex/pkg/chunk"
89
"github.com/stretchr/testify/require"
10+
11+
"github.com/cortexproject/cortex/pkg/chunk"
12+
"github.com/cortexproject/cortex/pkg/chunk/cache"
913
)
1014

1115
func TestIndexBasic(t *testing.T) {
@@ -194,3 +198,32 @@ func TestQueryPages(t *testing.T) {
194198
}
195199
})
196200
}
201+
202+
func TestCardinalityLimit(t *testing.T) {
203+
forAllFixtures(t, func(t *testing.T, client chunk.IndexClient, _ chunk.ObjectClient) {
204+
limits, err := defaultLimits()
205+
require.NoError(t, err)
206+
207+
client = newCachingIndexClient(client, cache.NewMockCache(), time.Minute, limits)
208+
batch := client.NewWriteBatch()
209+
for i := 0; i < 10; i++ {
210+
batch.Add(tableName, "bar", []byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
211+
}
212+
err = client.BatchWrite(ctx, batch)
213+
require.NoError(t, err)
214+
215+
var have int
216+
err = client.QueryPages(ctx, []chunk.IndexQuery{{
217+
TableName: tableName,
218+
HashValue: "bar",
219+
}}, func(_ chunk.IndexQuery, read chunk.ReadBatch) bool {
220+
iter := read.Iterator()
221+
for iter.Next() {
222+
have++
223+
}
224+
return true
225+
})
226+
require.Error(t, err, "cardinality limit exceeded for {}; 10 entries, more than limit of 5")
227+
require.Equal(t, 0, have)
228+
})
229+
}

0 commit comments

Comments
 (0)