Skip to content

Commit 8e84b22

Browse files
authored
Added option to ignore specific metrics when checking max-series-per-metric limit. (#4302)
* Added option to ignore specific metrics when checking max-series-per-metric limit. Signed-off-by: Peter Štibraný <[email protected]>
1 parent 8bdf5b1 commit 8e84b22

File tree

7 files changed

+143
-3
lines changed

7 files changed

+143
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* `cortex_ruler_write_requests_failed_total`
5050
* `cortex_ruler_queries_total`
5151
* `cortex_ruler_queries_failed_total`
52+
* [ENHANCEMENT] Ingester: Added option `-ingester.ignore-series-limit-for-metric-names` with comma-separated list of metric names that will be ignored in max series per metric limit. #4302
5253
* [BUGFIX] Purger: fix `Invalid null value in condition for column range` caused by `nil` value in range for WriteBatch query. #4128
5354
* [BUGFIX] Ingester: fixed infrequent panic caused by a race condition between TSDB mmap-ed head chunks truncation and queries. #4176
5455
* [BUGFIX] Alertmanager: fix Alertmanager status page if clustering via gossip is disabled or sharding is enabled. #4184

docs/configuration/config-file-reference.md

+7
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,13 @@ instance_limits:
810810
# tenants). Additional requests will be rejected. 0 = unlimited.
811811
# CLI flag: -ingester.instance-limits.max-inflight-push-requests
812812
[max_inflight_push_requests: <int> | default = 0]
813+
814+
# Comma-separated list of metric names, for which
815+
# -ingester.max-series-per-metric and -ingester.max-global-series-per-metric
816+
# limits will be ignored. Does not affect max-series-per-user or
817+
# max-global-series-per-metric limits.
818+
# CLI flag: -ingester.ignore-series-limit-for-metric-names
819+
[ignore_series_limit_for_metric_names: <string> | default = ""]
813820
```
814821

815822
### `querier_config`

pkg/ingester/ingester.go

+26
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net/http"
88
"os"
9+
"strings"
910
"sync"
1011
"time"
1112

@@ -98,6 +99,8 @@ type Config struct {
9899
DefaultLimits InstanceLimits `yaml:"instance_limits"`
99100
InstanceLimitsFn func() *InstanceLimits `yaml:"-"`
100101

102+
IgnoreSeriesLimitForMetricNames string `yaml:"ignore_series_limit_for_metric_names"`
103+
101104
// For testing, you can override the address and ID of this ingester.
102105
ingesterClientFactory func(addr string, cfg client.Config) (client.HealthAndIngesterClient, error)
103106
}
@@ -131,6 +134,29 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
131134
f.Int64Var(&cfg.DefaultLimits.MaxInMemoryTenants, "ingester.instance-limits.max-tenants", 0, "Max users that this ingester can hold. Requests from additional users will be rejected. This limit only works when using blocks engine. 0 = unlimited.")
132135
f.Int64Var(&cfg.DefaultLimits.MaxInMemorySeries, "ingester.instance-limits.max-series", 0, "Max series that this ingester can hold (across all tenants). Requests to create additional series will be rejected. This limit only works when using blocks engine. 0 = unlimited.")
133136
f.Int64Var(&cfg.DefaultLimits.MaxInflightPushRequests, "ingester.instance-limits.max-inflight-push-requests", 0, "Max inflight push requests that this ingester can handle (across all tenants). Additional requests will be rejected. 0 = unlimited.")
137+
138+
f.StringVar(&cfg.IgnoreSeriesLimitForMetricNames, "ingester.ignore-series-limit-for-metric-names", "", "Comma-separated list of metric names, for which -ingester.max-series-per-metric and -ingester.max-global-series-per-metric limits will be ignored. Does not affect max-series-per-user or max-global-series-per-metric limits.")
139+
}
140+
141+
func (cfg *Config) getIgnoreSeriesLimitForMetricNamesMap() map[string]struct{} {
142+
if cfg.IgnoreSeriesLimitForMetricNames == "" {
143+
return nil
144+
}
145+
146+
result := map[string]struct{}{}
147+
148+
for _, s := range strings.Split(cfg.IgnoreSeriesLimitForMetricNames, ",") {
149+
tr := strings.TrimSpace(s)
150+
if tr != "" {
151+
result[tr] = struct{}{}
152+
}
153+
}
154+
155+
if len(result) == 0 {
156+
return nil
157+
}
158+
159+
return result
134160
}
135161

136162
// Ingester deals with "in flight" chunks. Based on Prometheus 1.x

pkg/ingester/ingester_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -1044,3 +1044,15 @@ func TestIngesterActiveSeries(t *testing.T) {
10441044
})
10451045
}
10461046
}
1047+
1048+
func TestGetIgnoreSeriesLimitForMetricNamesMap(t *testing.T) {
1049+
cfg := Config{}
1050+
1051+
require.Nil(t, cfg.getIgnoreSeriesLimitForMetricNamesMap())
1052+
1053+
cfg.IgnoreSeriesLimitForMetricNames = ", ,,,"
1054+
require.Nil(t, cfg.getIgnoreSeriesLimitForMetricNamesMap())
1055+
1056+
cfg.IgnoreSeriesLimitForMetricNames = "foo, bar, ,"
1057+
require.Equal(t, map[string]struct{}{"foo": {}, "bar": {}}, cfg.getIgnoreSeriesLimitForMetricNamesMap())
1058+
}

pkg/ingester/ingester_v2.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1558,7 +1558,7 @@ func (i *Ingester) createTSDB(userID string) (*userTSDB, error) {
15581558
userDB := &userTSDB{
15591559
userID: userID,
15601560
activeSeries: NewActiveSeries(),
1561-
seriesInMetric: newMetricCounter(i.limiter),
1561+
seriesInMetric: newMetricCounter(i.limiter, i.cfg.getIgnoreSeriesLimitForMetricNamesMap()),
15621562
ingestedAPISamples: util_math.NewEWMARate(0.2, i.cfg.RateUpdatePeriod),
15631563
ingestedRuleSamples: util_math.NewEWMARate(0.2, i.cfg.RateUpdatePeriod),
15641564

pkg/ingester/user_state.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (us *userStates) getOrCreate(userID string) *userState {
145145
index: index.New(),
146146
ingestedAPISamples: util_math.NewEWMARate(0.2, us.cfg.RateUpdatePeriod),
147147
ingestedRuleSamples: util_math.NewEWMARate(0.2, us.cfg.RateUpdatePeriod),
148-
seriesInMetric: newMetricCounter(us.limiter),
148+
seriesInMetric: newMetricCounter(us.limiter, us.cfg.getIgnoreSeriesLimitForMetricNamesMap()),
149149
logger: logger,
150150

151151
memSeries: us.metrics.memSeries,
@@ -362,9 +362,11 @@ type metricCounterShard struct {
362362
type metricCounter struct {
363363
limiter *Limiter
364364
shards []metricCounterShard
365+
366+
ignoredMetrics map[string]struct{}
365367
}
366368

367-
func newMetricCounter(limiter *Limiter) *metricCounter {
369+
func newMetricCounter(limiter *Limiter, ignoredMetricsForSeriesCount map[string]struct{}) *metricCounter {
368370
shards := make([]metricCounterShard, 0, numMetricCounterShards)
369371
for i := 0; i < numMetricCounterShards; i++ {
370372
shards = append(shards, metricCounterShard{
@@ -374,6 +376,8 @@ func newMetricCounter(limiter *Limiter) *metricCounter {
374376
return &metricCounter{
375377
limiter: limiter,
376378
shards: shards,
379+
380+
ignoredMetrics: ignoredMetricsForSeriesCount,
377381
}
378382
}
379383

@@ -394,6 +398,10 @@ func (m *metricCounter) getShard(metricName string) *metricCounterShard {
394398
}
395399

396400
func (m *metricCounter) canAddSeriesFor(userID, metric string) error {
401+
if _, ok := m.ignoredMetrics[metric]; ok {
402+
return nil
403+
}
404+
397405
shard := m.getShard(metric)
398406
shard.mtx.Lock()
399407
defer shard.mtx.Unlock()

pkg/ingester/user_state_test.go

+86
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
"github.com/stretchr/testify/assert"
1616
"github.com/stretchr/testify/require"
1717
"github.com/weaveworks/common/user"
18+
19+
"github.com/cortexproject/cortex/pkg/util"
20+
"github.com/cortexproject/cortex/pkg/util/validation"
1821
)
1922

2023
// Test forSeriesMatching correctly batches up series.
@@ -153,3 +156,86 @@ func TestTeardown(t *testing.T) {
153156
cortex_ingester_active_series{user="3"} 0
154157
`), metricNames...))
155158
}
159+
160+
func TestMetricCounter(t *testing.T) {
161+
const metric = "metric"
162+
163+
for name, tc := range map[string]struct {
164+
ignored []string
165+
localLimit int
166+
series int
167+
expectedErrorOnLastSeries error
168+
}{
169+
"no ignored metrics, limit not reached": {
170+
ignored: nil,
171+
localLimit: 5,
172+
series: 5,
173+
expectedErrorOnLastSeries: nil,
174+
},
175+
176+
"no ignored metrics, limit reached": {
177+
ignored: nil,
178+
localLimit: 5,
179+
series: 6,
180+
expectedErrorOnLastSeries: errMaxSeriesPerMetricLimitExceeded,
181+
},
182+
183+
"ignored metric, limit not reached": {
184+
ignored: []string{metric},
185+
localLimit: 5,
186+
series: 5,
187+
expectedErrorOnLastSeries: nil,
188+
},
189+
190+
"ignored metric, over limit": {
191+
ignored: []string{metric},
192+
localLimit: 5,
193+
series: 10,
194+
expectedErrorOnLastSeries: nil, // this metric is not checked for series-count.
195+
},
196+
197+
"ignored different metric, limit not reached": {
198+
ignored: []string{"another_metric1", "another_metric2"},
199+
localLimit: 5,
200+
series: 5,
201+
expectedErrorOnLastSeries: nil,
202+
},
203+
204+
"ignored different metric, over limit": {
205+
ignored: []string{"another_metric1", "another_metric2"},
206+
localLimit: 5,
207+
series: 6,
208+
expectedErrorOnLastSeries: errMaxSeriesPerMetricLimitExceeded,
209+
},
210+
} {
211+
t.Run(name, func(t *testing.T) {
212+
var ignored map[string]struct{}
213+
if tc.ignored != nil {
214+
ignored = make(map[string]struct{})
215+
for _, v := range tc.ignored {
216+
ignored[v] = struct{}{}
217+
}
218+
}
219+
220+
limits := validation.Limits{MaxLocalSeriesPerMetric: tc.localLimit}
221+
222+
overrides, err := validation.NewOverrides(limits, nil)
223+
require.NoError(t, err)
224+
225+
// We're testing code that's not dependant on sharding strategy, replication factor, etc. To simplify the test,
226+
// we use local limit only.
227+
limiter := NewLimiter(overrides, nil, util.ShardingStrategyDefault, true, 3, false)
228+
mc := newMetricCounter(limiter, ignored)
229+
230+
for i := 0; i < tc.series; i++ {
231+
err := mc.canAddSeriesFor("user", metric)
232+
if i < tc.series-1 {
233+
assert.NoError(t, err)
234+
mc.increaseSeriesForMetric(metric)
235+
} else {
236+
assert.Equal(t, tc.expectedErrorOnLastSeries, err)
237+
}
238+
}
239+
})
240+
}
241+
}

0 commit comments

Comments
 (0)