From caae028edfbafd68761e0ff9f267c18b5769a7e6 Mon Sep 17 00:00:00 2001 From: Radu Berinde Date: Tue, 7 Oct 2025 12:29:56 -0700 Subject: [PATCH 1/2] base: move cache.Level to base We also rework the implementation so it's less confusing. --- db_test.go | 2 +- file_cache.go | 2 +- internal/base/level.go | 44 ++++++++++++++ internal/cache/cache.go | 6 +- internal/cache/cache_test.go | 21 ++++--- internal/cache/clockpro.go | 12 ++-- internal/cache/metrics.go | 67 ++++++++------------- internal/cache/read_shard_test.go | 4 +- sstable/block/block.go | 4 +- sstable/colblk/index_block_test.go | 2 +- sstable/colblk/keyspan_test.go | 2 +- sstable/copier.go | 3 +- sstable/rowblk/rowblk_fragment_iter_test.go | 2 +- sstable/writer_test.go | 2 +- table_stats.go | 3 +- 15 files changed, 102 insertions(+), 74 deletions(-) create mode 100644 internal/base/level.go diff --git a/db_test.go b/db_test.go index ef85d85c01..51dad9f731 100644 --- a/db_test.go +++ b/db_test.go @@ -858,7 +858,7 @@ func TestMemTableReservation(t *testing.T) { t.Fatalf("expected 2 refs, but found %d", refs) } // Verify the memtable reservation has caused our test block to be evicted. - if cv := tmpHandle.Peek(base.DiskFileNum(0), 0, cache.MakeLevel(0), cache.CategoryBackground); cv != nil { + if cv := tmpHandle.Peek(base.DiskFileNum(0), 0, base.MakeLevel(0), cache.CategoryBackground); cv != nil { t.Fatalf("expected failure, but found success: %#v", cv) } diff --git a/file_cache.go b/file_cache.go index 4e698e2c02..1c492f0d08 100644 --- a/file_cache.go +++ b/file_cache.go @@ -591,7 +591,7 @@ func (h *fileCacheHandle) newIters( internalOpts.readEnv.IsSharedIngested = env.IsSharedIngested internalOpts.readEnv.InternalBounds = env.InternalBounds if opts != nil && opts.layer.IsSet() && !opts.layer.IsFlushableIngests() { - internalOpts.readEnv.Block.Level = cache.MakeLevel(opts.layer.Level()) + internalOpts.readEnv.Block.Level = base.MakeLevel(opts.layer.Level()) } var iters iterSet diff --git a/internal/base/level.go b/internal/base/level.go new file mode 100644 index 0000000000..7040247c43 --- /dev/null +++ b/internal/base/level.go @@ -0,0 +1,44 @@ +// Copyright 2025 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +package base + +import ( + "fmt" + + "github.com/cockroachdb/errors" + "github.com/cockroachdb/pebble/internal/invariants" +) + +// Level identifies an LSM level. The zero value indicates that the level is +// uninitialized or unknown. +type Level struct { + // v contains the level in the lowest 7 bits. The highest bit is set iff the + // value is valid. + v uint8 +} + +const validBit = 1 << 7 + +func (l Level) Get() (level int, ok bool) { + return int(l.v &^ validBit), l.Valid() +} + +func (l Level) Valid() bool { + return l.v&validBit != 0 +} + +func (l Level) String() string { + if level, ok := l.Get(); ok { + return fmt.Sprintf("L%d", level) + } + return "n/a" +} + +func MakeLevel(l int) Level { + if invariants.Enabled && l < 0 || l >= validBit { + panic(errors.AssertionFailedf("invalid level: %d", l)) + } + return Level{uint8(l) | validBit} +} diff --git a/internal/cache/cache.go b/internal/cache/cache.go index dd3ff09858..bb5288efcf 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -253,7 +253,7 @@ func (c *Handle) Cache() *Cache { // Peek supports the special CategoryHidden category, in which case the hit or // miss is not recorded in metrics. func (c *Handle) Peek( - fileNum base.DiskFileNum, offset uint64, level Level, category Category, + fileNum base.DiskFileNum, offset uint64, level base.Level, category Category, ) *Value { k := makeKey(c.id, fileNum, offset) return c.cache.getShard(k).get(k, level, category, true /* peekOnly */) @@ -265,7 +265,7 @@ const CategoryHidden Category = -1 // Get retrieves the cache value for the specified file and offset, returning // nil if no value is present. func (c *Handle) Get( - fileNum base.DiskFileNum, offset uint64, level Level, category Category, + fileNum base.DiskFileNum, offset uint64, level base.Level, category Category, ) *Value { k := makeKey(c.id, fileNum, offset) return c.cache.getShard(k).get(k, level, category, false /* peekOnly */) @@ -295,7 +295,7 @@ func (c *Handle) Get( // While waiting, someone else may successfully read the value, which results // in a valid Handle being returned. This is a case where cacheHit=false. func (c *Handle) GetWithReadHandle( - ctx context.Context, fileNum base.DiskFileNum, offset uint64, level Level, category Category, + ctx context.Context, fileNum base.DiskFileNum, offset uint64, level base.Level, category Category, ) ( cv *Value, rh ReadHandle, diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go index 0abdb2eed1..c9f14e4ffc 100644 --- a/internal/cache/cache_test.go +++ b/internal/cache/cache_test.go @@ -41,7 +41,7 @@ func TestCache(t *testing.T) { wantHit := fields[1][0] == 'h' var hit bool - cv := h.Get(base.DiskFileNum(key), 0, MakeLevel(0), CategorySSTableData) + cv := h.Get(base.DiskFileNum(key), 0, base.MakeLevel(0), CategorySSTableData) if cv == nil { cv = Alloc(1) cv.RawBuffer()[0] = fields[0][0] @@ -81,14 +81,14 @@ func TestCachePeek(t *testing.T) { setTestValue(h, 0, uint64(i), "a", 1) } for i := range size / 2 { - v := h.Get(base.DiskFileNum(0), uint64(i), MakeLevel(0), CategoryBackground) + v := h.Get(base.DiskFileNum(0), uint64(i), base.MakeLevel(0), CategoryBackground) if v == nil { t.Fatalf("expected to find block %d", i) } v.Release() } for i := size / 2; i < size; i++ { - v := h.Peek(base.DiskFileNum(0), uint64(i), MakeLevel(0), CategoryBackground) + v := h.Peek(base.DiskFileNum(0), uint64(i), base.MakeLevel(0), CategoryBackground) if v == nil { t.Fatalf("expected to find block %d", i) } @@ -100,7 +100,7 @@ func TestCachePeek(t *testing.T) { } // Verify that the Gets still find their values, despite the Peeks. for i := range size / 2 { - v := h.Get(base.DiskFileNum(0), uint64(i), MakeLevel(0), CategoryBackground) + v := h.Get(base.DiskFileNum(0), uint64(i), base.MakeLevel(0), CategoryBackground) if v == nil { t.Fatalf("expected to find block %d", i) } @@ -124,12 +124,12 @@ func TestCacheDelete(t *testing.T) { if expected, size := int64(10), cache.Size(); expected != size { t.Fatalf("expected cache size %d, but found %d", expected, size) } - if v := h.Get(base.DiskFileNum(0), 0, MakeLevel(0), CategorySSTableData); v == nil { + if v := h.Get(base.DiskFileNum(0), 0, base.MakeLevel(0), CategorySSTableData); v == nil { t.Fatalf("expected to find block 0/0") } else { v.Release() } - if v := h.Get(base.DiskFileNum(1), 0, MakeLevel(0), CategorySSTableData); v != nil { + if v := h.Get(base.DiskFileNum(1), 0, base.MakeLevel(0), CategorySSTableData); v != nil { t.Fatalf("expected to not find block 1/0") } // Deleting a non-existing block does nothing. @@ -196,11 +196,11 @@ func TestMultipleDBs(t *testing.T) { if expected, size := int64(5), cache.Size(); expected != size { t.Fatalf("expected cache size %d, but found %d", expected, size) } - v := h1.Get(base.DiskFileNum(0), 0, MakeLevel(0), CategorySSTableData) + v := h1.Get(base.DiskFileNum(0), 0, base.MakeLevel(0), CategorySSTableData) if v != nil { t.Fatalf("expected not present, but found %#v", v) } - v = h2.Get(base.DiskFileNum(0), 0, MakeLevel(0), CategorySSTableData) + v = h2.Get(base.DiskFileNum(0), 0, base.MakeLevel(0), CategorySSTableData) if v := v.RawBuffer(); string(v) != "bbbbb" { t.Fatalf("expected bbbbb, but found %s", v) } @@ -308,7 +308,10 @@ func BenchmarkCacheGet(b *testing.B) { for pb.Next() { randVal := pcg.Uint64() offset := randVal % size - level := Level{levelPlusOne: int8((randVal >> 32) % NumLevels)} + var level base.Level + if l := int((randVal >> 32) % NumLevels); l > 0 { + level = base.MakeLevel(l - 1) + } category := Category((randVal >> 48) % uint64(NumCategories)) v := h.Get(base.DiskFileNum(0), offset, level, category) if v == nil { diff --git a/internal/cache/clockpro.go b/internal/cache/clockpro.go index c707c36dd6..dfda772a9f 100644 --- a/internal/cache/clockpro.go +++ b/internal/cache/clockpro.go @@ -138,7 +138,7 @@ func (c *shard) init(maxSize int64) { // // If peekOnly is true, the state of the cache is not modified to reflect the // access. -func (c *shard) get(k key, level Level, category Category, peekOnly bool) *Value { +func (c *shard) get(k key, level base.Level, category Category, peekOnly bool) *Value { c.mu.RLock() if e, _ := c.blocks.Get(k); e != nil { if value := e.acquireValue(); value != nil { @@ -148,14 +148,14 @@ func (c *shard) get(k key, level Level, category Category, peekOnly bool) *Value } c.mu.RUnlock() if category != CategoryHidden { - c.counters[level.index()][category].hits.Add(1) + c.counters[levelIndex(level)][category].hits.Add(1) } return value } } c.mu.RUnlock() if category != CategoryHidden { - c.counters[level.index()][category].misses.Add(1) + c.counters[levelIndex(level)][category].misses.Add(1) } return nil } @@ -165,7 +165,7 @@ func (c *shard) get(k key, level Level, category Category, peekOnly bool) *Value // is not in the cache (nil Value), a non-nil readEntry is returned (in which // case the caller is responsible to dereference the entry, via one of // unrefAndTryRemoveFromMap(), setReadValue(), setReadError()). -func (c *shard) getWithReadEntry(k key, level Level, category Category) (*Value, *readEntry) { +func (c *shard) getWithReadEntry(k key, level base.Level, category Category) (*Value, *readEntry) { c.mu.RLock() if e, _ := c.blocks.Get(k); e != nil { if value := e.acquireValue(); value != nil { @@ -174,13 +174,13 @@ func (c *shard) getWithReadEntry(k key, level Level, category Category) (*Value, e.referenced.Store(true) } c.mu.RUnlock() - c.counters[level.index()][category].hits.Add(1) + c.counters[levelIndex(level)][category].hits.Add(1) return value, nil } } re := c.readShard.acquireReadEntry(k) c.mu.RUnlock() - c.counters[level.index()][category].misses.Add(1) + c.counters[levelIndex(level)][category].misses.Add(1) return nil, re } diff --git a/internal/cache/metrics.go b/internal/cache/metrics.go index d1a5a46e23..0f9309c7d3 100644 --- a/internal/cache/metrics.go +++ b/internal/cache/metrics.go @@ -9,50 +9,33 @@ import ( "iter" "github.com/cockroachdb/crlib/crtime" - "github.com/cockroachdb/errors" - "github.com/cockroachdb/pebble/internal/invariants" + "github.com/cockroachdb/pebble/internal/base" ) -// Level is the LSM level associated with an accessed block. Used to maintain -// granular cache hit/miss statistics. -// -// The zero value indicates that there is no level (e.g. flushable ingests) or -// it is unknown. -type Level struct { - levelPlusOne int8 -} +const NumLevels = 1 /* unknown level */ + 7 -func (l Level) String() string { - if l.levelPlusOne <= 0 { - return "n/a" +// Levels is an iter.Seq[base.Level] that produces all the levels in the +// HitsAndMisses array. +func Levels(yield func(l base.Level) bool) { + if !yield(base.Level{}) { + return } - return fmt.Sprintf("L%d", l.levelPlusOne-1) -} - -// index returns a value between [0, NumLevels). -func (l Level) index() int8 { - return l.levelPlusOne -} - -func MakeLevel(l int) Level { - if invariants.Enabled && (l < 0 || l >= NumLevels-1) { - panic(errors.AssertionFailedf("invalid level: %d", l)) + for i := range NumLevels - 1 { + if !yield(base.MakeLevel(i)) { + return + } } - return Level{levelPlusOne: int8(l + 1)} } -const NumLevels = 1 /* unknown level */ + 7 - -// Levels is an iter.Seq[Level]. -func Levels(yield func(l Level) bool) { - for i := range NumLevels { - if !yield(Level{levelPlusOne: int8(i)}) { - return - } +// levelIndex returns the index of level in the HitsAndMisses array. +func levelIndex(level base.Level) int { + if l, ok := level.Get(); ok { + return l + 1 } + return 0 } -var _ iter.Seq[Level] = Levels +var _ iter.Seq[base.Level] = Levels // Category is used to maintain granular cache hit/miss statistics. type Category int8 @@ -117,17 +100,17 @@ type HitsAndMisses [NumLevels][NumCategories]struct { Misses int64 } -func (hm *HitsAndMisses) Get(level Level, category Category) (hits, misses int64) { - v := hm[level.index()][category] +func (hm *HitsAndMisses) Get(level base.Level, category Category) (hits, misses int64) { + v := hm[levelIndex(level)][category] return v.Hits, v.Misses } -func (hm *HitsAndMisses) Hits(level Level, category Category) int64 { - return hm[level.index()][category].Hits +func (hm *HitsAndMisses) Hits(level base.Level, category Category) int64 { + return hm[levelIndex(level)][category].Hits } -func (hm *HitsAndMisses) Misses(level Level, category Category) int64 { - return hm[level.index()][category].Misses +func (hm *HitsAndMisses) Misses(level base.Level, category Category) int64 { + return hm[levelIndex(level)][category].Misses } // Aggregate returns the total hits and misses across all categories and levels. @@ -143,8 +126,8 @@ func (hm *HitsAndMisses) Aggregate() (hits, misses int64) { // AggregateLevel returns the total hits and misses for a specific level (across // all categories). -func (hm *HitsAndMisses) AggregateLevel(level Level) (hits, misses int64) { - for _, v := range hm[level.index()] { +func (hm *HitsAndMisses) AggregateLevel(level base.Level) (hits, misses int64) { + for _, v := range hm[levelIndex(level)] { hits += v.Hits misses += v.Misses } diff --git a/internal/cache/read_shard_test.go b/internal/cache/read_shard_test.go index d9753489e3..788c44fb65 100644 --- a/internal/cache/read_shard_test.go +++ b/internal/cache/read_shard_test.go @@ -50,7 +50,7 @@ func newTestReader( } func (r *testReader) getAsync(shard *shard) *string { - v, re := shard.getWithReadEntry(r.key, MakeLevel(0), CategorySSTableData) + v, re := shard.getWithReadEntry(r.key, base.MakeLevel(0), CategorySSTableData) if v != nil { str := string(v.RawBuffer()) v.Release() @@ -285,7 +285,7 @@ func TestReadShardConcurrent(t *testing.T) { for _, r := range differentReaders { for j := 0; j < r.numReaders; j++ { go func(r *testSyncReaders, index int) { - v, rh, _, _, _, err := r.handle.GetWithReadHandle(context.Background(), r.fileNum, r.offset, MakeLevel(0), CategorySSTableData) + v, rh, _, _, _, err := r.handle.GetWithReadHandle(context.Background(), r.fileNum, r.offset, base.MakeLevel(0), CategorySSTableData) require.NoError(t, err) if v != nil { require.Equal(t, r.val, v.RawBuffer()) diff --git a/sstable/block/block.go b/sstable/block/block.go index f515874b05..30dacdfbf6 100644 --- a/sstable/block/block.go +++ b/sstable/block/block.go @@ -276,7 +276,7 @@ type ReadEnv struct { // Level is the LSM level associated with the operation, when the operation // applies to a (possibly virtual) sstable. It is used when interacting with // the block cache. - Level cache.Level + Level base.Level // ReportCorruptionFn is called with ReportCorruptionArg and the error // whenever an SSTable corruption is detected. The argument is used to avoid @@ -566,7 +566,7 @@ func (r *Reader) Readable() objstorage.Readable { // // Users should prefer using Read, which handles reading from object storage on // a cache miss. -func (r *Reader) GetFromCache(bh Handle, level cache.Level) *cache.Value { +func (r *Reader) GetFromCache(bh Handle, level base.Level) *cache.Value { return r.opts.CacheOpts.CacheHandle.Peek(r.opts.CacheOpts.FileNum, bh.Offset, level, cache.CategoryBackground) } diff --git a/sstable/colblk/index_block_test.go b/sstable/colblk/index_block_test.go index b56fd94297..3074739cbd 100644 --- a/sstable/colblk/index_block_test.go +++ b/sstable/colblk/index_block_test.go @@ -132,7 +132,7 @@ func TestIndexIterInitHandle(t *testing.T) { } getBlockAndIterate := func(it *IndexIter) { - cv := ch.Get(base.DiskFileNum(1), 0, cache.MakeLevel(0), cache.CategorySSTableData) + cv := ch.Get(base.DiskFileNum(1), 0, base.MakeLevel(0), cache.CategorySSTableData) require.NotNil(t, cv) require.NoError(t, it.InitHandle(testkeys.Comparer, block.CacheBufferHandle(cv), blockiter.NoTransforms)) defer it.Close() diff --git a/sstable/colblk/keyspan_test.go b/sstable/colblk/keyspan_test.go index f8b1048abf..07e7e5dee1 100644 --- a/sstable/colblk/keyspan_test.go +++ b/sstable/colblk/keyspan_test.go @@ -94,7 +94,7 @@ func TestKeyspanBlockPooling(t *testing.T) { v.SetInCacheForTesting(ch, base.DiskFileNum(1), 0) getBlockAndIterate := func() { - cv := ch.Get(base.DiskFileNum(1), 0, cache.MakeLevel(0), cache.CategorySSTableData) + cv := ch.Get(base.DiskFileNum(1), 0, base.MakeLevel(0), cache.CategorySSTableData) require.NotNil(t, cv) it := NewKeyspanIter(testkeys.Comparer.Compare, block.CacheBufferHandle(cv), blockiter.NoFragmentTransforms) defer it.Close() diff --git a/sstable/copier.go b/sstable/copier.go index 3075ccad3a..019d60cdd4 100644 --- a/sstable/copier.go +++ b/sstable/copier.go @@ -12,7 +12,6 @@ import ( "github.com/cockroachdb/errors" "github.com/cockroachdb/pebble/internal/base" "github.com/cockroachdb/pebble/internal/bytealloc" - "github.com/cockroachdb/pebble/internal/cache" "github.com/cockroachdb/pebble/objstorage" "github.com/cockroachdb/pebble/objstorage/objstorageprovider" "github.com/cockroachdb/pebble/sstable/block" @@ -163,7 +162,7 @@ func CopySpan( var blocksNotInCache []indexEntry for i := range blocks { - cv := r.blockReader.GetFromCache(blocks[i].bh.Handle, cache.MakeLevel(level)) + cv := r.blockReader.GetFromCache(blocks[i].bh.Handle, base.MakeLevel(level)) if cv == nil { // Cache miss. Add this block to the list of blocks that are not in cache. blocksNotInCache = blocks[i-len(blocksNotInCache) : i+1] diff --git a/sstable/rowblk/rowblk_fragment_iter_test.go b/sstable/rowblk/rowblk_fragment_iter_test.go index ec58ec47fb..34a7489f88 100644 --- a/sstable/rowblk/rowblk_fragment_iter_test.go +++ b/sstable/rowblk/rowblk_fragment_iter_test.go @@ -96,7 +96,7 @@ func TestBlockFragmentIterator(t *testing.T) { return d.Expected } - blockHandle := block.CacheBufferHandle(cacheHandle.Get(0, 0, cache.MakeLevel(0), cache.CategorySSTableData)) + blockHandle := block.CacheBufferHandle(cacheHandle.Get(0, 0, base.MakeLevel(0), cache.CategorySSTableData)) require.True(t, blockHandle.Valid()) i, err := NewFragmentIter(0, comparer, blockHandle, transforms) defer i.Close() diff --git a/sstable/writer_test.go b/sstable/writer_test.go index 6024b10812..93b1e2ed97 100644 --- a/sstable/writer_test.go +++ b/sstable/writer_test.go @@ -835,7 +835,7 @@ func TestWriterClearCache(t *testing.T) { // Verify that the written blocks have been cleared from the cache. check := func(bh block.Handle) { - cv := cacheOpts.CacheHandle.Get(cacheOpts.FileNum, bh.Offset, cache.MakeLevel(0), cache.CategorySSTableData) + cv := cacheOpts.CacheHandle.Get(cacheOpts.FileNum, bh.Offset, base.MakeLevel(0), cache.CategorySSTableData) if cv != nil { t.Fatalf("%d: expected cache to be cleared, but found %#v", bh.Offset, cv) } diff --git a/table_stats.go b/table_stats.go index 0d939758ae..e16c1a73ab 100644 --- a/table_stats.go +++ b/table_stats.go @@ -14,7 +14,6 @@ import ( "github.com/cockroachdb/crlib/crtime" "github.com/cockroachdb/errors" "github.com/cockroachdb/pebble/internal/base" - "github.com/cockroachdb/pebble/internal/cache" "github.com/cockroachdb/pebble/internal/invariants" "github.com/cockroachdb/pebble/internal/keyspan" "github.com/cockroachdb/pebble/internal/keyspan/keyspanimpl" @@ -351,7 +350,7 @@ func (d *DB) loadTableStats( backingProps, backingPropsOk := meta.TableBacking.Properties() blockReadEnv := block.ReadEnv{ - Level: cache.MakeLevel(level), + Level: base.MakeLevel(level), } // If the stats are already available (always the case other than after // initial startup), and there are no range deletions or range key deletions, From 1f2a4a990f3b4fe01f0c6684149a760bb180d906 Mon Sep 17 00:00:00 2001 From: Radu Berinde Date: Tue, 7 Oct 2025 12:29:56 -0700 Subject: [PATCH 2/2] metrics: add compression counters Add running counters that keep track of how many bytes were compressed and decompressed. The counters are segregated along the same lines where compression settings can differ: L5 vs L6 vs other levels, and data vs value vs other blocks. The intention is to estimate the CPU usage change for a different compression profile (in conjunction with data about each algorithm's performance, as obtained by the compression analyzer). --- compaction.go | 2 + db.go | 5 + flushable_test.go | 2 +- ingest.go | 9 +- ingest_test.go | 6 +- internal.go | 7 +- metrics.go | 60 ++++++- metrics_test.go | 18 +++ open.go | 6 +- options.go | 11 +- sstable/blob/blob.go | 11 +- sstable/blob/blob_test.go | 30 ++-- sstable/blob/fetcher_test.go | 2 +- sstable/blob/testdata/value_fetcher | 24 +-- sstable/blob/testdata/writer | 15 +- sstable/block/block.go | 8 + sstable/block/compression.go | 173 +++++++++++++++++---- sstable/block/compression_test.go | 2 +- sstable/block/compressor.go | 52 ++++--- sstable/block/compressor_test.go | 18 ++- sstable/block/physical.go | 18 ++- sstable/compressionanalyzer/buckets.go | 60 ++++--- sstable/layout.go | 2 +- sstable/options.go | 3 + sstable/suffix_rewriter.go | 2 +- testdata/compaction/l0_to_lbase_compaction | 5 + testdata/compaction/value_separation | 17 +- testdata/compaction/virtual_rewrite | 6 + testdata/event_listener | 12 ++ testdata/ingest | 7 +- testdata/metrics | 128 ++++++++++++++- tool/testdata/db_lsm | 6 + 32 files changed, 577 insertions(+), 150 deletions(-) diff --git a/compaction.go b/compaction.go index 7e30efb22e..0291ec87d9 100644 --- a/compaction.go +++ b/compaction.go @@ -2945,6 +2945,8 @@ func (d *DB) runCopyCompaction( var wrote uint64 err = d.fileCache.withReader(ctx, block.NoReadEnv, inputMeta.VirtualMeta(), func(r *sstable.Reader, env sstable.ReadEnv) error { var err error + writerOpts := d.opts.MakeWriterOptions(c.outputLevel.level, d.TableFormat()) + writerOpts.CompressionCounters = d.compressionCounters.Compressed.ForLevel(base.MakeLevel(c.outputLevel.level)) // TODO(radu): plumb a ReadEnv to CopySpan (it could use the buffer pool // or update category stats). wrote, err = sstable.CopySpan(ctx, diff --git a/db.go b/db.go index 2f80857373..f8892d7bcb 100644 --- a/db.go +++ b/db.go @@ -547,6 +547,8 @@ type DB struct { // compaction concurrency openedAt time.Time + compressionCounters block.CompressionCounters + iterTracker *inflight.Tracker } @@ -2013,6 +2015,9 @@ func (d *DB) Metrics() *Metrics { blobCompressionMetrics := blobCompressionStatsAnnotator.Annotation(&vers.BlobFiles) metrics.BlobFiles.Compression.MergeWith(&blobCompressionMetrics) + metrics.CompressionCounters.LogicalBytesCompressed = d.compressionCounters.LoadCompressed() + metrics.CompressionCounters.LogicalBytesDecompressed = d.compressionCounters.LoadDecompressed() + metrics.BlockCache = d.opts.Cache.Metrics() metrics.FileCache, metrics.Filter = d.fileCache.Metrics() metrics.TableIters = d.fileCache.IterCount() diff --git a/flushable_test.go b/flushable_test.go index abfc9105f5..beba66bdbd 100644 --- a/flushable_test.go +++ b/flushable_test.go @@ -58,7 +58,7 @@ func TestIngestedSSTFlushableAPI(t *testing.T) { // We can reuse the ingestLoad function for this test even if we're // not actually ingesting a file. - lr, err := ingestLoad(context.Background(), d.opts, d.FormatMajorVersion(), paths, nil, nil, d.cacheHandle, pendingOutputs) + lr, err := ingestLoad(context.Background(), d.opts, d.FormatMajorVersion(), paths, nil, nil, d.cacheHandle, &d.compressionCounters, pendingOutputs) if err != nil { t.Fatal(err) } diff --git a/ingest.go b/ingest.go index 2ba54b2063..2d1d482716 100644 --- a/ingest.go +++ b/ingest.go @@ -257,6 +257,7 @@ func ingestLoad1( fmv FormatMajorVersion, readable objstorage.Readable, cacheHandle *cache.Handle, + compressionCounters *block.CompressionCounters, tableNum base.TableNum, rangeKeyValidator rangeKeyIngestValidator, ) ( @@ -270,6 +271,9 @@ func ingestLoad1( CacheHandle: cacheHandle, FileNum: base.PhysicalTableDiskFileNum(tableNum), } + if compressionCounters != nil { + o.CompressionCounters = &compressionCounters.Decompressed + } r, err := sstable.NewReader(ctx, readable, o) if err != nil { return nil, keyspan.Span{}, base.BlockReadStats{}, errors.CombineErrors(err, readable.Close()) @@ -498,6 +502,7 @@ func ingestLoad( shared []SharedSSTMeta, external []ExternalFile, cacheHandle *cache.Handle, + compressionCounters *block.CompressionCounters, pending []base.TableNum, ) (ingestLoadResult, error) { localFileNums := pending[:len(paths)] @@ -531,7 +536,7 @@ func ingestLoad( if !shouldDisableRangeKeyChecks { rangeKeyValidator = validateSuffixedBoundaries(opts.Comparer, lastRangeKey) } - m, lastRangeKey, blockReadStats, err = ingestLoad1(ctx, opts, fmv, readable, cacheHandle, localFileNums[i], rangeKeyValidator) + m, lastRangeKey, blockReadStats, err = ingestLoad1(ctx, opts, fmv, readable, cacheHandle, compressionCounters, localFileNums[i], rangeKeyValidator) if err != nil { return ingestLoadResult{}, err } @@ -1480,7 +1485,7 @@ func (d *DB) ingest(ctx context.Context, args ingestArgs) (IngestOperationStats, // Load the metadata for all the files being ingested. This step detects // and elides empty sstables. loadResult, err := ingestLoad(ctx, d.opts, d.FormatMajorVersion(), paths, shared, external, - d.cacheHandle, pendingOutputs) + d.cacheHandle, &d.compressionCounters, pendingOutputs) if err != nil { return IngestOperationStats{}, err } diff --git a/ingest_test.go b/ingest_test.go index ce7427b985..89a3921b8c 100644 --- a/ingest_test.go +++ b/ingest_test.go @@ -148,7 +148,7 @@ func TestIngestLoad(t *testing.T) { FS: mem, } opts.WithFSDefaults() - lr, err := ingestLoad(context.Background(), opts, dbVersion, []string{"ext"}, nil, nil, nil, []base.TableNum{1}) + lr, err := ingestLoad(context.Background(), opts, dbVersion, []string{"ext"}, nil, nil, nil, nil, []base.TableNum{1}) if err != nil { return err.Error() } @@ -247,7 +247,7 @@ func TestIngestLoadRand(t *testing.T) { } opts.WithFSDefaults() opts.EnsureDefaults() - lr, err := ingestLoad(context.Background(), opts, version, paths, nil, nil, nil, pending) + lr, err := ingestLoad(context.Background(), opts, version, paths, nil, nil, nil, nil, pending) require.NoError(t, err) // Reset flaky stats. @@ -272,7 +272,7 @@ func TestIngestLoadInvalid(t *testing.T) { FS: mem, } opts.WithFSDefaults() - if _, err := ingestLoad(context.Background(), opts, internalFormatNewest, []string{"invalid"}, nil, nil, nil, []base.TableNum{1}); err == nil { + if _, err := ingestLoad(context.Background(), opts, internalFormatNewest, []string{"invalid"}, nil, nil, nil, nil, []base.TableNum{1}); err == nil { t.Fatalf("expected error, but found success") } } diff --git a/internal.go b/internal.go index 3b6d71da81..5738c3582e 100644 --- a/internal.go +++ b/internal.go @@ -4,7 +4,10 @@ package pebble -import "github.com/cockroachdb/pebble/internal/base" +import ( + "github.com/cockroachdb/pebble/internal/base" + "github.com/cockroachdb/pebble/sstable/block" +) // SeqNum exports the base.SeqNum type. type SeqNum = base.SeqNum @@ -80,3 +83,5 @@ type ShortAttribute = base.ShortAttribute // LazyValue.Clone requires a pointer to a LazyFetcher struct to avoid // allocations. No code outside Pebble needs to peer into a LazyFetcher. type LazyFetcher = base.LazyFetcher + +type CompressionCounters = block.CompressionCounters diff --git a/metrics.go b/metrics.go index dd899b243f..250770ea77 100644 --- a/metrics.go +++ b/metrics.go @@ -349,7 +349,7 @@ type Metrics struct { BackingTableCount uint64 // The sum of the sizes of the BackingTableCount sstables that are backing virtual tables. BackingTableSize uint64 - // Compression statistics for sstable data (does not include blob files). + // Compression statistics for the current sstables. Compression CompressionMetrics // Local file sizes. @@ -447,9 +447,17 @@ type Metrics struct { ZombieCount uint64 } + // Compression statistics for the current blob files. Compression CompressionMetrics } + // CompressionCounters are cumulative counters for the number of logical + // (uncompressed) bytes that went through compression and decompression. + CompressionCounters struct { + LogicalBytesCompressed block.ByLevel[block.ByKind[uint64]] + LogicalBytesDecompressed block.ByLevel[block.ByKind[uint64]] + } + FileCache FileCacheMetrics // Count of the number of open sstable iterators. @@ -500,7 +508,7 @@ type Metrics struct { // CompressionMetrics contains compression metrics for sstables or blob files. type CompressionMetrics struct { - // NoCompressionBytes is the total number of bytes in files that do are not + // NoCompressionBytes is the total number of bytes in files that are not // compressed. Data can be uncompressed when 1) compression is disabled; 2) // for certain special types of blocks; and 3) for blocks that are not // compressible. @@ -845,6 +853,16 @@ var ( table.Div(), table.String("blob files", 13, table.AlignRight, func(i compressionInfo) string { return i.blobFiles }), ) + compressionCountersTableHeader = ` | logical bytes compressed / decompressed` + compressionCountersTable = table.Define[compressionCountersInfo]( + table.String("level", 5, table.AlignRight, func(i compressionCountersInfo) string { return i.level }), + table.Div(), + table.String("data blocks", 14, table.AlignCenter, func(i compressionCountersInfo) string { return i.DataBlocks }), + table.Div(), + table.String("value blocks", 14, table.AlignCenter, func(i compressionCountersInfo) string { return i.ValueBlocks }), + table.Div(), + table.String("other blocks", 14, table.AlignCenter, func(i compressionCountersInfo) string { return i.OtherBlocks }), + ) ) type commitPipelineInfo struct { @@ -973,6 +991,34 @@ func makeCompressionInfo(algorithm string, table, blob CompressionStatsForSettin return i } +type compressionCountersInfo struct { + level string + block.ByKind[string] +} + +func makeCompressionCountersInfo(m *Metrics) []compressionCountersInfo { + var result []compressionCountersInfo + isZero := func(c *block.ByKind[uint64]) bool { + return c.DataBlocks == 0 && c.ValueBlocks == 0 && c.OtherBlocks == 0 + } + addLevel := func(level string, compressed, decompressed *block.ByKind[uint64]) { + if isZero(compressed) && isZero(decompressed) { + return + } + result = append(result, compressionCountersInfo{ + level: level, + ByKind: block.ByKind[string]{ + DataBlocks: humanizeBytes(compressed.DataBlocks) + " / " + humanizeBytes(decompressed.DataBlocks), + ValueBlocks: humanizeBytes(compressed.ValueBlocks) + " / " + humanizeBytes(decompressed.ValueBlocks), + OtherBlocks: humanizeBytes(compressed.OtherBlocks) + " / " + humanizeBytes(decompressed.OtherBlocks)}, + }) + } + addLevel("L0-L4", &m.CompressionCounters.LogicalBytesCompressed.OtherLevels, &m.CompressionCounters.LogicalBytesDecompressed.OtherLevels) + addLevel("L5", &m.CompressionCounters.LogicalBytesCompressed.L5, &m.CompressionCounters.LogicalBytesDecompressed.L5) + addLevel("L6", &m.CompressionCounters.LogicalBytesCompressed.L6, &m.CompressionCounters.LogicalBytesDecompressed.L6) + return result +} + // String pretty-prints the metrics. // // See testdata/metrics for an example. @@ -1160,7 +1206,11 @@ func (m *Metrics) String() string { compressionContents = slices.DeleteFunc(compressionContents, func(i compressionInfo) bool { return i.tables == "" && i.blobFiles == "" }) - compressionTable.Render(cur, table.RenderOptions{}, compressionContents...) + cur = compressionTable.Render(cur, table.RenderOptions{}, compressionContents...) + + cur = cur.NewlineReturn() + cur = cur.WriteString(compressionCountersTableHeader).NewlineReturn() + compressionCountersTable.Render(cur, table.RenderOptions{}, makeCompressionCountersInfo(m)...) return wb.String() } @@ -1190,8 +1240,8 @@ func (m *Metrics) StringForTests() string { // We recalculate the file cache size using the 64-bit sizes, and we ignore // the genericcache metadata size which is harder to adjust. - const sstableReaderSize64bit = 280 - const blobFileReaderSize64bit = 112 + const sstableReaderSize64bit = 288 + const blobFileReaderSize64bit = 120 mCopy.FileCache.Size = mCopy.FileCache.TableCount*sstableReaderSize64bit + mCopy.FileCache.BlobFileCount*blobFileReaderSize64bit if math.MaxInt == math.MaxInt64 { // Verify the 64-bit sizes, so they are kept updated. diff --git a/metrics_test.go b/metrics_test.go index de4e7f2f4c..8c9e800015 100644 --- a/metrics_test.go +++ b/metrics_test.go @@ -184,6 +184,24 @@ func exampleMetrics() Metrics { m.BlobFiles.Compression.Zstd.CompressedBytes = 100 * GB m.BlobFiles.Compression.Zstd.UncompressedBytes = 500 * GB + byKind := func(n uint64) block.ByKind[uint64] { + return block.ByKind[uint64]{ + DataBlocks: n * 10 * GB, + ValueBlocks: n * 100 * GB, + OtherBlocks: n * GB, + } + } + m.CompressionCounters.LogicalBytesCompressed = block.ByLevel[block.ByKind[uint64]]{ + L5: byKind(5), + L6: byKind(6), + OtherLevels: byKind(1), + } + m.CompressionCounters.LogicalBytesDecompressed = block.ByLevel[block.ByKind[uint64]]{ + L5: byKind(50), + L6: byKind(60), + OtherLevels: byKind(10), + } + m.FileCache.Size = 1 * MB m.FileCache.TableCount = 180 m.FileCache.BlobFileCount = 181 diff --git a/open.go b/open.go index e14323cd92..ef89d98fe4 100644 --- a/open.go +++ b/open.go @@ -476,7 +476,9 @@ func Open(dirname string, opts *Options) (db *DB, err error) { opts.FileCache = NewFileCache(opts.Experimental.FileCacheShards, fileCacheSize) defer opts.FileCache.Unref() } - d.fileCache = opts.FileCache.newHandle(d.cacheHandle, d.objProvider, d.opts.LoggerAndTracer, d.opts.MakeReaderOptions(), d.reportCorruption) + fileCacheReaderOpts := d.opts.MakeReaderOptions() + fileCacheReaderOpts.CompressionCounters = &d.compressionCounters.Decompressed + d.fileCache = opts.FileCache.newHandle(d.cacheHandle, d.objProvider, d.opts.LoggerAndTracer, fileCacheReaderOpts, d.reportCorruption) d.newIters = d.fileCache.newIters d.tableNewRangeKeyIter = tableNewRangeKeyIter(d.newIters) @@ -875,7 +877,7 @@ func (d *DB) replayIngestedFlushable( } // NB: ingestLoad1 will close readable. meta[i], lastRangeKey, _, err = ingestLoad1(context.TODO(), d.opts, d.FormatMajorVersion(), - readable, d.cacheHandle, base.PhysicalTableFileNum(n), disableRangeKeyChecks()) + readable, d.cacheHandle, &d.compressionCounters, base.PhysicalTableFileNum(n), disableRangeKeyChecks()) if err != nil { return nil, errors.Wrap(err, "pebble: error when loading flushable ingest files") } diff --git a/options.go b/options.go index 8bff73e2b0..a4933fafdc 100644 --- a/options.go +++ b/options.go @@ -2634,7 +2634,9 @@ func (o *Options) MakeWriterOptions(level int, format sstable.TableFormat) sstab // makeWriterOptions constructs sstable.WriterOptions for the specified level // using the current DB options and format. func (d *DB) makeWriterOptions(level int) sstable.WriterOptions { - return d.opts.MakeWriterOptions(level, d.TableFormat()) + o := d.opts.MakeWriterOptions(level, d.TableFormat()) + o.CompressionCounters = d.compressionCounters.Compressed.ForLevel(base.MakeLevel(level)) + return o } // makeBlobWriterOptions constructs blob.FileWriterOptions using the current DB @@ -2642,9 +2644,10 @@ func (d *DB) makeWriterOptions(level int) sstable.WriterOptions { func (d *DB) makeBlobWriterOptions(level int) blob.FileWriterOptions { lo := &d.opts.Levels[level] return blob.FileWriterOptions{ - Format: d.BlobFileFormat(), - Compression: lo.Compression(), - ChecksumType: block.ChecksumTypeCRC32c, + Format: d.BlobFileFormat(), + Compression: lo.Compression(), + CompressionCounters: d.compressionCounters.Compressed.ForLevel(base.MakeLevel(level)), + ChecksumType: block.ChecksumTypeCRC32c, FlushGovernor: block.MakeFlushGovernor( lo.BlockSize, lo.BlockSizeThreshold, diff --git a/sstable/blob/blob.go b/sstable/blob/blob.go index c1769b859b..bc4636ae80 100644 --- a/sstable/blob/blob.go +++ b/sstable/blob/blob.go @@ -88,10 +88,11 @@ const ( // FileWriterOptions are used to configure the FileWriter. type FileWriterOptions struct { - Format FileFormat - Compression *block.CompressionProfile - ChecksumType block.ChecksumType - FlushGovernor block.FlushGovernor + Format FileFormat + Compression *block.CompressionProfile + CompressionCounters *block.ByKind[block.LogicalBytesCompressed] + ChecksumType block.ChecksumType + FlushGovernor block.FlushGovernor // Only CPUMeasurer.MeasureCPUBlobFileSecondary is used. CpuMeasurer base.CPUMeasurer } @@ -175,7 +176,7 @@ func NewFileWriter(fn base.DiskFileNum, w objstorage.Writable, opts FileWriterOp fw.valuesEncoder.Init() fw.flushGov = opts.FlushGovernor fw.indexEncoder.Init() - fw.physBlockMaker.Init(opts.Compression, opts.ChecksumType) + fw.physBlockMaker.Init(opts.Compression, opts.ChecksumType, opts.CompressionCounters) fw.cpuMeasurer = opts.CpuMeasurer fw.writeQueue.ch = make(chan compressedBlock) fw.writeQueue.wg.Add(1) diff --git a/sstable/blob/blob_test.go b/sstable/blob/blob_test.go index aeb2653c6f..e76b3321cd 100644 --- a/sstable/blob/blob_test.go +++ b/sstable/blob/blob_test.go @@ -12,6 +12,7 @@ import ( "math" "testing" + "github.com/cockroachdb/crlib/crhumanize" "github.com/cockroachdb/crlib/crstrings" "github.com/cockroachdb/crlib/testutils/leaktest" "github.com/cockroachdb/datadriven" @@ -39,7 +40,7 @@ func TestBlobWriter(t *testing.T) { if err != nil { t.Fatal(err) } - printFileWriterStats(&buf, stats) + printFileWriterStats(&buf, stats, opts.CompressionCounters) return buf.String() case "build-sparse": opts := scanFileWriterOptions(t, td) @@ -61,7 +62,7 @@ func TestBlobWriter(t *testing.T) { } stats, err := w.Close() require.NoError(t, err) - printFileWriterStats(&buf, stats) + printFileWriterStats(&buf, stats, opts.CompressionCounters) return buf.String() case "open": r, err := NewFileReader(context.Background(), obj, FileReaderOptions{}) @@ -102,19 +103,30 @@ func scanFileWriterOptions(t *testing.T, td *datadriven.TestData) FileWriterOpti } td.MaybeScanArgs(t, "format", &format) return FileWriterOptions{ - Format: FileFormat(format), - Compression: compression, - ChecksumType: block.ChecksumTypeCRC32c, - FlushGovernor: block.MakeFlushGovernor(targetBlockSize, blockSizeThreshold, 0, nil), + Format: FileFormat(format), + Compression: compression, + CompressionCounters: &block.ByKind[block.LogicalBytesCompressed]{}, + ChecksumType: block.ChecksumTypeCRC32c, + FlushGovernor: block.MakeFlushGovernor(targetBlockSize, blockSizeThreshold, 0, nil), } } -func printFileWriterStats(w io.Writer, stats FileWriterStats) { +func printFileWriterStats( + w io.Writer, stats FileWriterStats, counters *block.ByKind[block.LogicalBytesCompressed], +) { fmt.Fprintf(w, "Stats:\n") fmt.Fprintf(w, " BlockCount: %d\n", stats.BlockCount) fmt.Fprintf(w, " ValueCount: %d\n", stats.ValueCount) - fmt.Fprintf(w, " UncompressedValueBytes: %d\n", stats.UncompressedValueBytes) - fmt.Fprintf(w, " FileLen: %d\n", stats.FileLen) + fmt.Fprintf(w, " UncompressedValueBytes: %s\n", + crhumanize.Bytes(stats.UncompressedValueBytes, crhumanize.Exact, crhumanize.Compact), + ) + fmt.Fprintf(w, " FileLen: %s\n", crhumanize.Bytes(stats.FileLen, crhumanize.Exact, crhumanize.Compact)) + if counters != nil { + fmt.Fprintf(w, "Compression counters: value: %s other: %s\n", + crhumanize.Bytes(counters.ValueBlocks.Load(), crhumanize.Exact, crhumanize.Compact), + crhumanize.Bytes(counters.OtherBlocks.Load(), crhumanize.Exact, crhumanize.Compact), + ) + } } func TestHandleRoundtrip(t *testing.T) { diff --git a/sstable/blob/fetcher_test.go b/sstable/blob/fetcher_test.go index dbf95f0672..351c287737 100644 --- a/sstable/blob/fetcher_test.go +++ b/sstable/blob/fetcher_test.go @@ -106,7 +106,7 @@ func TestValueFetcher(t *testing.T) { if err != nil { t.Fatal(err) } - printFileWriterStats(&buf, stats) + printFileWriterStats(&buf, stats, nil) r, err := NewFileReader(ctx, obj, FileReaderOptions{ ReaderOptions: block.ReaderOptions{ diff --git a/sstable/blob/testdata/value_fetcher b/sstable/blob/testdata/value_fetcher index caa7a0166f..ec6197cdfa 100644 --- a/sstable/blob/testdata/value_fetcher +++ b/sstable/blob/testdata/value_fetcher @@ -55,8 +55,8 @@ nectarine Stats: BlockCount: 5 ValueCount: 26 - UncompressedValueBytes: 182 - FileLen: 386 + UncompressedValueBytes: 182B + FileLen: 386B define filenum=000002 target-block-size=64 block-size-threshold=90 kale @@ -89,8 +89,8 @@ microgreens Stats: BlockCount: 3 ValueCount: 13 - UncompressedValueBytes: 105 - FileLen: 248 + UncompressedValueBytes: 105B + FileLen: 248B define filenum=000003 target-block-size=64 block-size-threshold=90 beet @@ -113,8 +113,8 @@ potato Stats: BlockCount: 2 ValueCount: 8 - UncompressedValueBytes: 55 - FileLen: 172 + UncompressedValueBytes: 55B + FileLen: 172B define filenum=000004 target-block-size=64 block-size-threshold=90 onion @@ -135,8 +135,8 @@ chives Stats: BlockCount: 1 ValueCount: 7 - UncompressedValueBytes: 42 - FileLen: 137 + UncompressedValueBytes: 42B + FileLen: 137B define filenum=000005 target-block-size=64 block-size-threshold=90 shitake @@ -161,8 +161,8 @@ maitake Stats: BlockCount: 2 ValueCount: 9 - UncompressedValueBytes: 65 - FileLen: 183 + UncompressedValueBytes: 65B + FileLen: 183B define filenum=000006 target-block-size=64 block-size-threshold=90 squash @@ -175,8 +175,8 @@ cucumber Stats: BlockCount: 1 ValueCount: 3 - UncompressedValueBytes: 21 - FileLen: 112 + UncompressedValueBytes: 21B + FileLen: 112B new-fetcher name=iter1 ---- diff --git a/sstable/blob/testdata/writer b/sstable/blob/testdata/writer index dbb184f69e..5075094cb7 100644 --- a/sstable/blob/testdata/writer +++ b/sstable/blob/testdata/writer @@ -55,8 +55,9 @@ nectarine Stats: BlockCount: 5 ValueCount: 26 - UncompressedValueBytes: 182 - FileLen: 386 + UncompressedValueBytes: 182B + FileLen: 386B +Compression counters: value: 0B other: 0B open ---- @@ -121,8 +122,9 @@ nectarine Stats: BlockCount: 5 ValueCount: 26 - UncompressedValueBytes: 182 - FileLen: 472 + UncompressedValueBytes: 182B + FileLen: 472B +Compression counters: value: 0B other: 0B open ---- @@ -167,8 +169,9 @@ nectarine Stats: BlockCount: 3 ValueCount: 10 - UncompressedValueBytes: 60 - FileLen: 206 + UncompressedValueBytes: 60B + FileLen: 206B +Compression counters: value: 0B other: 0B open ---- diff --git a/sstable/block/block.go b/sstable/block/block.go index 30dacdfbf6..e1d9498bc3 100644 --- a/sstable/block/block.go +++ b/sstable/block/block.go @@ -339,6 +339,10 @@ type ReaderOptions struct { LoadBlockSema *fifo.Semaphore // LoggerAndTracer is an optional logger and tracer. LoggerAndTracer base.LoggerAndTracer + + // CompressionCounters, if non-nil, is used to keep track of how much data was + // decompressed. + CompressionCounters *ByLevel[ByKind[LogicalBytesDecompressed]] } // Init initializes the Reader to read blocks from the provided Readable. @@ -546,6 +550,10 @@ func (r *Reader) doRead( decompressed.Release() return Value{}, base.MarkCorruptionError(err) } + + if r.opts.CompressionCounters != nil { + r.opts.CompressionCounters.ForLevel(env.Level).ForKind(kind).Add(uint64(decodedLen)) + } } if err = initBlockMetadataFn(decompressed.BlockMetadata(), decompressed.BlockData()); err != nil { decompressed.Release() diff --git a/sstable/block/compression.go b/sstable/block/compression.go index 918a09dc28..4c16bd5cb8 100644 --- a/sstable/block/compression.go +++ b/sstable/block/compression.go @@ -7,31 +7,28 @@ package block import ( "runtime" "strings" + "sync/atomic" "github.com/cockroachdb/errors" + "github.com/cockroachdb/pebble/internal/base" "github.com/cockroachdb/pebble/internal/compression" + "github.com/cockroachdb/pebble/sstable/block/blockkind" ) // CompressionProfile contains the parameters for compressing blocks in an // sstable or blob file. // // CompressionProfile is a more advanced successor to Compression. +// +// Some blocks (like rangedel) never use compression; this is at the +// discretion of the sstable or blob file writer. +// +// Note that MinLZ is only supported with table formats v6+. Older formats +// fall back to Snappy. type CompressionProfile struct { Name string - // DataBlocks applies to sstable data blocks. - // ValueBlocks applies to sstable value blocks and blob file value blocks. - // OtherBlocks applies to all other blocks (such as index, filter, metadata - // blocks). - // - // Some blocks (like rangedel) never use compression; this is at the - // discretion of the sstable or blob file writer. - // - // Note that MinLZ is only supported with table formats v6+. Older formats - // fall back to Snappy. - DataBlocks compression.Setting - ValueBlocks compression.Setting - OtherBlocks compression.Setting + Settings ByKind[compression.Setting] // Blocks that are reduced by less than this percentage are stored // uncompressed. @@ -48,9 +45,9 @@ type CompressionProfile struct { // UsesMinLZ returns true if the profile uses the MinLZ compression algorithm // (for any block kind). func (p *CompressionProfile) UsesMinLZ() bool { - return p.DataBlocks.Algorithm == compression.MinLZ || - p.ValueBlocks.Algorithm == compression.MinLZ || - p.OtherBlocks.Algorithm == compression.MinLZ + return p.Settings.DataBlocks.Algorithm == compression.MinLZ || + p.Settings.ValueBlocks.Algorithm == compression.MinLZ || + p.Settings.OtherBlocks.Algorithm == compression.MinLZ } var ( @@ -68,10 +65,12 @@ var ( // FastCompression automatically chooses between Snappy/MinLZ1 and Zstd1 for // sstable and blob file value blocks. FastCompression = registerCompressionProfile(CompressionProfile{ - Name: "Fast", - DataBlocks: fastestCompression, - ValueBlocks: compression.ZstdLevel1, - OtherBlocks: fastestCompression, + Name: "Fast", + Settings: ByKind[compression.Setting]{ + DataBlocks: fastestCompression, + ValueBlocks: compression.ZstdLevel1, + OtherBlocks: fastestCompression, + }, MinReductionPercent: 10, AdaptiveReductionCutoffPercent: 30, }) @@ -79,10 +78,12 @@ var ( // BalancedCompression automatically chooses between Snappy/MinLZ1 and Zstd1 // for data and value blocks. BalancedCompression = registerCompressionProfile(CompressionProfile{ - Name: "Balanced", - DataBlocks: compression.ZstdLevel1, - ValueBlocks: compression.ZstdLevel1, - OtherBlocks: fastestCompression, + Name: "Balanced", + Settings: ByKind[compression.Setting]{ + DataBlocks: compression.ZstdLevel1, + ValueBlocks: compression.ZstdLevel1, + OtherBlocks: fastestCompression, + }, MinReductionPercent: 5, AdaptiveReductionCutoffPercent: 15, }) @@ -93,9 +94,11 @@ var ( // In practice, we have observed very little size benefit to using higher // zstd levels like ZstdLevel3 while paying a significant compression // performance cost. - DataBlocks: compression.ZstdLevel1, - ValueBlocks: compression.ZstdLevel1, - OtherBlocks: fastestCompression, + Settings: ByKind[compression.Setting]{ + DataBlocks: compression.ZstdLevel1, + ValueBlocks: compression.ZstdLevel1, + OtherBlocks: fastestCompression, + }, MinReductionPercent: 3, }) ) @@ -118,10 +121,12 @@ var fastestCompression = func() compression.Setting { // It should only be used during global initialization. func simpleCompressionProfile(name string, setting compression.Setting) *CompressionProfile { return registerCompressionProfile(CompressionProfile{ - Name: name, - DataBlocks: setting, - ValueBlocks: setting, - OtherBlocks: setting, + Name: name, + Settings: ByKind[compression.Setting]{ + DataBlocks: setting, + ValueBlocks: setting, + OtherBlocks: setting, + }, MinReductionPercent: 12, }) } @@ -226,3 +231,109 @@ func compressionIndicatorFromAlgorithm(algo compression.Algorithm) CompressionIn panic("invalid algorithm") } } + +// CompressionCounters holds running counters for the number of bytes compressed +// and decompressed. Counters are separated by L5 vs L6 vs other levels, and by +// data blocks vs value blocks vs other blocks. These are the same categories +// for which compression profiles can vary. +// +// The main purpose for these metrics is to allow estimating how overall CPU +// usage would change with a different compression algorithm (in conjunction +// with performance information about the algorithms, like that produced by the +// compression analyzer). +// +// In all cases, the figures refer to the uncompressed ("logical") size; i.e. +// the *input* size for compression and the *output* size for decompression. +// +// Note that even if the compressor does not use the result of a compression +// (because the block didn't compress), those bytes are still counted (they are +// relevant for CPU usage). +// +// Blocks for which compression is disabled upfront (e.g. filter blocks) are not +// counted. +type CompressionCounters struct { + Compressed ByLevel[ByKind[LogicalBytesCompressed]] + Decompressed ByLevel[ByKind[LogicalBytesDecompressed]] +} + +func (c *CompressionCounters) LoadCompressed() ByLevel[ByKind[uint64]] { + return mapByLevel(&c.Compressed, func(k *ByKind[LogicalBytesCompressed]) ByKind[uint64] { + return mapByKind(k, func(v *LogicalBytesCompressed) uint64 { + return v.Load() + }) + }) +} + +func (c *CompressionCounters) LoadDecompressed() ByLevel[ByKind[uint64]] { + return mapByLevel(&c.Decompressed, func(k *ByKind[LogicalBytesDecompressed]) ByKind[uint64] { + return mapByKind(k, func(v *LogicalBytesDecompressed) uint64 { + return v.Load() + }) + }) +} + +// LogicalBytesCompressed keeps a count of the logical bytes that were compressed. +type LogicalBytesCompressed struct { + atomic.Uint64 +} + +// LogicalBytesDecompressed keeps a count of the logical bytes that were decompressed. +type LogicalBytesDecompressed struct { + atomic.Uint64 +} + +// ByKind stores three different instances of T, one for sstable data +// blocks, one for sstable and blob file value blocks, and one for all other +// blocks. +type ByKind[T any] struct { + DataBlocks T + ValueBlocks T + OtherBlocks T +} + +func (b *ByKind[T]) ForKind(kind Kind) *T { + switch kind { + case blockkind.SSTableValue, blockkind.BlobValue: + return &b.ValueBlocks + case blockkind.SSTableData: + return &b.DataBlocks + default: + return &b.OtherBlocks + } +} + +func mapByKind[T, U any](b *ByKind[T], f func(*T) U) ByKind[U] { + return ByKind[U]{ + DataBlocks: f(&b.DataBlocks), + ValueBlocks: f(&b.ValueBlocks), + OtherBlocks: f(&b.OtherBlocks), + } +} + +// ByLevel stores three different instance of T, one for L5, one for L6, and one +// for all other levels. +type ByLevel[T any] struct { + L5 T + L6 T + OtherLevels T +} + +func (b *ByLevel[T]) ForLevel(level base.Level) *T { + if l, ok := level.Get(); ok { + switch l { + case 5: + return &b.L5 + case 6: + return &b.L6 + } + } + return &b.OtherLevels +} + +func mapByLevel[T, U any](b *ByLevel[T], f func(*T) U) ByLevel[U] { + return ByLevel[U]{ + L5: f(&b.L5), + L6: f(&b.L6), + OtherLevels: f(&b.OtherLevels), + } +} diff --git a/sstable/block/compression_test.go b/sstable/block/compression_test.go index 7dfb770826..acf1a6d4e3 100644 --- a/sstable/block/compression_test.go +++ b/sstable/block/compression_test.go @@ -20,7 +20,7 @@ func TestBufferRandomized(t *testing.T) { rng := rand.New(rand.NewPCG(0, seed)) var physBlockMaker PhysicalBlockMaker - physBlockMaker.Init(SnappyCompression, ChecksumTypeCRC32c) + physBlockMaker.Init(SnappyCompression, ChecksumTypeCRC32c, nil) defer physBlockMaker.Close() b := NewTempBuffer() defer b.Release() diff --git a/sstable/block/compressor.go b/sstable/block/compressor.go index d2065485b2..fd619ffe8a 100644 --- a/sstable/block/compressor.go +++ b/sstable/block/compressor.go @@ -5,6 +5,7 @@ package block import ( + "iter" "math/rand" "github.com/cockroachdb/pebble/internal/compression" @@ -18,12 +19,15 @@ import ( // .. = c.Compress(..) // c.Close() type Compressor struct { - minReductionPercent uint8 - dataBlocksCompressor compression.Compressor - valueBlocksCompressor compression.Compressor - otherBlocksCompressor compression.Compressor + minReductionPercent uint8 + + compressors ByKind[compression.Compressor] stats CompressionStats + + // inputBytes keeps track of the total number of bytes passed to the + // compressor, by block kind. + inputBytes [blockkind.NumKinds]uint64 } // MakeCompressor returns a Compressor that applies the given compression @@ -33,19 +37,19 @@ func MakeCompressor(profile *CompressionProfile) Compressor { minReductionPercent: profile.MinReductionPercent, } - c.dataBlocksCompressor = maybeAdaptiveCompressor(profile, profile.DataBlocks) - c.valueBlocksCompressor = maybeAdaptiveCompressor(profile, profile.ValueBlocks) - c.otherBlocksCompressor = compression.GetCompressor(profile.OtherBlocks) + c.compressors.DataBlocks = maybeAdaptiveCompressor(profile, profile.Settings.DataBlocks) + c.compressors.ValueBlocks = maybeAdaptiveCompressor(profile, profile.Settings.ValueBlocks) + c.compressors.OtherBlocks = compression.GetCompressor(profile.Settings.OtherBlocks) return c } func maybeAdaptiveCompressor( profile *CompressionProfile, setting compression.Setting, ) compression.Compressor { - if profile.AdaptiveReductionCutoffPercent != 0 && setting != profile.OtherBlocks { + if profile.AdaptiveReductionCutoffPercent != 0 && setting != profile.Settings.OtherBlocks { params := compression.AdaptiveCompressorParams{ Slow: setting, - Fast: profile.OtherBlocks, + Fast: profile.Settings.OtherBlocks, ReductionCutoff: float64(profile.AdaptiveReductionCutoffPercent) * 0.01, SampleEvery: 10, SampleHalfLife: 256 * 1024, // 256 KB @@ -59,9 +63,9 @@ func maybeAdaptiveCompressor( // Close must be called when the Compressor is no longer needed. // After Close is called, the Compressor must not be used again. func (c *Compressor) Close() { - c.dataBlocksCompressor.Close() - c.valueBlocksCompressor.Close() - c.otherBlocksCompressor.Close() + c.compressors.DataBlocks.Close() + c.compressors.ValueBlocks.Close() + c.compressors.OtherBlocks.Close() *c = Compressor{} } @@ -69,15 +73,9 @@ func (c *Compressor) Close() { // // In addition to the buffer, returns the algorithm that was used. func (c *Compressor) Compress(dst, src []byte, kind Kind) (CompressionIndicator, []byte) { - var compressor compression.Compressor - switch kind { - case blockkind.SSTableData: - compressor = c.dataBlocksCompressor - case blockkind.SSTableValue, blockkind.BlobValue: - compressor = c.valueBlocksCompressor - default: - compressor = c.otherBlocksCompressor - } + c.inputBytes[kind] += uint64(len(src)) + + compressor := *c.compressors.ForKind(kind) out, setting := compressor.Compress(dst, src) @@ -115,6 +113,18 @@ func (c *Compressor) Stats() *CompressionStats { return &c.stats } +// InputBytes returns an iterator over the total number of input bytes passed +// through the compressor, by block kind. +func (c *Compressor) InputBytes() iter.Seq2[Kind, uint64] { + return func(yield func(blockkind.Kind, uint64) bool) { + for k, v := range c.inputBytes { + if v != 0 && !yield(blockkind.Kind(k), v) { + return + } + } + } +} + type Decompressor = compression.Decompressor func GetDecompressor(c CompressionIndicator) Decompressor { diff --git a/sstable/block/compressor_test.go b/sstable/block/compressor_test.go index bdf326d2e3..16db59abcd 100644 --- a/sstable/block/compressor_test.go +++ b/sstable/block/compressor_test.go @@ -25,27 +25,29 @@ func TestCompressor(t *testing.T) { dst := make([]byte, 0, 1024) for runs := 0; runs < 100; runs++ { profile := &CompressionProfile{ - DataBlocks: settings[rand.IntN(len(settings))], - ValueBlocks: settings[rand.IntN(len(settings))], - OtherBlocks: settings[rand.IntN(len(settings))], + Settings: ByKind[compression.Setting]{ + DataBlocks: settings[rand.IntN(len(settings))], + ValueBlocks: settings[rand.IntN(len(settings))], + OtherBlocks: settings[rand.IntN(len(settings))], + }, MinReductionPercent: 0, } compressor := MakeCompressor(profile) ci, _ := compressor.Compress(dst, src, blockkind.SSTableData) - require.Equal(t, compressionIndicatorFromAlgorithm(profile.DataBlocks.Algorithm), ci) + require.Equal(t, compressionIndicatorFromAlgorithm(profile.Settings.DataBlocks.Algorithm), ci) ci, _ = compressor.Compress(dst, src, blockkind.SSTableValue) - require.Equal(t, compressionIndicatorFromAlgorithm(profile.ValueBlocks.Algorithm), ci) + require.Equal(t, compressionIndicatorFromAlgorithm(profile.Settings.ValueBlocks.Algorithm), ci) ci, _ = compressor.Compress(dst, src, blockkind.BlobValue) - require.Equal(t, compressionIndicatorFromAlgorithm(profile.ValueBlocks.Algorithm), ci) + require.Equal(t, compressionIndicatorFromAlgorithm(profile.Settings.ValueBlocks.Algorithm), ci) ci, _ = compressor.Compress(dst, src, blockkind.SSTableIndex) - require.Equal(t, compressionIndicatorFromAlgorithm(profile.OtherBlocks.Algorithm), ci) + require.Equal(t, compressionIndicatorFromAlgorithm(profile.Settings.OtherBlocks.Algorithm), ci) ci, _ = compressor.Compress(dst, src, blockkind.Metadata) - require.Equal(t, compressionIndicatorFromAlgorithm(profile.OtherBlocks.Algorithm), ci) + require.Equal(t, compressionIndicatorFromAlgorithm(profile.Settings.OtherBlocks.Algorithm), ci) compressor.Close() } diff --git a/sstable/block/physical.go b/sstable/block/physical.go index d069ab9843..04e10972d0 100644 --- a/sstable/block/physical.go +++ b/sstable/block/physical.go @@ -114,8 +114,9 @@ func WriteAndReleasePhysicalBlock( // // It is not thread-safe and should not be used concurrently. type PhysicalBlockMaker struct { - Compressor Compressor - Checksummer Checksummer + Compressor Compressor + Checksummer Checksummer + CompressionCounters *ByKind[LogicalBytesCompressed] } // PhysicalBlockFlags is a bitmask with flags used when making a physical block. @@ -127,15 +128,26 @@ const ( ) // Init the physical block maker. Closed must be called when no longer needed. -func (p *PhysicalBlockMaker) Init(profile *CompressionProfile, checksumType ChecksumType) { +func (p *PhysicalBlockMaker) Init( + profile *CompressionProfile, + checksumType ChecksumType, + compressionCounters *ByKind[LogicalBytesCompressed], +) { p.Compressor = MakeCompressor(profile) p.Checksummer.Init(checksumType) + p.CompressionCounters = compressionCounters } // Close must be called when the PhysicalBlockMaker is no longer needed. After // Close is called, the PhysicalBlockMaker must not be used again (unless it is // initialized again). func (p *PhysicalBlockMaker) Close() { + // Update the compression counters. + if p.CompressionCounters != nil { + for kind, bytes := range p.Compressor.InputBytes() { + p.CompressionCounters.ForKind(kind).Add(bytes) + } + } p.Compressor.Close() } diff --git a/sstable/compressionanalyzer/buckets.go b/sstable/compressionanalyzer/buckets.go index 60e00c06b5..a267618188 100644 --- a/sstable/compressionanalyzer/buckets.go +++ b/sstable/compressionanalyzer/buckets.go @@ -98,51 +98,63 @@ func (c Compressibility) String() string { var Profiles = [...]*block.CompressionProfile{ { - Name: "Snappy", - DataBlocks: compression.SnappySetting, - ValueBlocks: compression.SnappySetting, - OtherBlocks: compression.SnappySetting, + Name: "Snappy", + Settings: block.ByKind[compression.Setting]{ + DataBlocks: compression.SnappySetting, + ValueBlocks: compression.SnappySetting, + OtherBlocks: compression.SnappySetting, + }, MinReductionPercent: 0, }, { - Name: "MinLZ1", - DataBlocks: compression.MinLZFastest, - ValueBlocks: compression.MinLZFastest, - OtherBlocks: compression.MinLZFastest, + Name: "MinLZ1", + Settings: block.ByKind[compression.Setting]{ + DataBlocks: compression.MinLZFastest, + ValueBlocks: compression.MinLZFastest, + OtherBlocks: compression.MinLZFastest, + }, MinReductionPercent: 0, }, { - Name: "Zstd1", - DataBlocks: compression.ZstdLevel1, - ValueBlocks: compression.ZstdLevel1, - OtherBlocks: compression.ZstdLevel1, + Name: "Zstd1", + Settings: block.ByKind[compression.Setting]{ + DataBlocks: compression.ZstdLevel1, + ValueBlocks: compression.ZstdLevel1, + OtherBlocks: compression.ZstdLevel1, + }, MinReductionPercent: 0, }, { - Name: "Auto1/30", - DataBlocks: compression.ZstdLevel1, - ValueBlocks: compression.ZstdLevel1, - OtherBlocks: compression.MinLZFastest, + Name: "Auto1/30", + Settings: block.ByKind[compression.Setting]{ + DataBlocks: compression.ZstdLevel1, + ValueBlocks: compression.ZstdLevel1, + OtherBlocks: compression.MinLZFastest, + }, AdaptiveReductionCutoffPercent: 30, MinReductionPercent: 0, }, { - Name: "Auto1/15", - DataBlocks: compression.ZstdLevel1, - ValueBlocks: compression.ZstdLevel1, - OtherBlocks: compression.MinLZFastest, + Name: "Auto1/15", + Settings: block.ByKind[compression.Setting]{ + DataBlocks: compression.ZstdLevel1, + ValueBlocks: compression.ZstdLevel1, + OtherBlocks: compression.MinLZFastest, + }, AdaptiveReductionCutoffPercent: 15, MinReductionPercent: 0, }, { - Name: "Zstd3", - DataBlocks: compression.ZstdLevel3, - ValueBlocks: compression.ZstdLevel3, - OtherBlocks: compression.ZstdLevel3, + Name: "Zstd3", + Settings: block.ByKind[compression.Setting]{ + DataBlocks: compression.ZstdLevel3, + ValueBlocks: compression.ZstdLevel3, + OtherBlocks: compression.ZstdLevel3, + }, MinReductionPercent: 0, }, diff --git a/sstable/layout.go b/sstable/layout.go index aa51c2ffbe..052295dcf6 100644 --- a/sstable/layout.go +++ b/sstable/layout.go @@ -837,7 +837,7 @@ func (w *layoutWriter) Init(writable objstorage.Writable, opts WriterOptions) { w.writable = writable w.cacheOpts = opts.internal.CacheOpts w.tableFormat = opts.TableFormat - w.physBlockMaker.Init(opts.Compression, opts.Checksum) + w.physBlockMaker.Init(opts.Compression, opts.Checksum, opts.CompressionCounters) } type metaIndexHandle struct { diff --git a/sstable/options.go b/sstable/options.go index c5bfdefb00..679b9455b8 100644 --- a/sstable/options.go +++ b/sstable/options.go @@ -271,6 +271,9 @@ type WriterOptions struct { // internal fragmentation when loaded into the block cache. AllocatorSizeClasses []int + // CompressionCounters are updated by the writer (if not nil). + CompressionCounters *block.ByKind[block.LogicalBytesCompressed] + // internal options can only be used from within the pebble package. internal sstableinternal.WriterOptions diff --git a/sstable/suffix_rewriter.go b/sstable/suffix_rewriter.go index d75f2ead46..a7d1acbd02 100644 --- a/sstable/suffix_rewriter.go +++ b/sstable/suffix_rewriter.go @@ -168,7 +168,7 @@ func rewriteDataBlocksInParallel( rw := newDataBlockRewriter() var inputBlock, inputBlockBuf []byte var physBlockMaker block.PhysicalBlockMaker - physBlockMaker.Init(opts.Compression, opts.Checksum) + physBlockMaker.Init(opts.Compression, opts.Checksum, nil) defer physBlockMaker.Close() // We'll assume all blocks are _roughly_ equal so round-robin static partition // of each worker doing every ith block is probably enough. diff --git a/testdata/compaction/l0_to_lbase_compaction b/testdata/compaction/l0_to_lbase_compaction index defe2c8a6b..857fbc88eb 100644 --- a/testdata/compaction/l0_to_lbase_compaction +++ b/testdata/compaction/l0_to_lbase_compaction @@ -98,5 +98,10 @@ COMPRESSION algorithm | tables | blob files --------------+---------------+-------------- none | 234KB | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 233KB / 0B | 0B / 0B | 569B / 0B ---- ---- diff --git a/testdata/compaction/value_separation b/testdata/compaction/value_separation index da42ee16f5..ff3606fe34 100644 --- a/testdata/compaction/value_separation +++ b/testdata/compaction/value_separation @@ -191,7 +191,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 1 (392B) | 89.6% | 0.0% | 0 | 0 + 1 (408B) | 89.6% | 0.0% | 0 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -218,6 +218,13 @@ COMPRESSION --------------+----------------+-------------- none | 56B | 64B snappy | 134B (CR=1.15) | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 252B / 2.2KB | 0B / 0B | 1.4KB / 2.1KB + L5 | 0B / 228B | 0B / 0B | 0B / 0B + L6 | 154B / 1.2KB | 0B / 0B | 750B / 0B ---- ---- @@ -491,7 +498,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 1 (504B) | 78.6% | 0.0% | 0 | 0 + 1 (528B) | 78.6% | 0.0% | 0 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -518,6 +525,12 @@ COMPRESSION --------------+----------------+-------------- none | 61B | 149B snappy | 130B (CR=1.31) | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 292B / 1.5KB | 0B / 0B | 1.5KB / 2.1KB + L6 | 170B / 340B | 0B / 0B | 714B / 0B ---- ---- diff --git a/testdata/compaction/virtual_rewrite b/testdata/compaction/virtual_rewrite index 28196314c9..87c552cfca 100644 --- a/testdata/compaction/virtual_rewrite +++ b/testdata/compaction/virtual_rewrite @@ -202,6 +202,12 @@ COMPRESSION --------------+----------------+-------------- none | 116B | snappy | 296B (CR=1.23) | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 407B / 407B | 0B / 0B | 1.3KB / 5.4KB + L6 | 322B / 2KB | 0B / 0B | 1.9KB / 0B ---- ---- diff --git a/testdata/event_listener b/testdata/event_listener index 08aaf23d95..231fd2dbed 100644 --- a/testdata/event_listener +++ b/testdata/event_listener @@ -344,6 +344,12 @@ COMPRESSION --------------+---------------+-------------- none | 260B | snappy | 73B (CR=1.15) | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 252B / 168B | 0B / 0B | 1.9KB / 2.4KB + L6 | 76B / 0B | 0B / 0B | 632B / 0B ---- ---- @@ -487,6 +493,12 @@ COMPRESSION --------------+----------------+-------------- none | 520B | snappy | 146B (CR=1.15) | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 336B / 168B | 0B / 0B | 2.5KB / 4.7KB + L6 | 76B / 0B | 0B / 0B | 632B / 0B ---- ---- diff --git a/testdata/ingest b/testdata/ingest index 637c2faf6f..7de03ac394 100644 --- a/testdata/ingest +++ b/testdata/ingest @@ -67,7 +67,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 1 (280B) | 50.0% | 0.0% | 0 | 0 + 1 (288B) | 50.0% | 0.0% | 0 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -93,6 +93,11 @@ COMPRESSION algorithm | tables | blob files --------------+---------------+-------------- unknown | 463B | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 34B / 34B | 0B / 0B | 646B / 1.2KB ---- ---- diff --git a/testdata/metrics b/testdata/metrics index 6ca8c409dc..cd7e4c333a 100644 --- a/testdata/metrics +++ b/testdata/metrics @@ -85,6 +85,13 @@ COMPRESSION minlz | 1GB (CR=3) | 10GB (CR=3) zstd | 10GB (CR=5) | 100GB (CR=5) unknown | 500MB | 50MB + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 10GB / 100GB | 100GB / 1TB | 1GB / 10GB + L5 | 50GB / 500GB | 500GB / 4.9TB | 5GB / 50GB + L6 | 60GB / 600GB | 600GB / 5.9TB | 6GB / 60GB ---- ---- @@ -147,7 +154,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 1 (280B) | 0.0% | 0.0% | 1 | 0 + 1 (288B) | 0.0% | 0.0% | 1 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -175,6 +182,11 @@ COMPRESSION none | 36B | snappy | 76B (CR=1.14) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 87B / 87B | 0B / 0B | 708B / 672B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -250,7 +262,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 2 (560B) | 66.7% | 0.0% | 2 | 0 + 2 (576B) | 66.7% | 0.0% | 2 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -277,6 +289,12 @@ COMPRESSION --------------+---------------+-------------- none | 230B | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 174B / 174B | 0B / 0B | 1.4KB / 1.3KB + L6 | 158B / 0B | 0B / 0B | 1.4KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:738 BlockBytesInCache:112 BlockReadDuration:20ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -335,7 +353,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 2 (560B) | 66.7% | 0.0% | 2 | 0 + 2 (576B) | 66.7% | 0.0% | 2 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -362,6 +380,12 @@ COMPRESSION --------------+---------------+-------------- none | 230B | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 174B / 174B | 0B / 0B | 1.4KB / 1.3KB + L6 | 158B / 0B | 0B / 0B | 1.4KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:738 BlockBytesInCache:112 BlockReadDuration:20ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -417,7 +441,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 1 (280B) | 66.7% | 0.0% | 1 | 0 + 1 (288B) | 66.7% | 0.0% | 1 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -444,6 +468,12 @@ COMPRESSION --------------+---------------+-------------- none | 230B | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 174B / 174B | 0B / 0B | 1.4KB / 1.3KB + L6 | 158B / 0B | 0B / 0B | 1.4KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:738 BlockBytesInCache:112 BlockReadDuration:20ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -529,6 +559,12 @@ COMPRESSION --------------+---------------+-------------- none | 230B | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 174B / 174B | 0B / 0B | 1.4KB / 1.3KB + L6 | 158B / 0B | 0B / 0B | 1.4KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:738 BlockBytesInCache:112 BlockReadDuration:20ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -654,6 +690,12 @@ COMPRESSION none | 622B | 308B snappy | 630B (CR=1.27) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 972B / 174B | 0B / 0B | 6.5KB / 1.3KB + L6 | 158B / 0B | 0B / 0B | 1.4KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:738 BlockBytesInCache:112 BlockReadDuration:20ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -764,6 +806,12 @@ COMPRESSION none | 622B | 308B snappy | 615B (CR=1.3) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 972B / 972B | 0B / 0B | 6.5KB / 6.2KB + L6 | 956B / 0B | 0B / 0B | 6.2KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:5575 BlockBytesInCache:112 BlockReadDuration:160ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -925,6 +973,12 @@ COMPRESSION none | 1KB | 308B snappy | 843B (CR=1.26) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 1.4KB / 972B | 0B / 0B | 10KB / 9.6KB + L6 | 956B / 0B | 0B / 0B | 6.2KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:5575 BlockBytesInCache:112 BlockReadDuration:160ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -1048,6 +1102,12 @@ COMPRESSION none | 1.3KB | 308B snappy | 1.3KB (CR=1.21) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 2KB / 972B | 0B / 0B | 15KB / 9.6KB + L6 | 956B / 0B | 0B / 0B | 6.2KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:5575 BlockBytesInCache:112 BlockReadDuration:160ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -1180,6 +1240,12 @@ COMPRESSION none | 1.3KB | 308B snappy | 1.2KB (CR=1.22) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 2.1KB / 972B | 0B / 0B | 16KB / 11KB + L6 | 956B / 0B | 0B / 0B | 6.2KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:5575 BlockBytesInCache:112 BlockReadDuration:160ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -1356,6 +1422,12 @@ COMPRESSION none | 1.5KB | 308B snappy | 682B (CR=1.3) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 2.2KB / 1.6KB | 0B / 0B | 16KB / 18KB + L6 | 1.5KB / 0B | 0B / 0B | 11KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:11679 BlockBytesInCache:457 BlockReadDuration:330ms} a, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} @@ -1447,6 +1519,11 @@ COMPRESSION none | 108B | snappy | 228B (CR=1.14) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 261B / 0B | 0B / 0B | 2.1KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} ---- @@ -1528,6 +1605,12 @@ COMPRESSION --------------+---------------+-------------- none | 345B | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 261B / 261B | 0B / 0B | 2.1KB / 2KB + L6 | 237B / 0B | 0B / 0B | 1.8KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:1878 BlockBytesInCache:0 BlockReadDuration:60ms} ---- @@ -1625,6 +1708,12 @@ COMPRESSION --------------+---------------+-------------- none | 460B | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 340B / 261B | 0B / 0B | 2.7KB / 3.1KB + L6 | 237B / 0B | 0B / 0B | 1.8KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:1878 BlockBytesInCache:0 BlockReadDuration:60ms} ---- @@ -1715,6 +1804,12 @@ COMPRESSION none | 496B | snappy | 76B (CR=1.14) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 427B / 261B | 0B / 0B | 3.4KB / 3.1KB + L6 | 237B / 0B | 0B / 0B | 1.8KB / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:1878 BlockBytesInCache:0 BlockReadDuration:60ms} ---- @@ -1791,6 +1886,11 @@ COMPRESSION none | 496B | snappy | 76B (CR=1.14) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 0B / 0B | 0B / 0B | 0B / 5.9KB + Iter category stats: pebble-compaction, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} ---- @@ -1867,6 +1967,12 @@ COMPRESSION --------------+---------------+-------------- none | 345B | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 0B / 87B | 0B / 0B | 0B / 5.9KB + L6 | 79B / 0B | 0B / 0B | 696B / 0B + Iter category stats: pebble-compaction, non-latency: {BlockBytes:342 BlockBytesInCache:0 BlockReadDuration:30ms} ---- @@ -1959,7 +2065,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 2 (560B) | 0.0% | 0.0% | 0 | 0 + 2 (576B) | 0.0% | 0.0% | 0 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -1987,6 +2093,11 @@ COMPRESSION none | 266B | snappy | 221B (CR=1.19) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 262B / 0B | 0B / 0B | 3.5KB / 1.3KB + Iter category stats: pebble-compaction, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} ---- @@ -2038,7 +2149,7 @@ ITERATORS file cache | filter | open | open entries | hit rate | utilization | sst iters | snapshots -------------+------------+-------------+-------------+------------ - 2 (560B) | 0.0% | 0.0% | 0 | 0 + 2 (576B) | 0.0% | 0.0% | 0 | 0 FILES tables | blob files | blob values stats prog | backing | zombie | live | zombie | total | refed @@ -2066,6 +2177,11 @@ COMPRESSION none | 266B | snappy | 221B (CR=1.19) | + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks +------+----------------+----------------+--------------- +L0-L4 | 262B / 0B | 0B / 0B | 3.5KB / 1.3KB + Iter category stats: pebble-compaction, non-latency: {BlockBytes:0 BlockBytesInCache:0 BlockReadDuration:0s} ---- diff --git a/tool/testdata/db_lsm b/tool/testdata/db_lsm index 3b44ea6dc0..0c2e711f81 100644 --- a/tool/testdata/db_lsm +++ b/tool/testdata/db_lsm @@ -70,6 +70,9 @@ COMPRESSION algorithm | tables | blob files --------------+---------------+-------------- unknown | 709B | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks ---- ---- @@ -136,6 +139,9 @@ COMPRESSION algorithm | tables | blob files --------------+---------------+-------------- unknown | 709B | + + | logical bytes compressed / decompressed +level | data blocks | value blocks | other blocks LSM viewer: https://raduberinde.github.io/lsmview/decode.html#eJyE0EFLw0AQBeC7v2J4uU5lN42W7lHsrTe9SSgTOi2hm13NRqGV_HdJCaUWMXvaxzfMwPuG1y_1Ce5t_G6CNAqHtbk3YHRSeR1ZKvVwKMBI9UnhFmbJSI14r6nbHPQIZxhe2v0lW8ZWO6nPJ2CGVziqpM1swc-rNc1oF2Nm5_yyeh0XO1qY5dMQ9CN8NsmRzWlGdjj8HuvQpf820JWN8ZTZ_KI3wyFSK2GvtB1qKPuy59sm7HUPf3g-4fMJLyb8YcIff3vJOOjx3HclLRi7GFH2dz8BAAD__2dulBM= ---- ----