From d11ee103acd9eb46a28b621828604b2446a0887a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gy=C3=B6rgy=20Krajcsovits?= Date: Mon, 6 Oct 2025 21:44:34 +0200 Subject: [PATCH 1/3] perf(tsdb): reuse map of sample types to speed up head appender MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While investigating +10% CPU in v3.7 release, found that ~5% is from expanding the types map. Try reuse. Also fix a linter error. Signed-off-by: György Krajcsovits --- tsdb/head.go | 1 + tsdb/head_append.go | 21 ++++++++++++++++++++- util/testutil/synctest/disabled.go | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tsdb/head.go b/tsdb/head.go index 45f425cea9..f3242b8ba7 100644 --- a/tsdb/head.go +++ b/tsdb/head.go @@ -93,6 +93,7 @@ type Head struct { floatHistogramsPool zeropool.Pool[[]record.RefFloatHistogramSample] metadataPool zeropool.Pool[[]record.RefMetadata] seriesPool zeropool.Pool[[]*memSeries] + typeMapPool zeropool.Pool[map[chunks.HeadSeriesRef]sampleType] bytesPool zeropool.Pool[[]byte] memChunkPool sync.Pool diff --git a/tsdb/head_append.go b/tsdb/head_append.go index 659c09f7e7..9f930a763f 100644 --- a/tsdb/head_append.go +++ b/tsdb/head_append.go @@ -173,7 +173,7 @@ func (h *Head) appender() *headAppender { oooTimeWindow: h.opts.OutOfOrderTimeWindow.Load(), seriesRefs: h.getRefSeriesBuffer(), series: h.getSeriesBuffer(), - typesInBatch: map[chunks.HeadSeriesRef]sampleType{}, + typesInBatch: h.getTypeMap(), appendID: appendID, cleanupAppendIDsBelow: cleanupAppendIDsBelow, } @@ -297,6 +297,19 @@ func (h *Head) putSeriesBuffer(b []*memSeries) { h.seriesPool.Put(b[:0]) } +func (h *Head) getTypeMap() map[chunks.HeadSeriesRef]sampleType { + b := h.typeMapPool.Get() + if b == nil { + return make(map[chunks.HeadSeriesRef]sampleType) + } + return b +} + +func (h *Head) putTypeMap(b map[chunks.HeadSeriesRef]sampleType) { + clear(b) + h.typeMapPool.Put(b) +} + func (h *Head) getBytesBuffer() []byte { b := h.bytesPool.Get() if b == nil { @@ -1687,8 +1700,13 @@ func (a *headAppender) Commit() (err error) { h := a.head defer func() { + if a.closed { + // Don't double-close in case Rollback() was called. + return + } h.putRefSeriesBuffer(a.seriesRefs) h.putSeriesBuffer(a.series) + h.putTypeMap(a.typesInBatch) a.closed = true }() @@ -2216,6 +2234,7 @@ func (a *headAppender) Rollback() (err error) { a.closed = true h.putRefSeriesBuffer(a.seriesRefs) h.putSeriesBuffer(a.series) + h.putTypeMap(a.typesInBatch) }() var series *memSeries diff --git a/util/testutil/synctest/disabled.go b/util/testutil/synctest/disabled.go index 2cdcc72e07..e87454afcf 100644 --- a/util/testutil/synctest/disabled.go +++ b/util/testutil/synctest/disabled.go @@ -19,7 +19,7 @@ import ( "testing" ) -func Test(t *testing.T, f func(t *testing.T)) { +func Test(t *testing.T, _ func(t *testing.T)) { t.Skip("goexperiment.synctest is not enabled") } From 5f582a7e1feb95957cd67f2f90f1d2d3f7e42aa8 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Tue, 7 Oct 2025 14:58:25 +0200 Subject: [PATCH 2/3] tsdb: Remove leftover debug fmt.Println Signed-off-by: beorn7 --- tsdb/head_append.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tsdb/head_append.go b/tsdb/head_append.go index 9f930a763f..f49bf1b261 100644 --- a/tsdb/head_append.go +++ b/tsdb/head_append.go @@ -2238,7 +2238,6 @@ func (a *headAppender) Rollback() (err error) { }() var series *memSeries - fmt.Println("ROLLBACK") for _, b := range a.batches { for i := range b.floats { series = b.floatSeries[i] From 51c8e558357f112c6802dc1a98eb704f0d1ac6eb Mon Sep 17 00:00:00 2001 From: beorn7 Date: Tue, 7 Oct 2025 15:01:22 +0200 Subject: [PATCH 3/3] tsdb: Do not track stFloat in typesInBatch explicitly Signed-off-by: beorn7 --- tsdb/head_append.go | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tsdb/head_append.go b/tsdb/head_append.go index f49bf1b261..1bf8a5b9eb 100644 --- a/tsdb/head_append.go +++ b/tsdb/head_append.go @@ -570,7 +570,10 @@ func (a *headAppender) getCurrentBatch(st sampleType, s chunks.HeadSeriesRef) *a b.exemplars = h.getExemplarBuffer() } clear(a.typesInBatch) - if st != stNone { + switch st { + case stHistogram, stFloatHistogram, stCustomBucketHistogram, stCustomBucketFloatHistogram: + // We only record histogram sample types in the map. + // Floats are implicit. a.typesInBatch[s] = st } a.batches = append(a.batches, &b) @@ -597,14 +600,32 @@ func (a *headAppender) getCurrentBatch(st sampleType, s chunks.HeadSeriesRef) *a } prevST, ok := a.typesInBatch[s] switch { - case !ok: // New series. Add it to map and return current batch. + case prevST == st: + // An old series of some histogram type with the same type being appended. + // Continue the batch. + return lastBatch + case !ok && st == stFloat: + // A new float series, or an old float series that gets floats appended. + // Note that we do not track stFloat in typesInBatch. + // Continue the batch. + return lastBatch + case st == stFloat: + // A float being appended to a histogram series. + // Start a new batch. + return newBatch() + case !ok: + // A new series of some histogram type, or some histogram type + // being appended to on old float series. Even in the latter + // case, we don't need to start a new batch because histograms + // after floats are fine. + // Add new sample type to the map and continue batch. a.typesInBatch[s] = st return lastBatch - case prevST == st: // Old series, same type. Just return batch. - return lastBatch + default: + // One histogram type changed to another. + // Start a new batch. + return newBatch() } - // An old series got a new type. Start new batch. - return newBatch() } // appendable checks whether the given sample is valid for appending to the series. @@ -1068,6 +1089,8 @@ func (a *headAppender) log() error { return fmt.Errorf("log metadata: %w", err) } } + // It's important to do (float) Samples before histogram samples + // to end up with the correct order. if len(b.floats) > 0 { rec = enc.Samples(b.floats, buf) buf = rec[:0] @@ -1748,8 +1771,9 @@ func (a *headAppender) Commit() (err error) { }() for _, b := range a.batches { - // Do not change the order of these calls. The staleness marker - // handling depends on it. + // Do not change the order of these calls. We depend on it for + // correct commit order of samples and for the staleness marker + // handling. a.commitFloats(b, acc) a.commitHistograms(b, acc) a.commitFloatHistograms(b, acc)