Skip to content

Commit bdfc33b

Browse files
committed
feat(runtime-config): Add default tenant ID for runtime config fallback
Add -runtime-config.default-tenant-id flag that enables a synthetic tenant entry in the runtime config overrides to serve as the default for all tenants without per-tenant overrides. This allows changing default limits via the config pipeline (hot-reloaded) instead of requiring a Cortex restart. Resolution order: per-tenant override → default tenant → CLI flags. When the flag is empty (default), the feature is disabled and behavior is unchanged. This is an experimental feature. Signed-off-by: Ben Ye <benye@amazon.com>
1 parent 8ec34d8 commit bdfc33b

7 files changed

Lines changed: 118 additions & 3 deletions

File tree

docs/configuration/config-file-reference.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6382,6 +6382,12 @@ filesystem:
63826382
# Local filesystem storage directory.
63836383
# CLI flag: -runtime-config.filesystem.dir
63846384
[dir: <string> | default = ""]
6385+
6386+
# [Experimental] Synthetic tenant ID used as fallback for runtime config
6387+
# defaults. When set, overrides for this tenant ID apply to all tenants without
6388+
# per-tenant overrides, before falling back to CLI flag defaults.
6389+
# CLI flag: -runtime-config.default-tenant-id
6390+
[default_tenant_id: <string> | default = ""]
63856391
```
63866392
63876393
### `s3_sse_config`

docs/configuration/v1-guarantees.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,5 @@ Currently experimental features are:
133133
- Ingester: Active Series Tracker
134134
- Per-tenant `active_series_trackers` configuration in runtime config overrides
135135
- Counts active series matching PromQL label matchers and exposes `cortex_ingester_active_series_per_tracker` metric
136+
- Runtime Config: Default Tenant ID
137+
- `-runtime-config.default-tenant-id` (string) - Synthetic tenant ID used as fallback for runtime config defaults. Overrides for this tenant apply to all tenants without per-tenant overrides.

pkg/cortex/modules.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ func (t *Cortex) initRuntimeConfig() (services.Service, error) {
206206
}
207207

208208
func (t *Cortex) initOverridesConfig() (services.Service, error) {
209-
t.OverridesConfig = validation.NewOverrides(t.Cfg.LimitsConfig, t.TenantLimits)
209+
t.OverridesConfig = validation.NewOverridesWithDefaultTenantID(t.Cfg.LimitsConfig, t.TenantLimits, t.Cfg.RuntimeConfig.DefaultTenantID)
210210
// overrides don't have operational state, nor do they need to do anything more in starting/stopping phase,
211211
// so there is no need to return any service.
212212
return nil, nil

pkg/util/runtimeconfig/manager.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,19 @@ type Config struct {
3636
Loader Loader `yaml:"-"`
3737

3838
StorageConfig bucket.Config `yaml:",inline"`
39+
40+
// DefaultTenantID is the synthetic tenant ID used as a fallback for default
41+
// runtime config values. When set, overrides for this tenant ID are applied
42+
// to all tenants that do not have their own per-tenant override, before
43+
// falling back to CLI flag defaults. This is an experimental feature.
44+
DefaultTenantID string `yaml:"default_tenant_id"`
3945
}
4046

4147
// RegisterFlags registers flags.
4248
func (mc *Config) RegisterFlags(f *flag.FlagSet) {
4349
f.StringVar(&mc.LoadPath, "runtime-config.file", "", "File with the configuration that can be updated in runtime.")
4450
f.DurationVar(&mc.ReloadPeriod, "runtime-config.reload-period", 10*time.Second, "How often to check runtime config file.")
51+
f.StringVar(&mc.DefaultTenantID, "runtime-config.default-tenant-id", "", "[Experimental] Synthetic tenant ID used as fallback for runtime config defaults. When set, overrides for this tenant ID apply to all tenants without per-tenant overrides, before falling back to CLI flag defaults.")
4552

4653
mc.StorageConfig.RegisterFlagsWithPrefixAndBackend("runtime-config.", f, bucket.Filesystem)
4754
}

pkg/util/validation/limits.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -690,8 +690,9 @@ type TenantLimits interface {
690690
// Overrides periodically fetch a set of per-user overrides, and provides convenience
691691
// functions for fetching the correct value.
692692
type Overrides struct {
693-
defaultLimits *Limits
694-
tenantLimits TenantLimits
693+
defaultLimits *Limits
694+
tenantLimits TenantLimits
695+
defaultTenantID string
695696
}
696697

697698
// NewOverrides makes a new Overrides.
@@ -702,6 +703,18 @@ func NewOverrides(defaults Limits, tenantLimits TenantLimits) *Overrides {
702703
}
703704
}
704705

706+
// NewOverridesWithDefaultTenantID creates Overrides with a synthetic default tenant ID.
707+
// When a tenant has no per-tenant override and defaultTenantID is non-empty,
708+
// the overrides for defaultTenantID are used before falling back to CLI flag defaults.
709+
// This is an experimental feature.
710+
func NewOverridesWithDefaultTenantID(defaults Limits, tenantLimits TenantLimits, defaultTenantID string) *Overrides {
711+
return &Overrides{
712+
tenantLimits: tenantLimits,
713+
defaultLimits: &defaults,
714+
defaultTenantID: defaultTenantID,
715+
}
716+
}
717+
705718
// IngestionRate returns the limit on ingester rate (samples per second).
706719
func (o *Overrides) IngestionRate(userID string) float64 {
707720
return o.GetOverridesForUser(userID).IngestionRate
@@ -1288,6 +1301,12 @@ func (o *Overrides) GetOverridesForUser(userID string) *Limits {
12881301
if l != nil {
12891302
return l
12901303
}
1304+
if o.defaultTenantID != "" && userID != o.defaultTenantID {
1305+
l = o.tenantLimits.ByUserID(o.defaultTenantID)
1306+
if l != nil {
1307+
return l
1308+
}
1309+
}
12911310
}
12921311
return o.defaultLimits
12931312
}

pkg/util/validation/limits_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,82 @@ func TestOverridesManager_GetOverrides(t *testing.T) {
266266
require.Equal(t, 0, ov.MaxLabelsSizeBytes("user2"))
267267
}
268268

269+
func TestOverrides_DefaultTenantFallback(t *testing.T) {
270+
defaultTenantID := "__default__"
271+
272+
tenantLimits := map[string]*Limits{}
273+
274+
cliDefaults := Limits{
275+
IngestionRate: 10000,
276+
MaxGlobalSeriesPerUser: 100000,
277+
MaxLabelNamesPerSeries: 30,
278+
}
279+
280+
// Set up default tenant override with higher ingestion rate
281+
defaultTenantOverride := cliDefaults
282+
defaultTenantOverride.IngestionRate = 50000
283+
defaultTenantOverride.MaxGlobalSeriesPerUser = 500000
284+
tenantLimits[defaultTenantID] = &defaultTenantOverride
285+
286+
ov := NewOverridesWithDefaultTenantID(cliDefaults, newMockTenantLimits(tenantLimits), defaultTenantID)
287+
288+
// Tenant without override should get default tenant values
289+
require.Equal(t, float64(50000), ov.IngestionRate("user1"))
290+
require.Equal(t, 500000, ov.MaxGlobalSeriesPerUser("user1"))
291+
require.Equal(t, 30, ov.MaxLabelNamesPerSeries("user1"))
292+
293+
// Add a per-tenant override for user2
294+
user2Limits := cliDefaults
295+
user2Limits.IngestionRate = 200000
296+
tenantLimits["user2"] = &user2Limits
297+
298+
// user2 should get its own override, not the default tenant
299+
require.Equal(t, float64(200000), ov.IngestionRate("user2"))
300+
// user2's MaxGlobalSeriesPerUser comes from its own override (which copied cliDefaults)
301+
require.Equal(t, 100000, ov.MaxGlobalSeriesPerUser("user2"))
302+
303+
// The default tenant itself should get its own limits (not recurse)
304+
require.Equal(t, float64(50000), ov.IngestionRate(defaultTenantID))
305+
require.Equal(t, 500000, ov.MaxGlobalSeriesPerUser(defaultTenantID))
306+
}
307+
308+
func TestOverrides_DefaultTenantDisabled(t *testing.T) {
309+
tenantLimits := map[string]*Limits{}
310+
311+
cliDefaults := Limits{
312+
IngestionRate: 10000,
313+
MaxGlobalSeriesPerUser: 100000,
314+
}
315+
316+
// Default tenant override exists in the map but feature is disabled (empty ID)
317+
defaultOverride := cliDefaults
318+
defaultOverride.IngestionRate = 50000
319+
tenantLimits["__default__"] = &defaultOverride
320+
321+
ov := NewOverridesWithDefaultTenantID(cliDefaults, newMockTenantLimits(tenantLimits), "")
322+
323+
// With empty defaultTenantID, feature is disabled — should get CLI defaults
324+
require.Equal(t, float64(10000), ov.IngestionRate("user1"))
325+
require.Equal(t, 100000, ov.MaxGlobalSeriesPerUser("user1"))
326+
}
327+
328+
func TestOverrides_DefaultTenantNotInMap(t *testing.T) {
329+
tenantLimits := map[string]*Limits{}
330+
331+
cliDefaults := Limits{
332+
IngestionRate: 10000,
333+
MaxGlobalSeriesPerUser: 100000,
334+
}
335+
336+
// Feature enabled but no __default__ entry in runtime config
337+
ov := NewOverridesWithDefaultTenantID(cliDefaults, newMockTenantLimits(tenantLimits), "__default__")
338+
339+
// Should fall through to CLI defaults
340+
require.Equal(t, float64(10000), ov.IngestionRate("user1"))
341+
require.Equal(t, 100000, ov.MaxGlobalSeriesPerUser("user1"))
342+
}
343+
344+
269345
func TestLimitsLoadingFromYaml(t *testing.T) {
270346
SetDefaultLimitsForYAMLUnmarshalling(Limits{
271347
MaxLabelNameLength: 100,

schemas/cortex-config-schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7810,6 +7810,11 @@
78107810
"type": "string",
78117811
"x-cli-flag": "runtime-config.backend"
78127812
},
7813+
"default_tenant_id": {
7814+
"description": "[Experimental] Synthetic tenant ID used as fallback for runtime config defaults. When set, overrides for this tenant ID apply to all tenants without per-tenant overrides, before falling back to CLI flag defaults.",
7815+
"type": "string",
7816+
"x-cli-flag": "runtime-config.default-tenant-id"
7817+
},
78137818
"file": {
78147819
"description": "File with the configuration that can be updated in runtime.",
78157820
"type": "string",

0 commit comments

Comments
 (0)