Skip to content

Commit 18e5fb3

Browse files
authored
Adding experimental flag for Metrics Labels String Interning (#6057)
* Metrics Labels String Interning Signed-off-by: alanprot <[email protected]> * Changelog Signed-off-by: alanprot <[email protected]> * v1 guarantees Signed-off-by: alanprot <[email protected]> --------- Signed-off-by: alanprot <[email protected]>
1 parent 4a82d36 commit 18e5fb3

File tree

6 files changed

+107
-2
lines changed

6 files changed

+107
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* [ENHANCEMENT] Store Gateway: Log gRPC requests together with headers configured in `http_request_headers_to_log`. #5958
1818
* [ENHANCEMENT] Upgrade Alpine to 3.19. #6014
1919
* [ENHANCEMENT] Upgrade go to 1.21.11 #6014
20+
* [ENHANCEMENT] Ingester: Add a new experimental `-ingester.labels-string-interning-enabled` flag to enable string interning for metrics labels. #6057
2021
* [BUGFIX] Configsdb: Fix endline issue in db password. #5920
2122
* [BUGFIX] Ingester: Fix `user` and `type` labels for the `cortex_ingester_tsdb_head_samples_appended_total` TSDB metric. #5952
2223
* [BUGFIX] Querier: Enforce max query length check for `/api/v1/series` API even though `ignoreMaxQueryLength` is set to true. #6018

docs/configuration/config-file-reference.md

+4
Original file line numberDiff line numberDiff line change
@@ -2971,6 +2971,10 @@ instance_limits:
29712971
# Customize the message contained in limit errors
29722972
# CLI flag: -ingester.admin-limit-message
29732973
[admin_limit_message: <string> | default = "please contact administrator to raise it"]
2974+
2975+
# Experimental: Enable string interning for metrics labels.
2976+
# CLI flag: -ingester.labels-string-interning-enabled
2977+
[labels_string_interning_enabled: <boolean> | default = false]
29742978
```
29752979

29762980
### `ingester_client_config`

docs/configuration/v1-guarantees.md

+2
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,5 @@ Currently experimental features are:
113113
- `-ruler.ring.tokens-file-path` (path) CLI flag
114114
- Native Histograms
115115
- Ingestion can be enabled by setting `-blocks-storage.tsdb.enable-native-histograms=true` on Ingester.
116+
- String interning for metrics labels
117+
- Enable string interning for metrics labels by setting `-ingester.labels-string-interning-enabled` on Ingester.

pkg/ingester/ingester.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ type Config struct {
134134

135135
// For admin contact details
136136
AdminLimitMessage string `yaml:"admin_limit_message"`
137+
138+
LabelsStringInterningEnabled bool `yaml:"labels_string_interning_enabled"`
137139
}
138140

139141
// RegisterFlags adds the flags required to config this to the given FlagSet
@@ -158,13 +160,18 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
158160

159161
f.StringVar(&cfg.AdminLimitMessage, "ingester.admin-limit-message", "please contact administrator to raise it", "Customize the message contained in limit errors")
160162

163+
f.BoolVar(&cfg.LabelsStringInterningEnabled, "ingester.labels-string-interning-enabled", false, "Experimental: Enable string interning for metrics labels.")
161164
}
162165

163166
func (cfg *Config) Validate() error {
164167
if err := cfg.LifecyclerConfig.Validate(); err != nil {
165168
return err
166169
}
167170

171+
if cfg.LabelsStringInterningEnabled {
172+
logutil.WarnExperimentalUse("String interning for metrics labels Enabled")
173+
}
174+
168175
return nil
169176
}
170177

@@ -296,6 +303,10 @@ type userTSDB struct {
296303
// Cached shipped blocks.
297304
shippedBlocksMtx sync.Mutex
298305
shippedBlocks map[ulid.ULID]struct{}
306+
307+
// Used to dedup strings and keep a single reference in memory
308+
labelsStringInterningEnabled bool
309+
interner util.Interner
299310
}
300311

301312
// Explicitly wrapping the tsdb.DB functions that we use.
@@ -425,6 +436,9 @@ func (u *userTSDB) PostCreation(metric labels.Labels) {
425436
}
426437
u.seriesInMetric.increaseSeriesForMetric(metricName)
427438
u.labelSetCounter.increaseSeriesLabelSet(u, metric)
439+
if u.labelsStringInterningEnabled {
440+
metric.InternStrings(u.interner.Intern)
441+
}
428442
}
429443

430444
// PostDeletion implements SeriesLifecycleCallback interface.
@@ -439,6 +453,9 @@ func (u *userTSDB) PostDeletion(metrics map[chunks.HeadSeriesRef]labels.Labels)
439453
}
440454
u.seriesInMetric.decreaseSeriesForMetric(metricName)
441455
u.labelSetCounter.decreaseSeriesLabelSet(u, metric)
456+
if u.labelsStringInterningEnabled {
457+
metric.ReleaseStrings(u.interner.Release)
458+
}
442459
}
443460
}
444461

@@ -2047,8 +2064,10 @@ func (i *Ingester) createTSDB(userID string) (*userTSDB, error) {
20472064
ingestedAPISamples: util_math.NewEWMARate(0.2, i.cfg.RateUpdatePeriod),
20482065
ingestedRuleSamples: util_math.NewEWMARate(0.2, i.cfg.RateUpdatePeriod),
20492066

2050-
instanceLimitsFn: i.getInstanceLimits,
2051-
instanceSeriesCount: &i.TSDBState.seriesCount,
2067+
instanceLimitsFn: i.getInstanceLimits,
2068+
instanceSeriesCount: &i.TSDBState.seriesCount,
2069+
interner: util.NewInterner(),
2070+
labelsStringInterningEnabled: i.cfg.LabelsStringInterningEnabled,
20522071
}
20532072

20542073
enableExemplars := false

pkg/ingester/lifecycle_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func defaultIngesterTestConfig(t testing.TB) Config {
4242
cfg.LifecyclerConfig.ID = "localhost"
4343
cfg.LifecyclerConfig.FinalSleep = 0
4444
cfg.ActiveSeriesMetricsEnabled = true
45+
cfg.LabelsStringInterningEnabled = true
4546
return cfg
4647
}
4748

pkg/util/strings.go

+78
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"unsafe"
77

88
"github.com/bboreham/go-loser"
9+
"go.uber.org/atomic"
910
)
1011

1112
// StringsContain returns true if the search value is within the list of input values.
@@ -139,3 +140,80 @@ func MergeSortedSlices(ctx context.Context, a ...[]string) ([]string, error) {
139140
}
140141
return r, nil
141142
}
143+
144+
type Interner interface {
145+
Intern(s string) string
146+
Release(s string)
147+
}
148+
149+
// NewInterner returns a new Interner to be used to intern strings.
150+
// Based on https://github.com/prometheus/prometheus/blob/726ed124e4468d0274ba89b0934a6cc8c975532d/storage/remote/intern.go#L51
151+
func NewInterner() Interner {
152+
return &pool{
153+
pool: map[string]*entry{},
154+
}
155+
}
156+
157+
type pool struct {
158+
mtx sync.RWMutex
159+
pool map[string]*entry
160+
}
161+
162+
type entry struct {
163+
refs atomic.Int64
164+
165+
s string
166+
}
167+
168+
func newEntry(s string) *entry {
169+
return &entry{s: s}
170+
}
171+
172+
// Intern returns the interned string. It returns the canonical representation of string.
173+
func (p *pool) Intern(s string) string {
174+
if s == "" {
175+
return ""
176+
}
177+
178+
p.mtx.RLock()
179+
interned, ok := p.pool[s]
180+
p.mtx.RUnlock()
181+
if ok {
182+
interned.refs.Inc()
183+
return interned.s
184+
}
185+
p.mtx.Lock()
186+
defer p.mtx.Unlock()
187+
if interned, ok := p.pool[s]; ok {
188+
interned.refs.Inc()
189+
return interned.s
190+
}
191+
192+
p.pool[s] = newEntry(s)
193+
p.pool[s].refs.Store(1)
194+
return s
195+
}
196+
197+
// Release releases a reference of the string `s`.
198+
// If the reference count become 0, the string `s` is removed from the memory
199+
func (p *pool) Release(s string) {
200+
p.mtx.RLock()
201+
interned, ok := p.pool[s]
202+
p.mtx.RUnlock()
203+
204+
if !ok {
205+
return
206+
}
207+
208+
refs := interned.refs.Dec()
209+
if refs > 0 {
210+
return
211+
}
212+
213+
p.mtx.Lock()
214+
defer p.mtx.Unlock()
215+
if interned.refs.Load() != 0 {
216+
return
217+
}
218+
delete(p.pool, s)
219+
}

0 commit comments

Comments
 (0)