Skip to content

Commit cb8ed0e

Browse files
56quartersjtlisi
andauthored
Convert all validation.Limits durations to model.Duration (#4044)
* Test demonstrating time.Duration handling issue in JSON Signed-off-by: Nick Pillitteri <[email protected]> * Convert all validation.Limits durations to model.Duration Use the prometheus/common model.Duration for all duration related limits instead of the standard library time.Duration type. This works around an issue with the JSON representation of the standard library type. For example: * The YAML representation of time.Duration(1 * time.Second) is the string `1s` * The JSON representation of time.Duration(1 * time.Second) is the integer `1000000000` By contrast: * The YAML representation of model.Duration(1 * time.Second) is the string `1s` * The JSON representation of model.Duration(1 * time.Second) is the string `1s` Signed-off-by: Nick Pillitteri <[email protected]> * Update default configuration to match Limits struct Signed-off-by: Nick Pillitteri <[email protected]> * Update CHANGELOG.md Co-authored-by: Jacob Lisi <[email protected]> Signed-off-by: Nick Pillitteri <[email protected]> * Code review feedback Signed-off-by: Nick Pillitteri <[email protected]> Co-authored-by: Jacob Lisi <[email protected]>
1 parent 94b01cd commit cb8ed0e

File tree

9 files changed

+71
-32
lines changed

9 files changed

+71
-32
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* [ENHANCEMENT] Ingester: reduce CPU and memory when an high number of errors are returned by the ingester on the write path with the blocks storage. #3969 #3971 #3973
2424
* [ENHANCEMENT] Distributor: reduce CPU and memory when an high number of errors are returned by the distributor on the write path. #3990
2525
* [ENHANCEMENT] Put metric before label value in the "label value too long" error message. #4018
26+
* [ENHANCEMENT] Allow use of `y|w|d` suffixes for duration related limits and per-tenant limits. #4044
2627
* [BUGFIX] Ruler-API: fix bug where `/api/v1/rules/<namespace>/<group_name>` endpoint return `400` instead of `404`. #4013
2728
* [BUGFIX] Distributor: reverted changes done to rate limiting in #3825. #3948
2829
* [BUGFIX] Ingester: Fix race condition when opening and closing tsdb concurrently. #3959

docs/configuration/config-file-reference.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3829,7 +3829,7 @@ The `limits_config` configures default and per-tenant limits imposed by Cortex s
38293829
38303830
# Maximum accepted sample age before rejecting.
38313831
# CLI flag: -validation.reject-old-samples.max-age
3832-
[reject_old_samples_max_age: <duration> | default = 336h]
3832+
[reject_old_samples_max_age: <duration> | default = 2w]
38333833
38343834
# Duration which table will be created/deleted before/after it's needed; we
38353835
# won't accept sample from before this time.

pkg/chunk/chunk_store_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func newTestChunkStoreConfigWithMockStorage(t require.TestingT, schemaCfg Schema
8686

8787
var limits validation.Limits
8888
flagext.DefaultValues(&limits)
89-
limits.MaxQueryLength = 30 * 24 * time.Hour
89+
limits.MaxQueryLength = model.Duration(30 * 24 * time.Hour)
9090
overrides, err := validation.NewOverrides(limits, nil)
9191
require.NoError(t, err)
9292

pkg/chunk/testutils/testutils.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func SetupTestChunkStoreWithClients(indexClient chunk.IndexClient, chunksClient
126126

127127
var limits validation.Limits
128128
flagext.DefaultValues(&limits)
129-
limits.MaxQueryLength = 30 * 24 * time.Hour
129+
limits.MaxQueryLength = model.Duration(30 * 24 * time.Hour)
130130
overrides, err := validation.NewOverrides(limits, nil)
131131
if err != nil {
132132
return nil, err

pkg/cortex/modules.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func (t *Cortex) initRing() (serv services.Service, err error) {
151151
func (t *Cortex) initRuntimeConfig() (services.Service, error) {
152152
if t.Cfg.RuntimeConfig.LoadPath == "" {
153153
t.Cfg.RuntimeConfig.LoadPath = t.Cfg.LimitsConfig.PerTenantOverrideConfig
154-
t.Cfg.RuntimeConfig.ReloadPeriod = t.Cfg.LimitsConfig.PerTenantOverridePeriod
154+
t.Cfg.RuntimeConfig.ReloadPeriod = time.Duration(t.Cfg.LimitsConfig.PerTenantOverridePeriod)
155155
}
156156

157157
if t.Cfg.RuntimeConfig.LoadPath == "" {

pkg/distributor/distributor_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ func BenchmarkDistributor_Push(b *testing.B) {
11121112
"timestamp too old": {
11131113
prepareConfig: func(limits *validation.Limits) {
11141114
limits.RejectOldSamples = true
1115-
limits.RejectOldSamplesMaxAge = time.Hour
1115+
limits.RejectOldSamplesMaxAge = model.Duration(time.Hour)
11161116
},
11171117
prepareSeries: func() ([]labels.Labels, []cortexpb.Sample) {
11181118
metrics := make([]labels.Labels, numSeriesPerRequest)
@@ -1137,7 +1137,7 @@ func BenchmarkDistributor_Push(b *testing.B) {
11371137
},
11381138
"timestamp too new": {
11391139
prepareConfig: func(limits *validation.Limits) {
1140-
limits.CreationGracePeriod = time.Minute
1140+
limits.CreationGracePeriod = model.Duration(time.Minute)
11411141
},
11421142
prepareSeries: func() ([]labels.Labels, []cortexpb.Sample) {
11431143
metrics := make([]labels.Labels, numSeriesPerRequest)
@@ -2055,9 +2055,9 @@ func TestDistributorValidation(t *testing.T) {
20552055
var limits validation.Limits
20562056
flagext.DefaultValues(&limits)
20572057

2058-
limits.CreationGracePeriod = 2 * time.Hour
2058+
limits.CreationGracePeriod = model.Duration(2 * time.Hour)
20592059
limits.RejectOldSamples = true
2060-
limits.RejectOldSamplesMaxAge = 24 * time.Hour
2060+
limits.RejectOldSamplesMaxAge = model.Duration(24 * time.Hour)
20612061
limits.MaxLabelNamesPerSeries = 2
20622062

20632063
ds, _, r, _ := prepare(t, prepConfig{

pkg/querier/querier_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ func TestQuerier_ValidateQueryTimeRange_MaxQueryLength(t *testing.T) {
439439
flagext.DefaultValues(&cfg)
440440

441441
limits := defaultLimitsConfig()
442-
limits.MaxQueryLength = maxQueryLength
442+
limits.MaxQueryLength = model.Duration(maxQueryLength)
443443
overrides, err := validation.NewOverrides(limits, nil)
444444
require.NoError(t, err)
445445

pkg/util/validation/limits.go

+25-21
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ type Limits struct {
4646
MaxLabelNamesPerSeries int `yaml:"max_label_names_per_series" json:"max_label_names_per_series"`
4747
MaxMetadataLength int `yaml:"max_metadata_length" json:"max_metadata_length"`
4848
RejectOldSamples bool `yaml:"reject_old_samples" json:"reject_old_samples"`
49-
RejectOldSamplesMaxAge time.Duration `yaml:"reject_old_samples_max_age" json:"reject_old_samples_max_age"`
50-
CreationGracePeriod time.Duration `yaml:"creation_grace_period" json:"creation_grace_period"`
49+
RejectOldSamplesMaxAge model.Duration `yaml:"reject_old_samples_max_age" json:"reject_old_samples_max_age"`
50+
CreationGracePeriod model.Duration `yaml:"creation_grace_period" json:"creation_grace_period"`
5151
EnforceMetadataMetricName bool `yaml:"enforce_metadata_metric_name" json:"enforce_metadata_metric_name"`
5252
EnforceMetricName bool `yaml:"enforce_metric_name" json:"enforce_metric_name"`
5353
IngestionTenantShardSize int `yaml:"ingestion_tenant_shard_size" json:"ingestion_tenant_shard_size"`
@@ -71,17 +71,17 @@ type Limits struct {
7171
// Querier enforced limits.
7272
MaxChunksPerQuery int `yaml:"max_chunks_per_query" json:"max_chunks_per_query"`
7373
MaxQueryLookback model.Duration `yaml:"max_query_lookback" json:"max_query_lookback"`
74-
MaxQueryLength time.Duration `yaml:"max_query_length" json:"max_query_length"`
74+
MaxQueryLength model.Duration `yaml:"max_query_length" json:"max_query_length"`
7575
MaxQueryParallelism int `yaml:"max_query_parallelism" json:"max_query_parallelism"`
7676
CardinalityLimit int `yaml:"cardinality_limit" json:"cardinality_limit"`
77-
MaxCacheFreshness time.Duration `yaml:"max_cache_freshness" json:"max_cache_freshness"`
77+
MaxCacheFreshness model.Duration `yaml:"max_cache_freshness" json:"max_cache_freshness"`
7878
MaxQueriersPerTenant int `yaml:"max_queriers_per_tenant" json:"max_queriers_per_tenant"`
7979

8080
// Ruler defaults and limits.
81-
RulerEvaluationDelay time.Duration `yaml:"ruler_evaluation_delay_duration" json:"ruler_evaluation_delay_duration"`
82-
RulerTenantShardSize int `yaml:"ruler_tenant_shard_size" json:"ruler_tenant_shard_size"`
83-
RulerMaxRulesPerRuleGroup int `yaml:"ruler_max_rules_per_rule_group" json:"ruler_max_rules_per_rule_group"`
84-
RulerMaxRuleGroupsPerTenant int `yaml:"ruler_max_rule_groups_per_tenant" json:"ruler_max_rule_groups_per_tenant"`
81+
RulerEvaluationDelay model.Duration `yaml:"ruler_evaluation_delay_duration" json:"ruler_evaluation_delay_duration"`
82+
RulerTenantShardSize int `yaml:"ruler_tenant_shard_size" json:"ruler_tenant_shard_size"`
83+
RulerMaxRulesPerRuleGroup int `yaml:"ruler_max_rules_per_rule_group" json:"ruler_max_rules_per_rule_group"`
84+
RulerMaxRuleGroupsPerTenant int `yaml:"ruler_max_rule_groups_per_tenant" json:"ruler_max_rule_groups_per_tenant"`
8585

8686
// Store-gateway.
8787
StoreGatewayTenantShardSize int `yaml:"store_gateway_tenant_shard_size" json:"store_gateway_tenant_shard_size"`
@@ -96,8 +96,8 @@ type Limits struct {
9696
S3SSEKMSEncryptionContext string `yaml:"s3_sse_kms_encryption_context" json:"s3_sse_kms_encryption_context" doc:"nocli|description=S3 server-side encryption KMS encryption context. If unset and the key ID override is set, the encryption context will not be provided to S3. Ignored if the SSE type override is not set."`
9797

9898
// Config for overrides, convenient if it goes here. [Deprecated in favor of RuntimeConfig flag in cortex.Config]
99-
PerTenantOverrideConfig string `yaml:"per_tenant_override_config" json:"per_tenant_override_config"`
100-
PerTenantOverridePeriod time.Duration `yaml:"per_tenant_override_period" json:"per_tenant_override_period"`
99+
PerTenantOverrideConfig string `yaml:"per_tenant_override_config" json:"per_tenant_override_config"`
100+
PerTenantOverridePeriod model.Duration `yaml:"per_tenant_override_period" json:"per_tenant_override_period"`
101101
}
102102

103103
// RegisterFlags adds the flags required to config this to the given FlagSet
@@ -116,8 +116,10 @@ func (l *Limits) RegisterFlags(f *flag.FlagSet) {
116116
f.IntVar(&l.MaxLabelNamesPerSeries, "validation.max-label-names-per-series", 30, "Maximum number of label names per series.")
117117
f.IntVar(&l.MaxMetadataLength, "validation.max-metadata-length", 1024, "Maximum length accepted for metric metadata. Metadata refers to Metric Name, HELP and UNIT.")
118118
f.BoolVar(&l.RejectOldSamples, "validation.reject-old-samples", false, "Reject old samples.")
119-
f.DurationVar(&l.RejectOldSamplesMaxAge, "validation.reject-old-samples.max-age", 14*24*time.Hour, "Maximum accepted sample age before rejecting.")
120-
f.DurationVar(&l.CreationGracePeriod, "validation.create-grace-period", 10*time.Minute, "Duration which table will be created/deleted before/after it's needed; we won't accept sample from before this time.")
119+
_ = l.RejectOldSamplesMaxAge.Set("14d")
120+
f.Var(&l.RejectOldSamplesMaxAge, "validation.reject-old-samples.max-age", "Maximum accepted sample age before rejecting.")
121+
_ = l.CreationGracePeriod.Set("10m")
122+
f.Var(&l.CreationGracePeriod, "validation.create-grace-period", "Duration which table will be created/deleted before/after it's needed; we won't accept sample from before this time.")
121123
f.BoolVar(&l.EnforceMetricName, "validation.enforce-metric-name", true, "Enforce every sample has a metric name.")
122124
f.BoolVar(&l.EnforceMetadataMetricName, "validation.enforce-metadata-metric-name", true, "Enforce every metadata has a metric name.")
123125

@@ -135,22 +137,24 @@ func (l *Limits) RegisterFlags(f *flag.FlagSet) {
135137
f.IntVar(&l.MaxGlobalMetadataPerMetric, "ingester.max-global-metadata-per-metric", 0, "The maximum number of metadata per metric, across the cluster. 0 to disable.")
136138

137139
f.IntVar(&l.MaxChunksPerQuery, "store.query-chunk-limit", 2e6, "Maximum number of chunks that can be fetched in a single query. This limit is enforced when fetching chunks from the long-term storage. When running the Cortex chunks storage, this limit is enforced in the querier, while when running the Cortex blocks storage this limit is both enforced in the querier and store-gateway. 0 to disable.")
138-
f.DurationVar(&l.MaxQueryLength, "store.max-query-length", 0, "Limit the query time range (end - start time). This limit is enforced in the query-frontend (on the received query), in the querier (on the query possibly split by the query-frontend) and in the chunks storage. 0 to disable.")
140+
f.Var(&l.MaxQueryLength, "store.max-query-length", "Limit the query time range (end - start time). This limit is enforced in the query-frontend (on the received query), in the querier (on the query possibly split by the query-frontend) and in the chunks storage. 0 to disable.")
139141
f.Var(&l.MaxQueryLookback, "querier.max-query-lookback", "Limit how long back data (series and metadata) can be queried, up until <lookback> duration ago. This limit is enforced in the query-frontend, querier and ruler. If the requested time range is outside the allowed range, the request will not fail but will be manipulated to only query data within the allowed time range. 0 to disable.")
140142
f.IntVar(&l.MaxQueryParallelism, "querier.max-query-parallelism", 14, "Maximum number of split queries will be scheduled in parallel by the frontend.")
141143
f.IntVar(&l.CardinalityLimit, "store.cardinality-limit", 1e5, "Cardinality limit for index queries. This limit is ignored when running the Cortex blocks storage. 0 to disable.")
142-
f.DurationVar(&l.MaxCacheFreshness, "frontend.max-cache-freshness", 1*time.Minute, "Most recent allowed cacheable result per-tenant, to prevent caching very recent results that might still be in flux.")
144+
_ = l.MaxCacheFreshness.Set("1m")
145+
f.Var(&l.MaxCacheFreshness, "frontend.max-cache-freshness", "Most recent allowed cacheable result per-tenant, to prevent caching very recent results that might still be in flux.")
143146
f.IntVar(&l.MaxQueriersPerTenant, "frontend.max-queriers-per-tenant", 0, "Maximum number of queriers that can handle requests for a single tenant. If set to 0 or value higher than number of available queriers, *all* queriers will handle requests for the tenant. Each frontend (or query-scheduler, if used) will select the same set of queriers for the same tenant (given that all queriers are connected to all frontends / query-schedulers). This option only works with queriers connecting to the query-frontend / query-scheduler, not when using downstream URL.")
144147

145-
f.DurationVar(&l.RulerEvaluationDelay, "ruler.evaluation-delay-duration", 0, "Duration to delay the evaluation of rules to ensure the underlying metrics have been pushed to Cortex.")
148+
f.Var(&l.RulerEvaluationDelay, "ruler.evaluation-delay-duration", "Duration to delay the evaluation of rules to ensure the underlying metrics have been pushed to Cortex.")
146149
f.IntVar(&l.RulerTenantShardSize, "ruler.tenant-shard-size", 0, "The default tenant's shard size when the shuffle-sharding strategy is used by ruler. When this setting is specified in the per-tenant overrides, a value of 0 disables shuffle sharding for the tenant.")
147150
f.IntVar(&l.RulerMaxRulesPerRuleGroup, "ruler.max-rules-per-rule-group", 0, "Maximum number of rules per rule group per-tenant. 0 to disable.")
148151
f.IntVar(&l.RulerMaxRuleGroupsPerTenant, "ruler.max-rule-groups-per-tenant", 0, "Maximum number of rule groups per-tenant. 0 to disable.")
149152

150153
f.Var(&l.CompactorBlocksRetentionPeriod, "compactor.blocks-retention-period", "Delete blocks containing samples older than the specified retention period. 0 to disable.")
151154

152155
f.StringVar(&l.PerTenantOverrideConfig, "limits.per-user-override-config", "", "File name of per-user overrides. [deprecated, use -runtime-config.file instead]")
153-
f.DurationVar(&l.PerTenantOverridePeriod, "limits.per-user-override-period", 10*time.Second, "Period with which to reload the overrides. [deprecated, use -runtime-config.reload-period instead]")
156+
_ = l.PerTenantOverridePeriod.Set("10s")
157+
f.Var(&l.PerTenantOverridePeriod, "limits.per-user-override-period", "Period with which to reload the overrides. [deprecated, use -runtime-config.reload-period instead]")
154158

155159
// Store-gateway.
156160
f.IntVar(&l.StoreGatewayTenantShardSize, "store-gateway.tenant-shard-size", 0, "The default tenant's shard size when the shuffle-sharding strategy is used. Must be set when the store-gateway sharding is enabled with the shuffle-sharding strategy. When this setting is specified in the per-tenant overrides, a value of 0 disables shuffle sharding for the tenant.")
@@ -299,13 +303,13 @@ func (o *Overrides) RejectOldSamples(userID string) bool {
299303

300304
// RejectOldSamplesMaxAge returns the age at which samples should be rejected.
301305
func (o *Overrides) RejectOldSamplesMaxAge(userID string) time.Duration {
302-
return o.getOverridesForUser(userID).RejectOldSamplesMaxAge
306+
return time.Duration(o.getOverridesForUser(userID).RejectOldSamplesMaxAge)
303307
}
304308

305309
// CreationGracePeriod is misnamed, and actually returns how far into the future
306310
// we should accept samples.
307311
func (o *Overrides) CreationGracePeriod(userID string) time.Duration {
308-
return o.getOverridesForUser(userID).CreationGracePeriod
312+
return time.Duration(o.getOverridesForUser(userID).CreationGracePeriod)
309313
}
310314

311315
// MaxSeriesPerQuery returns the maximum number of series a query is allowed to hit.
@@ -350,13 +354,13 @@ func (o *Overrides) MaxQueryLookback(userID string) time.Duration {
350354

351355
// MaxQueryLength returns the limit of the length (in time) of a query.
352356
func (o *Overrides) MaxQueryLength(userID string) time.Duration {
353-
return o.getOverridesForUser(userID).MaxQueryLength
357+
return time.Duration(o.getOverridesForUser(userID).MaxQueryLength)
354358
}
355359

356360
// MaxCacheFreshness returns the period after which results are cacheable,
357361
// to prevent caching of very recent results.
358362
func (o *Overrides) MaxCacheFreshness(userID string) time.Duration {
359-
return o.getOverridesForUser(userID).MaxCacheFreshness
363+
return time.Duration(o.getOverridesForUser(userID).MaxCacheFreshness)
360364
}
361365

362366
// MaxQueriersPerUser returns the maximum number of queriers that can handle requests for this user.
@@ -417,7 +421,7 @@ func (o *Overrides) IngestionTenantShardSize(userID string) int {
417421

418422
// EvaluationDelay returns the rules evaluation delay for a given user.
419423
func (o *Overrides) EvaluationDelay(userID string) time.Duration {
420-
return o.getOverridesForUser(userID).RulerEvaluationDelay
424+
return time.Duration(o.getOverridesForUser(userID).RulerEvaluationDelay)
421425
}
422426

423427
// CompactorBlocksRetentionPeriod returns the retention period for a given user.

pkg/util/validation/limits_test.go

+36-2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,40 @@ func TestLimitsTagsYamlMatchJson(t *testing.T) {
148148
assert.Empty(t, mismatch, "expected no mismatched JSON and YAML tags")
149149
}
150150

151+
func TestLimitsStringDurationYamlMatchJson(t *testing.T) {
152+
inputYAML := `
153+
max_query_lookback: 1s
154+
max_query_length: 1s
155+
`
156+
inputJSON := `{"max_query_lookback": "1s", "max_query_length": "1s"}`
157+
158+
limitsYAML := Limits{}
159+
err := yaml.Unmarshal([]byte(inputYAML), &limitsYAML)
160+
require.NoError(t, err, "expected to be able to unmarshal from YAML")
161+
162+
limitsJSON := Limits{}
163+
err = json.Unmarshal([]byte(inputJSON), &limitsJSON)
164+
require.NoError(t, err, "expected to be able to unmarshal from JSON")
165+
166+
assert.Equal(t, limitsYAML, limitsJSON)
167+
}
168+
169+
func TestLimitsAlwaysUsesPromDuration(t *testing.T) {
170+
stdlibDuration := reflect.TypeOf(time.Duration(0))
171+
limits := reflect.TypeOf(Limits{})
172+
n := limits.NumField()
173+
var badDurationType []string
174+
175+
for i := 0; i < n; i++ {
176+
field := limits.Field(i)
177+
if field.Type == stdlibDuration {
178+
badDurationType = append(badDurationType, field.Name)
179+
}
180+
}
181+
182+
assert.Empty(t, badDurationType, "some Limits fields are using stdlib time.Duration instead of model.Duration")
183+
}
184+
151185
func TestMetricRelabelConfigLimitsLoadingFromYaml(t *testing.T) {
152186
SetDefaultLimitsForYAMLUnmarshalling(Limits{})
153187

@@ -238,10 +272,10 @@ func TestSmallestPositiveNonZeroIntPerTenant(t *testing.T) {
238272
func TestSmallestPositiveNonZeroDurationPerTenant(t *testing.T) {
239273
tenantLimits := map[string]*Limits{
240274
"tenant-a": {
241-
MaxQueryLength: time.Hour,
275+
MaxQueryLength: model.Duration(time.Hour),
242276
},
243277
"tenant-b": {
244-
MaxQueryLength: 4 * time.Hour,
278+
MaxQueryLength: model.Duration(4 * time.Hour),
245279
},
246280
}
247281

0 commit comments

Comments
 (0)