storage/remote: speed up StoreSeries by re-using labels.Builder

Relabeling can take a pre-populated `Builder` instead of making a new
one every time. This is much more efficient.

Signed-off-by: Bryan Boreham <bjboreham@gmail.com>
This commit is contained in:
Bryan Boreham 2024-01-29 18:49:55 +00:00
parent d9483bb77c
commit dcd024a095
2 changed files with 19 additions and 28 deletions

View file

@ -413,9 +413,10 @@ type QueueManager struct {
clientMtx sync.RWMutex clientMtx sync.RWMutex
storeClient WriteClient storeClient WriteClient
seriesMtx sync.Mutex // Covers seriesLabels and droppedSeries. seriesMtx sync.Mutex // Covers seriesLabels, droppedSeries and builder.
seriesLabels map[chunks.HeadSeriesRef]labels.Labels seriesLabels map[chunks.HeadSeriesRef]labels.Labels
droppedSeries map[chunks.HeadSeriesRef]struct{} droppedSeries map[chunks.HeadSeriesRef]struct{}
builder *labels.Builder
seriesSegmentMtx sync.Mutex // Covers seriesSegmentIndexes - if you also lock seriesMtx, take seriesMtx first. seriesSegmentMtx sync.Mutex // Covers seriesSegmentIndexes - if you also lock seriesMtx, take seriesMtx first.
seriesSegmentIndexes map[chunks.HeadSeriesRef]int seriesSegmentIndexes map[chunks.HeadSeriesRef]int
@ -482,6 +483,7 @@ func NewQueueManager(
seriesLabels: make(map[chunks.HeadSeriesRef]labels.Labels), seriesLabels: make(map[chunks.HeadSeriesRef]labels.Labels),
seriesSegmentIndexes: make(map[chunks.HeadSeriesRef]int), seriesSegmentIndexes: make(map[chunks.HeadSeriesRef]int),
droppedSeries: make(map[chunks.HeadSeriesRef]struct{}), droppedSeries: make(map[chunks.HeadSeriesRef]struct{}),
builder: labels.NewBuilder(labels.EmptyLabels()),
numShards: cfg.MinShards, numShards: cfg.MinShards,
reshardChan: make(chan int), reshardChan: make(chan int),
@ -897,12 +899,14 @@ func (t *QueueManager) StoreSeries(series []record.RefSeries, index int) {
// Just make sure all the Refs of Series will insert into seriesSegmentIndexes map for tracking. // Just make sure all the Refs of Series will insert into seriesSegmentIndexes map for tracking.
t.seriesSegmentIndexes[s.Ref] = index t.seriesSegmentIndexes[s.Ref] = index
ls := processExternalLabels(s.Labels, t.externalLabels) t.builder.Reset(s.Labels)
lbls, keep := relabel.Process(ls, t.relabelConfigs...) processExternalLabels(t.builder, t.externalLabels)
if !keep || lbls.IsEmpty() { keep := relabel.ProcessBuilder(t.builder, t.relabelConfigs...)
if !keep {
t.droppedSeries[s.Ref] = struct{}{} t.droppedSeries[s.Ref] = struct{}{}
continue continue
} }
lbls := t.builder.Labels()
t.internLabels(lbls) t.internLabels(lbls)
// We should not ever be replacing a series labels in the map, but just // We should not ever be replacing a series labels in the map, but just
@ -967,30 +971,14 @@ func (t *QueueManager) releaseLabels(ls labels.Labels) {
ls.ReleaseStrings(t.interner.release) ls.ReleaseStrings(t.interner.release)
} }
// processExternalLabels merges externalLabels into ls. If ls contains // processExternalLabels merges externalLabels into b. If b contains
// a label in externalLabels, the value in ls wins. // a label in externalLabels, the value in b wins.
func processExternalLabels(ls labels.Labels, externalLabels []labels.Label) labels.Labels { func processExternalLabels(b *labels.Builder, externalLabels []labels.Label) {
if len(externalLabels) == 0 { for _, el := range externalLabels {
return ls if b.Get(el.Name) == "" {
} b.Set(el.Name, el.Value)
b := labels.NewScratchBuilder(ls.Len() + len(externalLabels))
j := 0
ls.Range(func(l labels.Label) {
for j < len(externalLabels) && l.Name > externalLabels[j].Name {
b.Add(externalLabels[j].Name, externalLabels[j].Value)
j++
} }
if j < len(externalLabels) && l.Name == externalLabels[j].Name {
j++
}
b.Add(l.Name, l.Value)
})
for ; j < len(externalLabels); j++ {
b.Add(externalLabels[j].Name, externalLabels[j].Value)
} }
return b.Labels()
} }
func (t *QueueManager) updateShardsLoop() { func (t *QueueManager) updateShardsLoop() {

View file

@ -1013,7 +1013,8 @@ func BenchmarkStartup(b *testing.B) {
} }
func TestProcessExternalLabels(t *testing.T) { func TestProcessExternalLabels(t *testing.T) {
for _, tc := range []struct { b := labels.NewBuilder(labels.EmptyLabels())
for i, tc := range []struct {
labels labels.Labels labels labels.Labels
externalLabels []labels.Label externalLabels []labels.Label
expected labels.Labels expected labels.Labels
@ -1074,7 +1075,9 @@ func TestProcessExternalLabels(t *testing.T) {
expected: labels.FromStrings("a", "b", "c", "d", "e", "f"), expected: labels.FromStrings("a", "b", "c", "d", "e", "f"),
}, },
} { } {
require.Equal(t, tc.expected, processExternalLabels(tc.labels, tc.externalLabels)) b.Reset(tc.labels)
processExternalLabels(b, tc.externalLabels)
require.Equal(t, tc.expected, b.Labels(), "test %d", i)
} }
} }