From 2af40d71c4f69cba0368ebf09260198f2cb9f25e Mon Sep 17 00:00:00 2001 From: Essam Eldaly Date: Mon, 13 Jul 2026 15:38:03 -0700 Subject: [PATCH] feat(limits): add per-tenant metric_name_shard_size limit Introduce the metric_name_shard_size per-tenant limit and the GetMetricNameShardSize accessor on the tripperware Limits interface, implemented by validation.Overrides. Update the interface mocks and the overrides exporter test fixture accordingly. This is the configuration foundation for distributed query execution (DQE) metric-name sharding. --- .../instantquery/instant_query_middlewares_test.go | 4 ++++ pkg/querier/tripperware/limits.go | 4 ++++ pkg/querier/tripperware/queryrange/limits_test.go | 4 ++++ pkg/querier/tripperware/test_shard_by_query_utils.go | 4 ++++ pkg/util/validation/exporter_test.go | 1 + pkg/util/validation/limits.go | 7 +++++++ 6 files changed, 24 insertions(+) diff --git a/pkg/querier/tripperware/instantquery/instant_query_middlewares_test.go b/pkg/querier/tripperware/instantquery/instant_query_middlewares_test.go index 2118e737693..ac2aba44cb6 100644 --- a/pkg/querier/tripperware/instantquery/instant_query_middlewares_test.go +++ b/pkg/querier/tripperware/instantquery/instant_query_middlewares_test.go @@ -270,6 +270,10 @@ func (m mockLimitsShard) QueryVerticalShardSize(userID string) int { return m.shardSize } +func (m mockLimitsShard) GetMetricNameShardSize(userID string) int { + return m.shardSize +} + func (m mockLimitsShard) QueryPriority(userID string) validation.QueryPriority { return m.queryPriority } diff --git a/pkg/querier/tripperware/limits.go b/pkg/querier/tripperware/limits.go index 546422974e2..059814a2b1b 100644 --- a/pkg/querier/tripperware/limits.go +++ b/pkg/querier/tripperware/limits.go @@ -42,6 +42,10 @@ type Limits interface { // QueryVerticalShardSize returns the maximum number of queriers that can handle requests for this user. QueryVerticalShardSize(userID string) int + // GetMetricNameShardSize returns the number of metric-name shards configured + // for this user (matches the compactor's metric-name-shard-size). + GetMetricNameShardSize(userID string) int + // QueryPriority returns the query priority config for the tenant, including different priorities and their attributes. QueryPriority(userID string) validation.QueryPriority diff --git a/pkg/querier/tripperware/queryrange/limits_test.go b/pkg/querier/tripperware/queryrange/limits_test.go index 687a3a1359d..83d01aceed4 100644 --- a/pkg/querier/tripperware/queryrange/limits_test.go +++ b/pkg/querier/tripperware/queryrange/limits_test.go @@ -266,6 +266,10 @@ func (m mockLimits) QueryVerticalShardSize(userID string) int { return m.queryVerticalShardSize } +func (m mockLimits) GetMetricNameShardSize(userID string) int { + return 0 +} + func (m mockLimits) QueryPriority(userID string) validation.QueryPriority { return validation.QueryPriority{} } diff --git a/pkg/querier/tripperware/test_shard_by_query_utils.go b/pkg/querier/tripperware/test_shard_by_query_utils.go index 65c0e12f3ed..817d88772ef 100644 --- a/pkg/querier/tripperware/test_shard_by_query_utils.go +++ b/pkg/querier/tripperware/test_shard_by_query_utils.go @@ -507,6 +507,10 @@ func (m mockLimits) QueryVerticalShardSize(userID string) int { return m.shardSize } +func (m mockLimits) GetMetricNameShardSize(userID string) int { + return m.shardSize +} + func (m mockLimits) QueryPriority(userID string) validation.QueryPriority { return m.queryPriority } diff --git a/pkg/util/validation/exporter_test.go b/pkg/util/validation/exporter_test.go index 6067ed96067..b0a2c27c2b3 100644 --- a/pkg/util/validation/exporter_test.go +++ b/pkg/util/validation/exporter_test.go @@ -96,6 +96,7 @@ func TestOverridesExporter_withConfig(t *testing.T) { cortex_overrides{limit_name="max_series_per_metric",user="tenant-a"} 50000 cortex_overrides{limit_name="max_series_per_user",user="tenant-a"} 5e+06 cortex_overrides{limit_name="max_total_label_value_length_for_unoptimized_regex",user="tenant-a"} 0 + cortex_overrides{limit_name="metric_name_shard_size",user="tenant-a"} 0 cortex_overrides{limit_name="native_histogram_ingestion_burst_size",user="tenant-a"} 0 cortex_overrides{limit_name="native_histogram_ingestion_rate",user="tenant-a"} 1.7976931348623157e+308 cortex_overrides{limit_name="out_of_order_results_cache_ttl",user="tenant-a"} 0 diff --git a/pkg/util/validation/limits.go b/pkg/util/validation/limits.go index 019a5adc3ed..c43c3e030a7 100644 --- a/pkg/util/validation/limits.go +++ b/pkg/util/validation/limits.go @@ -199,6 +199,7 @@ type Limits struct { OutOfOrderResultsCacheTTL model.Duration `yaml:"out_of_order_results_cache_ttl" json:"out_of_order_results_cache_ttl"` MaxQueriersPerTenant float64 `yaml:"max_queriers_per_tenant" json:"max_queriers_per_tenant"` QueryVerticalShardSize int `yaml:"query_vertical_shard_size" json:"query_vertical_shard_size"` + MetricNameShardSize int `yaml:"metric_name_shard_size" json:"metric_name_shard_size"` QueryPartialData bool `yaml:"query_partial_data" json:"query_partial_data" doc:"nocli|description=Enable to allow queries to be evaluated with data from a single zone, if other zones are not available.|default=false"` QueryIngestersWithin model.Duration `yaml:"query_ingesters_within" json:"query_ingesters_within"` @@ -348,6 +349,7 @@ func (l *Limits) RegisterFlags(f *flag.FlagSet) { f.Var(&l.OutOfOrderResultsCacheTTL, "frontend.out-of-order-results-cache-ttl", "Per-tenant TTL for cached query results that overlap with the out-of-order time window. These results may still receive out-of-order samples, so they typically use a shorter TTL. 0 (default) means use the global cache backend TTL configuration.") f.Float64Var(&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. If the value is < 1, it will be treated as a percentage and the gets a percentage of the total queriers. 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.") f.IntVar(&l.QueryVerticalShardSize, "frontend.query-vertical-shard-size", 0, "[Experimental] Number of shards to use when distributing shardable PromQL queries.") + f.IntVar(&l.MetricNameShardSize, "compactor.metric-name-shard-size", 0, "Shard by metric name shard size.") f.BoolVar(&l.QueryPriority.Enabled, "frontend.query-priority.enabled", false, "Whether queries are assigned with priorities.") f.Int64Var(&l.QueryPriority.DefaultPriority, "frontend.query-priority.default-priority", 0, "Priority assigned to all queries by default. Must be a unique value. Use this as a baseline to make certain queries higher/lower priority.") f.BoolVar(&l.QueryRejection.Enabled, "frontend.query-rejection.enabled", false, "Whether query rejection is enabled.") @@ -931,6 +933,11 @@ func (o *Overrides) QueryVerticalShardSize(userID string) int { return o.GetOverridesForUser(userID).QueryVerticalShardSize } +// GetMetricNameShardSize returns the number of metric-name shards configured for this user. +func (o *Overrides) GetMetricNameShardSize(userID string) int { + return o.GetOverridesForUser(userID).MetricNameShardSize +} + // QueryPartialData returns whether query may be evaluated with data from a single zone, if other zones are not available. func (o *Overrides) QueryPartialData(userID string) bool { return o.GetOverridesForUser(userID).QueryPartialData