From 50cf911fb769cf472a010ec870086eb7897d1cfe Mon Sep 17 00:00:00 2001 From: Anton Kolesnikov Date: Fri, 7 Jun 2024 16:28:12 +0800 Subject: [PATCH] feat: optimize series order --- cmd/pyroscope/help-all.txt.tmpl | 2 + cmd/pyroscope/help.txt.tmpl | 2 + pkg/distributor/distributor.go | 12 +- pkg/distributor/distributor_test.go | 3 +- pkg/model/labels.go | 108 ++++++++++---- pkg/model/labels_test.go | 140 +++++++++++++++++- pkg/phlaredb/head.go | 5 +- pkg/phlaredb/labels/labels.go | 17 ++- pkg/phlaredb/labels/labels_test.go | 18 +-- pkg/phlaredb/schemas/v1/testhelper/profile.go | 2 +- pkg/validation/limits.go | 6 + 11 files changed, 257 insertions(+), 58 deletions(-) diff --git a/cmd/pyroscope/help-all.txt.tmpl b/cmd/pyroscope/help-all.txt.tmpl index 0b0c0de789..bf47cbe097 100644 --- a/cmd/pyroscope/help-all.txt.tmpl +++ b/cmd/pyroscope/help-all.txt.tmpl @@ -1047,6 +1047,8 @@ Usage of ./pyroscope: [experimental] Set to true to enable profiling integration. -usage-stats.enabled Enable anonymous usage reporting. (default true) + -validation.enforce-labels-order + Enforce labels order optimization. -validation.max-label-names-per-series int Maximum number of label names per series. (default 30) -validation.max-length-label-name int diff --git a/cmd/pyroscope/help.txt.tmpl b/cmd/pyroscope/help.txt.tmpl index e949ba0ea2..ad077e714e 100644 --- a/cmd/pyroscope/help.txt.tmpl +++ b/cmd/pyroscope/help.txt.tmpl @@ -381,6 +381,8 @@ Usage of ./pyroscope: Set to false to disable tracing. (default true) -usage-stats.enabled Enable anonymous usage reporting. (default true) + -validation.enforce-labels-order + Enforce labels order optimization. -validation.max-label-names-per-series int Maximum number of label names per series. (default 30) -validation.max-length-label-name int diff --git a/pkg/distributor/distributor.go b/pkg/distributor/distributor.go index 8c1611f80e..e2331145b4 100644 --- a/pkg/distributor/distributor.go +++ b/pkg/distributor/distributor.go @@ -122,6 +122,7 @@ type Limits interface { MaxProfileStacktraceDepth(tenantID string) int MaxProfileSymbolValueLength(tenantID string) int MaxSessionsPerSeries(tenantID string) int + EnforceLabelsOrder(tenantID string) bool validation.ProfileValidationLimits aggregator.Limits } @@ -393,8 +394,9 @@ func (d *Distributor) sendAggregatedProfile(ctx context.Context, req *distributo func (d *Distributor) sendRequests(ctx context.Context, req *distributormodel.PushRequest, tenantID string) (resp *connect.Response[pushv1.PushResponse], err error) { // Reduce cardinality of session_id label. + maxSessionsPerSeries := d.limits.MaxSessionsPerSeries(tenantID) for _, series := range req.Series { - series.Labels = d.limitMaxSessionsPerSeries(tenantID, series.Labels) + series.Labels = d.limitMaxSessionsPerSeries(maxSessionsPerSeries, series.Labels) } // Next we split profiles by labels. @@ -414,7 +416,12 @@ func (d *Distributor) sendRequests(ctx context.Context, req *distributormodel.Pu // Validate the labels again and generate tokens for shuffle sharding. keys := make([]uint32, len(profileSeries)) + enforceLabelsOrder := d.limits.EnforceLabelsOrder(tenantID) for i, series := range profileSeries { + if enforceLabelsOrder { + labels := phlaremodel.Labels(series.Labels) + labels.Insert(phlaremodel.LabelNameOrder, phlaremodel.LabelOrderEnforced) + } if err = validation.ValidateLabels(d.limits, tenantID, series.Labels); err != nil { validation.DiscardedProfiles.WithLabelValues(string(validation.ReasonOf(err)), tenantID).Add(float64(req.TotalProfiles)) validation.DiscardedBytes.WithLabelValues(string(validation.ReasonOf(err)), tenantID).Add(float64(req.TotalBytesUncompressed)) @@ -666,8 +673,7 @@ func extractSampleSeries(req *distributormodel.PushRequest) []*distributormodel. return profileSeries } -func (d *Distributor) limitMaxSessionsPerSeries(tenantID string, labels phlaremodel.Labels) phlaremodel.Labels { - maxSessionsPerSeries := d.limits.MaxSessionsPerSeries(tenantID) +func (d *Distributor) limitMaxSessionsPerSeries(maxSessionsPerSeries int, labels phlaremodel.Labels) phlaremodel.Labels { if maxSessionsPerSeries == 0 { return labels.Delete(phlaremodel.LabelNameSessionID) } diff --git a/pkg/distributor/distributor_test.go b/pkg/distributor/distributor_test.go index f78464adb7..acc0f9e5dd 100644 --- a/pkg/distributor/distributor_test.go +++ b/pkg/distributor/distributor_test.go @@ -415,7 +415,8 @@ func Test_Sessions_Limit(t *testing.T) { }), nil, log.NewLogfmtLogger(os.Stdout)) require.NoError(t, err) - assert.Equal(t, tc.expectedLabels, d.limitMaxSessionsPerSeries("user-1", tc.seriesLabels)) + limit := d.limits.MaxSessionsPerSeries("user-1") + assert.Equal(t, tc.expectedLabels, d.limitMaxSessionsPerSeries(limit, tc.seriesLabels)) }) } } diff --git a/pkg/model/labels.go b/pkg/model/labels.go index 191a77e7d8..f66de3a9ab 100644 --- a/pkg/model/labels.go +++ b/pkg/model/labels.go @@ -22,21 +22,24 @@ import ( var seps = []byte{'\xff'} const ( - LabelNameProfileType = "__profile_type__" - LabelNameType = "__type__" - LabelNameUnit = "__unit__" - LabelNamePeriodType = "__period_type__" - LabelNamePeriodUnit = "__period_unit__" - LabelNameDelta = "__delta__" - LabelNameProfileName = pmodel.MetricNameLabel - LabelNameSessionID = "__session_id__" + LabelNameProfileType = "__profile_type__" + LabelNameServiceNamePrivate = "__service_name__" + LabelNameDelta = "__delta__" + LabelNameProfileName = pmodel.MetricNameLabel + LabelNamePeriodType = "__period_type__" + LabelNamePeriodUnit = "__period_unit__" + LabelNameSessionID = "__session_id__" + LabelNameType = "__type__" + LabelNameUnit = "__unit__" + LabelNameServiceGitRef = "service_git_ref" LabelNameServiceName = "service_name" LabelNameServiceRepository = "service_repository" - LabelNameServiceGitRef = "service_git_ref" - LabelNamePyroscopeSpy = "pyroscope_spy" - LabelNameServiceNameK8s = "__meta_kubernetes_pod_annotation_pyroscope_io_service_name" + LabelNameOrder = "__order__" + LabelOrderEnforced = "enforced" + + LabelNamePyroscopeSpy = "pyroscope_spy" labelSep = '\xfe' ) @@ -49,6 +52,30 @@ func (ls Labels) Len() int { return len(ls) } func (ls Labels) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] } func (ls Labels) Less(i, j int) bool { return ls[i].Name < ls[j].Name } +// LabelsEnforcedOrder is a sort order of labels, where profile type and +// service name labels always go first. This is crucial for query performance +// as labels determine the physical order of the profiling data. +type LabelsEnforcedOrder []*typesv1.LabelPair + +func (ls LabelsEnforcedOrder) Len() int { return len(ls) } +func (ls LabelsEnforcedOrder) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] } + +func (ls LabelsEnforcedOrder) Less(i, j int) bool { + if ls[i].Name[0] == '_' || ls[j].Name[0] == '_' { + leftType := ls[i].Name == LabelNameProfileType + rightType := ls[j].Name == LabelNameProfileType + if leftType || rightType { + return leftType || !rightType + } + leftService := ls[i].Name == LabelNameServiceNamePrivate + rightService := ls[j].Name == LabelNameServiceNamePrivate + if leftService || rightService { + return leftService || !rightService + } + } + return ls[i].Name < ls[j].Name +} + // Hash returns a hash value for the label set. func (ls Labels) Hash() uint64 { // Use xxhash.Sum64(b) for fast path as it's faster. @@ -192,14 +219,41 @@ func (ls Labels) GetLabel(name string) (*typesv1.LabelPair, bool) { return nil, false } -// Delete removes the first label encountered with the name given. -// A copy of the label set without the label is returned. +// Delete removes the first label encountered with the name given in place. func (ls Labels) Delete(name string) Labels { return slices.RemoveInPlace(ls, func(pair *typesv1.LabelPair, i int) bool { return pair.Name == name }) } +// Insert adds the given label to the set of labels. +// It assumes the labels are ordered +func (ls *Labels) Insert(name, value string) { + // Find the index where the new label should be inserted + index := -1 + for i, label := range *ls { + if label.Name > name { + index = i + break + } + if label.Name == name { + label.Value = value + return + } + } + // Insert the new label at the found index + l := &typesv1.LabelPair{ + Name: name, + Value: value, + } + *ls = append(*ls, l) + if index == -1 { + return + } + copy((*ls)[index+1:], (*ls)[index:]) + (*ls)[index] = l +} + func (ls Labels) Clone() Labels { result := make(Labels, len(ls)) for i, l := range ls { @@ -277,19 +331,7 @@ func LabelsFromStrings(ss ...string) Labels { return res } -// CloneLabelPairs clones the label pairs. -func CloneLabelPairs(lbs []*typesv1.LabelPair) []*typesv1.LabelPair { - result := make([]*typesv1.LabelPair, len(lbs)) - for i, l := range lbs { - result[i] = &typesv1.LabelPair{ - Name: l.Name, - Value: l.Value, - } - } - return result -} - -// Compare compares the two label sets. +// CompareLabelPairs compares the two label sets. // The result will be 0 if a==b, <0 if a < b, and >0 if a > b. func CompareLabelPairs(a []*typesv1.LabelPair, b []*typesv1.LabelPair) int { l := len(a) @@ -377,6 +419,16 @@ func (b *LabelsBuilder) Set(n, v string) *LabelsBuilder { // Labels returns the labels from the builder. If no modifications // were made, the original labels are returned. func (b *LabelsBuilder) Labels() Labels { + res := b.LabelsUnsorted() + sort.Sort(res) + return res +} + +// LabelsUnsorted returns the labels from the builder. If no modifications +// were made, the original labels are returned. +// +// The order is not deterministic. +func (b *LabelsBuilder) LabelsUnsorted() Labels { if len(b.del) == 0 && len(b.add) == 0 { return b.base } @@ -398,10 +450,8 @@ Outer: } res = append(res, l) } - res = append(res, b.add...) - sort.Sort(res) - return res + return append(res, b.add...) } // StableHash is a labels hashing implementation which is guaranteed to not change over time. diff --git a/pkg/model/labels_test.go b/pkg/model/labels_test.go index 3395f238df..89380d1d79 100644 --- a/pkg/model/labels_test.go +++ b/pkg/model/labels_test.go @@ -6,6 +6,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" ) func TestLabelsUnique(t *testing.T) { @@ -67,21 +69,20 @@ func TestLabelsUnique(t *testing.T) { } func TestLabels_SessionID_Order(t *testing.T) { - const serviceNameLabel = "__service_name__" input := []Labels{ { {Name: LabelNameSessionID, Value: "session-a"}, {Name: LabelNameProfileType, Value: "cpu"}, - {Name: serviceNameLabel, Value: "service-name"}, + {Name: LabelNameServiceNamePrivate, Value: "service-name"}, }, { {Name: LabelNameSessionID, Value: "session-b"}, {Name: LabelNameProfileType, Value: "cpu"}, - {Name: serviceNameLabel, Value: "service-name"}, + {Name: LabelNameServiceNamePrivate, Value: "service-name"}, }, } for _, x := range input { - sort.Sort(x) + sort.Sort(LabelsEnforcedOrder(x)) } sort.Slice(input, func(i, j int) bool { return CompareLabelPairs(input[i], input[j]) < 0 @@ -90,11 +91,11 @@ func TestLabels_SessionID_Order(t *testing.T) { expectedOrder := []Labels{ { {Name: LabelNameProfileType, Value: "cpu"}, - {Name: serviceNameLabel, Value: "service-name"}, + {Name: LabelNameServiceNamePrivate, Value: "service-name"}, {Name: LabelNameSessionID, Value: "session-a"}, }, { {Name: LabelNameProfileType, Value: "cpu"}, - {Name: serviceNameLabel, Value: "service-name"}, + {Name: LabelNameServiceNamePrivate, Value: "service-name"}, {Name: LabelNameSessionID, Value: "session-b"}, }, } @@ -133,3 +134,130 @@ func Test_SessionID_Parse(t *testing.T) { _, err = ParseSessionID("not-a-session-id-either") assert.NotNil(t, err) } + +func TestLabels_LabelsEnforcedOrder(t *testing.T) { + labels := []*typesv1.LabelPair{ + {Name: "foo", Value: "bar"}, + {Name: LabelNameProfileType, Value: "cpu"}, + {Name: "__request_id__", Value: "mess"}, + {Name: LabelNameServiceNamePrivate, Value: "service"}, + {Name: "Alarm", Value: "Order"}, + } + + expected := Labels{ + {Name: LabelNameProfileType, Value: "cpu"}, + {Name: LabelNameServiceNamePrivate, Value: "service"}, + {Name: "Alarm", Value: "Order"}, + {Name: "__request_id__", Value: "mess"}, + {Name: "foo", Value: "bar"}, + } + + permute(labels, func(x []*typesv1.LabelPair) { + sort.Sort(LabelsEnforcedOrder(x)) + assert.Equal(t, LabelPairsString(expected), LabelPairsString(labels)) + }) +} + +func permute[T any](s []T, f func([]T)) { + n := len(s) + stack := make([]int, n) + f(s) + i := 0 + for i < n { + if stack[i] < i { + if i%2 == 0 { + s[0], s[i] = s[i], s[0] + } else { + s[stack[i]], s[i] = s[i], s[stack[i]] + } + f(s) + stack[i]++ + i = 0 + } else { + stack[i] = 0 + i++ + } + } +} + +func TestInsert(t *testing.T) { + tests := []struct { + name string + labels Labels + insertName string + insertValue string + expected Labels + }{ + { + name: "Insert into empty slice", + labels: Labels{}, + insertName: "foo", + insertValue: "bar", + expected: Labels{ + {Name: "foo", Value: "bar"}, + }, + }, + { + name: "Insert at the beginning", + labels: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + insertName: "alice", + insertValue: "bob", + expected: Labels{ + {Name: "alice", Value: "bob"}, + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + }, + { + name: "Insert in the middle", + labels: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + insertName: "foo", + insertValue: "bar", + expected: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "foo", Value: "bar"}, + {Name: "quux", Value: "corge"}, + }, + }, + { + name: "Insert at the end", + labels: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + insertName: "xyz", + insertValue: "123", + expected: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + {Name: "xyz", Value: "123"}, + }, + }, + { + name: "Update existing label", + labels: Labels{ + {Name: "baz", Value: "qux"}, + {Name: "quux", Value: "corge"}, + }, + insertName: "baz", + insertValue: "updated_value", + expected: Labels{ + {Name: "baz", Value: "updated_value"}, + {Name: "quux", Value: "corge"}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + test.labels.Insert(test.insertName, test.insertValue) + assert.Equal(t, test.expected, test.labels) + }) + } +} diff --git a/pkg/phlaredb/head.go b/pkg/phlaredb/head.go index b82d5c4904..d5f18b3e2b 100644 --- a/pkg/phlaredb/head.go +++ b/pkg/phlaredb/head.go @@ -187,7 +187,10 @@ func (h *Head) Ingest(ctx context.Context, p *profilev1.Profile, id uuid.UUID, e delta := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameDelta) != "false" externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameDelta) - lbls, seriesFingerprints := phlarelabels.CreateProfileLabels(p, externalLabels...) + enforceLabelOrder := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameOrder) == phlaremodel.LabelOrderEnforced + externalLabels = phlaremodel.Labels(externalLabels).Delete(phlaremodel.LabelNameOrder) + + lbls, seriesFingerprints := phlarelabels.CreateProfileLabels(enforceLabelOrder, p, externalLabels...) for i, fp := range seriesFingerprints { if err := h.limiter.AllowProfile(fp, lbls[i], p.TimeNanos); err != nil { diff --git a/pkg/phlaredb/labels/labels.go b/pkg/phlaredb/labels/labels.go index fb931ad160..405955abde 100644 --- a/pkg/phlaredb/labels/labels.go +++ b/pkg/phlaredb/labels/labels.go @@ -1,7 +1,7 @@ package labels import ( - "fmt" + "sort" "strings" "github.com/prometheus/common/model" @@ -11,9 +11,7 @@ import ( phlaremodel "github.com/grafana/pyroscope/pkg/model" ) -var labelNameServiceName = fmt.Sprintf("__%s__", phlaremodel.LabelNameServiceName) - -func CreateProfileLabels(p *profilev1.Profile, externalLabels ...*typesv1.LabelPair) ([]phlaremodel.Labels, []model.Fingerprint) { +func CreateProfileLabels(enforceOrder bool, p *profilev1.Profile, externalLabels ...*typesv1.LabelPair) ([]phlaremodel.Labels, []model.Fingerprint) { // build label set per sample type before references are rewritten var ( sb strings.Builder @@ -24,8 +22,8 @@ func CreateProfileLabels(p *profilev1.Profile, externalLabels ...*typesv1.LabelP // Inject into labels the __service_name__ label if it exists // This allows better locality of the data in parquet files (row group are sorted by). - if serviceName := lbls.Labels().Get(phlaremodel.LabelNameServiceName); serviceName != "" { - lbls.Set(labelNameServiceName, serviceName) + if serviceName := phlaremodel.Labels(externalLabels).Get(phlaremodel.LabelNameServiceName); serviceName != "" { + lbls.Set(phlaremodel.LabelNameServiceNamePrivate, serviceName) } // set common labels @@ -56,7 +54,12 @@ func CreateProfileLabels(p *profilev1.Profile, externalLabels ...*typesv1.LabelP _, _ = sb.WriteString(periodUnit) t := sb.String() lbls.Set(phlaremodel.LabelNameProfileType, t) - lbs := lbls.Labels().Clone() + lbs := lbls.LabelsUnsorted().Clone() + if enforceOrder { + sort.Sort(phlaremodel.LabelsEnforcedOrder(lbs)) + } else { + sort.Sort(lbs) + } profilesLabels[pos] = lbs seriesRefs[pos] = model.Fingerprint(lbs.Hash()) diff --git a/pkg/phlaredb/labels/labels_test.go b/pkg/phlaredb/labels/labels_test.go index 2d7b6dc09a..aaf7786362 100644 --- a/pkg/phlaredb/labels/labels_test.go +++ b/pkg/phlaredb/labels/labels_test.go @@ -1,7 +1,6 @@ package labels import ( - "sort" "testing" "github.com/prometheus/common/model" @@ -21,12 +20,12 @@ func TestLabelsForProfiles(t *testing.T) { "default", phlaremodel.Labels{{Name: model.MetricNameLabel, Value: "cpu"}}, phlaremodel.Labels{ - {Name: model.MetricNameLabel, Value: "cpu"}, - {Name: phlaremodel.LabelNameUnit, Value: "unit"}, {Name: phlaremodel.LabelNameProfileType, Value: "cpu:type:unit:type:unit"}, - {Name: phlaremodel.LabelNameType, Value: "type"}, + {Name: model.MetricNameLabel, Value: "cpu"}, {Name: phlaremodel.LabelNamePeriodType, Value: "type"}, {Name: phlaremodel.LabelNamePeriodUnit, Value: "unit"}, + {Name: phlaremodel.LabelNameType, Value: "type"}, + {Name: phlaremodel.LabelNameUnit, Value: "unit"}, }, }, { @@ -36,21 +35,20 @@ func TestLabelsForProfiles(t *testing.T) { {Name: phlaremodel.LabelNameServiceName, Value: "service_name"}, }, phlaremodel.Labels{ - {Name: model.MetricNameLabel, Value: "cpu"}, - {Name: phlaremodel.LabelNameUnit, Value: "unit"}, {Name: phlaremodel.LabelNameProfileType, Value: "cpu:type:unit:type:unit"}, - {Name: phlaremodel.LabelNameType, Value: "type"}, + {Name: phlaremodel.LabelNameServiceNamePrivate, Value: "service_name"}, + {Name: model.MetricNameLabel, Value: "cpu"}, {Name: phlaremodel.LabelNamePeriodType, Value: "type"}, {Name: phlaremodel.LabelNamePeriodUnit, Value: "unit"}, - {Name: labelNameServiceName, Value: "service_name"}, + {Name: phlaremodel.LabelNameType, Value: "type"}, + {Name: phlaremodel.LabelNameUnit, Value: "unit"}, {Name: phlaremodel.LabelNameServiceName, Value: "service_name"}, }, }, } { tt := tt t.Run(tt.name, func(t *testing.T) { - sort.Sort(tt.expected) - result, fps := CreateProfileLabels(newProfileFoo(), tt.in...) + result, fps := CreateProfileLabels(true, newProfileFoo(), tt.in...) require.Equal(t, tt.expected, result[0]) require.Equal(t, model.Fingerprint(tt.expected.Hash()), fps[0]) }) diff --git a/pkg/phlaredb/schemas/v1/testhelper/profile.go b/pkg/phlaredb/schemas/v1/testhelper/profile.go index 0189cdf669..5829b44476 100644 --- a/pkg/phlaredb/schemas/v1/testhelper/profile.go +++ b/pkg/phlaredb/schemas/v1/testhelper/profile.go @@ -14,7 +14,7 @@ import ( func NewProfileSchema(p *profilev1.Profile, name string) ([]schemav1.InMemoryProfile, []phlaremodel.Labels) { var ( - lbls, seriesRefs = labels.CreateProfileLabels(p, &typesv1.LabelPair{Name: model.MetricNameLabel, Value: name}) + lbls, seriesRefs = labels.CreateProfileLabels(true, p, &typesv1.LabelPair{Name: model.MetricNameLabel, Value: name}) ps = make([]schemav1.InMemoryProfile, len(lbls)) ) for idxType := range lbls { diff --git a/pkg/validation/limits.go b/pkg/validation/limits.go index 64a9fd91b6..4d0131628b 100644 --- a/pkg/validation/limits.go +++ b/pkg/validation/limits.go @@ -33,6 +33,7 @@ type Limits struct { MaxLabelValueLength int `yaml:"max_label_value_length" json:"max_label_value_length"` MaxLabelNamesPerSeries int `yaml:"max_label_names_per_series" json:"max_label_names_per_series"` MaxSessionsPerSeries int `yaml:"max_sessions_per_series" json:"max_sessions_per_series"` + EnforceLabelsOrder bool `yaml:"enforce_labels_order" json:"enforce_labels_order"` MaxProfileSizeBytes int `yaml:"max_profile_size_bytes" json:"max_profile_size_bytes"` MaxProfileStacktraceSamples int `yaml:"max_profile_stacktrace_samples" json:"max_profile_stacktrace_samples"` @@ -109,6 +110,7 @@ func (l *Limits) RegisterFlags(f *flag.FlagSet) { f.IntVar(&l.MaxLabelValueLength, "validation.max-length-label-value", 2048, "Maximum length accepted for label value. This setting also applies to the metric name.") f.IntVar(&l.MaxLabelNamesPerSeries, "validation.max-label-names-per-series", 30, "Maximum number of label names per series.") f.IntVar(&l.MaxSessionsPerSeries, "validation.max-sessions-per-series", 0, "Maximum number of sessions per series. 0 to disable.") + f.BoolVar(&l.EnforceLabelsOrder, "validation.enforce-labels-order", false, "Enforce labels order optimization.") f.IntVar(&l.MaxLocalSeriesPerTenant, "ingester.max-local-series-per-tenant", 0, "Maximum number of active series of profiles per tenant, per ingester. 0 to disable.") f.IntVar(&l.MaxGlobalSeriesPerTenant, "ingester.max-global-series-per-tenant", 5000, "Maximum number of active series of profiles per tenant, across the cluster. 0 to disable. When the global limit is enabled, each ingester is configured with a dynamic local limit based on the replication factor and the current number of healthy ingesters, and is kept updated whenever the number of ingesters change.") @@ -286,6 +288,10 @@ func (o *Overrides) MaxSessionsPerSeries(tenantID string) int { return o.getOverridesForTenant(tenantID).MaxSessionsPerSeries } +func (o *Overrides) EnforceLabelsOrder(tenantID string) bool { + return o.getOverridesForTenant(tenantID).EnforceLabelsOrder +} + func (o *Overrides) DistributorAggregationWindow(tenantID string) model.Duration { return o.getOverridesForTenant(tenantID).DistributorAggregationWindow }