From 2fb50b12cd3ad42b1f20a331d5a5e5402469c17f Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 27 Aug 2025 17:11:08 +0100 Subject: [PATCH] [PERF] TSDB: Optimize appender creation on empty chunks (#16922) Skip creating an iterator and walking all through any existing values, when we can easily tell there are no existing values. This is the normal case - the TSDB head creates an appender immediately after creating every chunk. Remove redundant handling of empty chunks. Signed-off-by: Bryan Boreham --- tsdb/chunkenc/float_histogram.go | 8 +++----- tsdb/chunkenc/histogram.go | 6 +++--- tsdb/chunkenc/xor.go | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/tsdb/chunkenc/float_histogram.go b/tsdb/chunkenc/float_histogram.go index 05f5d5e252..bb37f44395 100644 --- a/tsdb/chunkenc/float_histogram.go +++ b/tsdb/chunkenc/float_histogram.go @@ -89,6 +89,9 @@ func (c *FloatHistogramChunk) Compact() { // Appender implements the Chunk interface. func (c *FloatHistogramChunk) Appender() (Appender, error) { + if len(c.b.stream) == 3 { // Avoid allocating an Iterator when chunk is empty. + return &FloatHistogramAppender{b: &c.b, t: math.MinInt64, sum: xorValue{leading: 0xff}, cnt: xorValue{leading: 0xff}, zCnt: xorValue{leading: 0xff}}, nil + } it := c.iterator(nil) // To get an appender, we must know the state it would have if we had @@ -133,11 +136,6 @@ func (c *FloatHistogramChunk) Appender() (Appender, error) { nBuckets: nBuckets, sum: it.sum, } - if it.numTotal == 0 { - a.sum.leading = 0xff - a.cnt.leading = 0xff - a.zCnt.leading = 0xff - } return a, nil } diff --git a/tsdb/chunkenc/histogram.go b/tsdb/chunkenc/histogram.go index 1b511c1f7b..e818ca4d91 100644 --- a/tsdb/chunkenc/histogram.go +++ b/tsdb/chunkenc/histogram.go @@ -100,6 +100,9 @@ func (c *HistogramChunk) Compact() { // Appender implements the Chunk interface. func (c *HistogramChunk) Appender() (Appender, error) { + if len(c.b.stream) == 3 { // Avoid allocating an Iterator when chunk is empty. + return &HistogramAppender{b: &c.b, t: math.MinInt64, leading: 0xff}, nil + } it := c.iterator(nil) // To get an appender, we must know the state it would have if we had @@ -134,9 +137,6 @@ func (c *HistogramChunk) Appender() (Appender, error) { leading: it.leading, trailing: it.trailing, } - if it.numTotal == 0 { - a.leading = 0xff - } return a, nil } diff --git a/tsdb/chunkenc/xor.go b/tsdb/chunkenc/xor.go index 5a37ebfea9..aac0f546d7 100644 --- a/tsdb/chunkenc/xor.go +++ b/tsdb/chunkenc/xor.go @@ -98,6 +98,9 @@ func (c *XORChunk) Compact() { // It is not valid to call Appender() multiple times concurrently or to use multiple // Appenders on the same chunk. func (c *XORChunk) Appender() (Appender, error) { + if len(c.b.stream) == 2 { // Avoid allocating an Iterator when chunk is empty. + return &xorAppender{b: &c.b, t: math.MinInt64, leading: 0xff}, nil + } it := c.iterator(nil) // To get an appender we must know the state it would have if we had @@ -117,9 +120,6 @@ func (c *XORChunk) Appender() (Appender, error) { leading: it.leading, trailing: it.trailing, } - if it.numTotal == 0 { - a.leading = 0xff - } return a, nil }