diff --git a/tsdb/compact.go b/tsdb/compact.go index cc5f229c4e..37ded7202e 100644 --- a/tsdb/compact.go +++ b/tsdb/compact.go @@ -924,6 +924,13 @@ func (c *LeveledCompactor) populateSymbols(sets []storage.ChunkSeriesSet, outBlo batchers := make([]*symbolsBatcher, len(outBlocks)) for ix := range outBlocks { batchers[ix] = newSymbolsBatcher(10000, outBlocks[ix].tmpDir) + + // Always include empty symbol. Blocks created from Head always have it in the symbols table, + // and if we only include symbols from series, we would skip it. + // It may not be required, but it's small and better be safe than sorry. + if err := batchers[ix].addSymbol(""); err != nil { + return errors.Wrap(err, "addSymbol to batcher") + } } seriesSet := sets[0] diff --git a/tsdb/compact_test.go b/tsdb/compact_test.go index 29456a80e5..4dfb06a673 100644 --- a/tsdb/compact_test.go +++ b/tsdb/compact_test.go @@ -562,6 +562,11 @@ func TestCompaction_CompactWithSplitting(t *testing.T) { // Symbols found in series. seriesSymbols := map[string]struct{}{} + // We always expect to find "" symbol in the symbols table even if it's not in the series. + // Head compaction always includes it, and then it survives additional non-sharded compactions. + // Our splitting compaction preserves it too. + seriesSymbols[""] = struct{}{} + block, err := OpenBlock(log.NewNopLogger(), filepath.Join(dir, blockID.String()), nil) require.NoError(t, err) @@ -601,14 +606,6 @@ func TestCompaction_CompactWithSplitting(t *testing.T) { symIt := idxr.Symbols() for symIt.Next() { w := symIt.At() - - // When shardCount == 1, we're not doing symbols splitting. Head-compacted blocks - // however do have empty string as a symbol in the table. - // Since label name or value cannot be empty string, we will never find it in seriesSymbols. - if w == "" && shardCount <= 1 { - continue - } - _, ok := seriesSymbols[w] require.True(t, ok, "not found in series: '%s'", w) delete(seriesSymbols, w)