From 42b52ecc4be1392a54653a38698909eda03a9a40 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 17 Oct 2025 11:04:34 +0100 Subject: [PATCH] TSDB: Allocate series ID after seriesLifecycleCallback This callback is not used by Prometheus, but in downstream projects it is wasteful to allocate an ID only to abandon it. Remove lengthy commment which I feel is distracting from the flow. Signed-off-by: Bryan Boreham --- tsdb/head.go | 15 +++++++-------- tsdb/head_test.go | 2 +- tsdb/head_wal.go | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/tsdb/head.go b/tsdb/head.go index a02b05f95d..2c71977b1a 100644 --- a/tsdb/head.go +++ b/tsdb/head.go @@ -1738,24 +1738,23 @@ func (*Head) String() string { } func (h *Head) getOrCreate(hash uint64, lset labels.Labels, pendingCommit bool) (*memSeries, bool, error) { - // Just using `getOrCreateWithID` below would be semantically sufficient, but we'd create - // a new series on every sample inserted via Add(), which causes allocations - // and makes our series IDs rather random and harder to compress in postings. s := h.series.getByHash(hash, lset) if s != nil { return s, false, nil } - // Optimistically assume that we are the first one to create the series. - id := chunks.HeadSeriesRef(h.lastSeriesID.Inc()) - - return h.getOrCreateWithID(id, hash, lset, pendingCommit) + return h.getOrCreateWithOptionalID(0, hash, lset, pendingCommit) } -func (h *Head) getOrCreateWithID(id chunks.HeadSeriesRef, hash uint64, lset labels.Labels, pendingCommit bool) (*memSeries, bool, error) { +// If id is zero, one will be allocated. +func (h *Head) getOrCreateWithOptionalID(id chunks.HeadSeriesRef, hash uint64, lset labels.Labels, pendingCommit bool) (*memSeries, bool, error) { if preCreationErr := h.series.seriesLifecycleCallback.PreCreation(lset); preCreationErr != nil { return nil, false, preCreationErr } + if id == 0 { + // Note this id is wasted in the case where a concurrent operation creates the same series first. + id = chunks.HeadSeriesRef(h.lastSeriesID.Inc()) + } shardHash := uint64(0) if h.opts.EnableSharding { diff --git a/tsdb/head_test.go b/tsdb/head_test.go index 8565ac3bbe..44daa7cddc 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -1152,7 +1152,7 @@ func TestHead_KeepSeriesInWALCheckpoint(t *testing.T) { { name: "keep series still in the head", prepare: func(t *testing.T, h *Head) { - _, _, err := h.getOrCreateWithID(chunks.HeadSeriesRef(existingRef), existingLbls.Hash(), existingLbls, false) + _, _, err := h.getOrCreateWithOptionalID(chunks.HeadSeriesRef(existingRef), existingLbls.Hash(), existingLbls, false) require.NoError(t, err) }, expected: true, diff --git a/tsdb/head_wal.go b/tsdb/head_wal.go index 3c5390cab4..cb7397e502 100644 --- a/tsdb/head_wal.go +++ b/tsdb/head_wal.go @@ -255,7 +255,7 @@ Outer: switch v := d.(type) { case []record.RefSeries: for _, walSeries := range v { - mSeries, created, err := h.getOrCreateWithID(walSeries.Ref, walSeries.Labels.Hash(), walSeries.Labels, false) + mSeries, created, err := h.getOrCreateWithOptionalID(walSeries.Ref, walSeries.Labels.Hash(), walSeries.Labels, false) if err != nil { seriesCreationErr = err break Outer @@ -1590,7 +1590,7 @@ func (h *Head) loadChunkSnapshot() (int, int, map[chunks.HeadSeriesRef]*memSerie localRefSeries := shardedRefSeries[idx] for csr := range rc { - series, _, err := h.getOrCreateWithID(csr.ref, csr.lset.Hash(), csr.lset, false) + series, _, err := h.getOrCreateWithOptionalID(csr.ref, csr.lset.Hash(), csr.lset, false) if err != nil { errChan <- err return