diff --git a/compaction.go b/compaction.go index 5b84951b86..eb27f33594 100644 --- a/compaction.go +++ b/compaction.go @@ -738,7 +738,7 @@ func (c *compaction) newInputIters( } } } else { - addItersForLevel := func(level *compactionLevel, l manifest.Level) error { + addItersForLevel := func(level *compactionLevel, l manifest.Layer) error { // Add a *levelIter for point iterators. Because we don't call // initRangeDel, the levelIter will close and forget the range // deletion iterator when it steps on to a new file. Surfacing range @@ -899,9 +899,9 @@ func (c *compaction) newInputIters( } func (c *compaction) newRangeDelIter( - newIters tableNewIters, f manifest.LevelFile, opts IterOptions, l manifest.Level, + newIters tableNewIters, f manifest.LevelFile, opts IterOptions, l manifest.Layer, ) (*noCloseIter, error) { - opts.level = l + opts.layer = l iterSet, err := newIters(context.Background(), f.FileMetadata, &opts, internalIterOpts{ compaction: true, diff --git a/compaction_picker.go b/compaction_picker.go index 273772af68..5911c4a130 100644 --- a/compaction_picker.go +++ b/compaction_picker.go @@ -130,7 +130,7 @@ func (s sortCompactionLevelsByPriority) Swap(i, j int) { // sublevel. type sublevelInfo struct { manifest.LevelSlice - sublevel manifest.Level + sublevel manifest.Layer } func (cl sublevelInfo) Clone() sublevelInfo { diff --git a/db.go b/db.go index 4bb30aec22..826374660f 100644 --- a/db.go +++ b/db.go @@ -1475,7 +1475,7 @@ func (i *Iterator) constructPointIter( mlevels = mlevels[:numMergingLevels] levels = levels[:numLevelIters] i.opts.snapshotForHideObsoletePoints = buf.dbi.seqNum - addLevelIterForFiles := func(files manifest.LevelIterator, level manifest.Level) { + addLevelIterForFiles := func(files manifest.LevelIterator, level manifest.Layer) { li := &levels[levelsIndex] li.init(ctx, i.opts, &i.comparer, i.newIters, files, level, internalOpts) diff --git a/download.go b/download.go index 3c4a331a4e..f0f31bebfb 100644 --- a/download.go +++ b/download.go @@ -248,7 +248,7 @@ func (d *DB) newDownloadSpanTask(vers *version, sp DownloadSpan) (_ *downloadSpa // [sp.StartKey, sp.EndKey). Expand the bounds to the left so that we // include the start keys of any external sstables that overlap with // sp.StartKey. - vers.IterAllLevelsAndSublevels(func(iter manifest.LevelIterator, level, sublevel int) { + vers.IterAllLevelsAndSublevels(func(iter manifest.LevelIterator, level manifest.Layer) { if f := iter.SeekGE(d.cmp, sp.StartKey); f != nil && objstorage.IsExternalTable(d.objProvider, f.FileBacking.DiskFileNum) && d.cmp(f.Smallest.UserKey, bounds.Start) < 0 { diff --git a/flushable.go b/flushable.go index eca3722358..c014bb9b11 100644 --- a/flushable.go +++ b/flushable.go @@ -215,7 +215,7 @@ func (s *ingestedFlushable) newIter(o *IterOptions) internalIterator { opts = *o } return newLevelIter( - context.Background(), opts, s.comparer, s.newIters, s.slice.Iter(), manifest.FlushableIngestLevel(), + context.Background(), opts, s.comparer, s.newIters, s.slice.Iter(), manifest.FlushableIngestsLayer(), internalIterOpts{}, ) } @@ -248,7 +248,7 @@ func (s *ingestedFlushable) newRangeDelIter(_ *IterOptions) keyspan.FragmentIter return keyspanimpl.NewLevelIter( context.TODO(), keyspan.SpanIterOptions{}, s.comparer.Compare, - s.constructRangeDelIter, s.slice.Iter(), manifest.FlushableIngestLevel(), + s.constructRangeDelIter, s.slice.Iter(), manifest.FlushableIngestsLayer(), manifest.KeyTypePoint, ) } @@ -262,7 +262,7 @@ func (s *ingestedFlushable) newRangeKeyIter(o *IterOptions) keyspan.FragmentIter return keyspanimpl.NewLevelIter( context.TODO(), keyspan.SpanIterOptions{}, s.comparer.Compare, s.newRangeKeyIters, - s.slice.Iter(), manifest.FlushableIngestLevel(), manifest.KeyTypeRange, + s.slice.Iter(), manifest.FlushableIngestsLayer(), manifest.KeyTypeRange, ) } diff --git a/get_iter.go b/get_iter.go index 511685b0a7..0410feb015 100644 --- a/get_iter.go +++ b/get_iter.go @@ -257,7 +257,7 @@ func (g *getIter) initializeNextIterator() (ok bool) { // split user keys across adjacent sstables within a level, ensuring that at // most one sstable overlaps g.key. func (g *getIter) getSSTableIterators( - files manifest.LevelIterator, level manifest.Level, + files manifest.LevelIterator, level manifest.Layer, ) (internalIterator, keyspan.FragmentIterator, error) { files = files.Filter(manifest.KeyTypePoint) m := files.SeekGE(g.comparer.Compare, g.key) @@ -270,7 +270,7 @@ func (g *getIter) getSSTableIterators( return emptyIter, nil, nil } // m may possibly contain point (or range deletion) keys relevant to g.key. - g.iterOpts.level = level + g.iterOpts.layer = level iters, err := g.newIters(context.Background(), m, &g.iterOpts, internalIterOpts{}, iterPointKeys|iterRangeDeletions) if err != nil { return emptyIter, nil, err diff --git a/ingest.go b/ingest.go index b29bc02e0a..d0972d1544 100644 --- a/ingest.go +++ b/ingest.go @@ -1727,7 +1727,7 @@ func (d *DB) excise( Category: "pebble-ingest", QoSLevel: sstable.LatencySensitiveQoSLevel, }, - level: manifest.Level(level), + layer: manifest.Level(level), }, internalIterOpts{}, iterPointKeys|iterRangeDeletions|iterRangeKeys) itersLoaded = true return err diff --git a/ingest_test.go b/ingest_test.go index a39ad51a4d..b8216b9d3d 100644 --- a/ingest_test.go +++ b/ingest_test.go @@ -837,9 +837,9 @@ func TestExcise(t *testing.T) { d.mu.Unlock() current := d.mu.versions.currentVersion() - current.IterAllLevelsAndSublevels(func(iter manifest.LevelIterator, level, _ int) { + current.IterAllLevelsAndSublevels(func(iter manifest.LevelIterator, l manifest.Layer) { for m := iter.SeekGE(d.cmp, exciseSpan.Start); m != nil && d.cmp(m.Smallest.UserKey, exciseSpan.End) < 0; m = iter.Next() { - _, err := d.excise(exciseSpan.UserKeyBounds(), m, ve, level) + _, err := d.excise(exciseSpan.UserKeyBounds(), m, ve, l.Level()) if err != nil { td.Fatalf(t, "error when excising %s: %s", m.FileNum, err.Error()) } diff --git a/internal/keyspan/keyspanimpl/level_iter.go b/internal/keyspan/keyspanimpl/level_iter.go index 2fc5b8362f..9334a92090 100644 --- a/internal/keyspan/keyspanimpl/level_iter.go +++ b/internal/keyspan/keyspanimpl/level_iter.go @@ -42,8 +42,8 @@ type TableNewSpanIter func( type LevelIter struct { cmp base.Compare keyType manifest.KeyType - // The LSM level this LevelIter is initialized for. Used in logging. - level manifest.Level + // The LSM level this LevelIter is initialized for. + level manifest.Layer // newIter creates a range del iterator if keyType is KeyTypePoint or a range // key iterator if keyType is KeyTypeRange. newIter TableNewSpanIter @@ -87,7 +87,7 @@ func NewLevelIter( cmp base.Compare, newIter TableNewSpanIter, files manifest.LevelIterator, - level manifest.Level, + level manifest.Layer, keyType manifest.KeyType, ) *LevelIter { l := &LevelIter{} @@ -105,7 +105,7 @@ func (l *LevelIter) Init( cmp base.Compare, newIter TableNewSpanIter, files manifest.LevelIterator, - level manifest.Level, + level manifest.Layer, keyType manifest.KeyType, ) { if keyType != manifest.KeyTypePoint && keyType != manifest.KeyTypeRange { diff --git a/internal/keyspan/keyspanimpl/level_iter_test.go b/internal/keyspan/keyspanimpl/level_iter_test.go index 3ef71a12b2..decff7ca91 100644 --- a/internal/keyspan/keyspanimpl/level_iter_test.go +++ b/internal/keyspan/keyspanimpl/level_iter_test.go @@ -317,7 +317,7 @@ func TestLevelIterEquivalence(t *testing.T) { levelIter.Init( context.Background(), keyspan.SpanIterOptions{}, base.DefaultComparer.Compare, tableNewIters, - v.Levels[6].Iter(), 0, manifest.KeyTypeRange, + v.Levels[6].Iter(), manifest.Level(0), manifest.KeyTypeRange, ) levelIters = append(levelIters, &levelIter) } @@ -428,7 +428,7 @@ func TestLevelIter(t *testing.T) { metas[i] = files[i].meta } lm := manifest.MakeLevelMetadata(cmp, 6, metas) - iter := NewLevelIter(context.Background(), keyspan.SpanIterOptions{}, cmp, tableNewIters, lm.Iter(), 6, keyType) + iter := NewLevelIter(context.Background(), keyspan.SpanIterOptions{}, cmp, tableNewIters, lm.Iter(), manifest.Level(6), keyType) extraInfo := func() string { return iter.String() } diff --git a/internal/manifest/layer.go b/internal/manifest/layer.go new file mode 100644 index 0000000000..12e824d9cd --- /dev/null +++ b/internal/manifest/layer.go @@ -0,0 +1,121 @@ +// Copyright 2020 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 manifest + +import ( + "fmt" + "math" + + "github.com/cockroachdb/redact" +) + +// Layer represents a section of the logical sstable hierarchy. It can represent: +// - a level L1 through L6, or +// - the entire L0 level, or +// - a specific L0 sublevel, or +// - the layer of flushable ingests (which is conceptually above the LSM). +type Layer struct { + kind layerKind + value uint16 +} + +// Level returns a Layer that represents an entire level (L0 through L6). +func Level(level int) Layer { + if level < 0 || level >= NumLevels { + panic("invalid level") + } + return Layer{ + kind: levelLayer, + value: uint16(level), + } +} + +// L0Sublevel returns a Layer that represents a specific L0 sublevel. +func L0Sublevel(sublevel int) Layer { + // Note: Pebble stops writes once we get to 1000 sublevels. + if sublevel < 0 || sublevel > math.MaxUint16 { + panic("invalid sublevel") + } + return Layer{ + kind: l0SublevelLayer, + value: uint16(sublevel), + } +} + +// FlushableIngestsLayer returns a Layer that represents the flushable ingests +// layer (which is logically above L0). +func FlushableIngestsLayer() Layer { + return Layer{ + kind: flushableIngestsLayer, + } +} + +// IsSet returns true if l has been initialized. +func (l Layer) IsSet() bool { + return l.kind != 0 +} + +// IsFlushableIngests returns true if the layer represents flushable ingests. +func (l Layer) IsFlushableIngests() bool { + return l.kind == flushableIngestsLayer +} + +// IsL0Sublevel returns true if the layer represents an L0 sublevel. +func (l Layer) IsL0Sublevel() bool { + return l.kind == l0SublevelLayer +} + +// Level returns the level for the layer. Must not be called if +// the layer represents flushable ingests. +func (l Layer) Level() int { + switch l.kind { + case levelLayer: + return int(l.value) + case l0SublevelLayer: + return 0 + case flushableIngestsLayer: + panic("flushable ingests layer") + default: + panic("invalid layer") + } +} + +// Sublevel returns the L0 sublevel. Can only be called if the layer represents +// an L0 sublevel. +func (l Layer) Sublevel() int { + if !l.IsL0Sublevel() { + panic("not an L0 sublevel layer") + } + return int(l.value) +} + +func (l Layer) String() string { + switch l.kind { + case levelLayer: + return fmt.Sprintf("L%d", l.value) + case l0SublevelLayer: + return fmt.Sprintf("L0.%d", l.value) + case flushableIngestsLayer: + return "flushable-ingests" + default: + return "unknown" + } +} + +// SafeFormat implements redact.SafeFormatter. +func (l Layer) SafeFormat(s redact.SafePrinter, verb rune) { + s.SafeString(redact.SafeString(l.String())) +} + +type layerKind uint8 + +const ( + // Entire level: value contains the level number (0 through 6). + levelLayer layerKind = iota + 1 + // L0 sublevel: value contains the sublevel number. + l0SublevelLayer + // Flushable ingests layer: value is unused. + flushableIngestsLayer +) diff --git a/internal/manifest/level_test.go b/internal/manifest/layer_test.go similarity index 84% rename from internal/manifest/level_test.go rename to internal/manifest/layer_test.go index 98b726f8e4..ad1b8a2d56 100644 --- a/internal/manifest/level_test.go +++ b/internal/manifest/layer_test.go @@ -10,9 +10,9 @@ import ( "github.com/stretchr/testify/require" ) -func TestLevel(t *testing.T) { +func TestLayer(t *testing.T) { testCases := []struct { - level Level + layer Layer expected string }{ {Level(0), "L0"}, @@ -27,12 +27,12 @@ func TestLevel(t *testing.T) { {L0Sublevel(1), "L0.1"}, {L0Sublevel(2), "L0.2"}, - {FlushableIngestLevel(), "flushable-ingest"}, + {FlushableIngestsLayer(), "flushable-ingests"}, } for _, c := range testCases { t.Run(c.expected, func(t *testing.T) { - s := c.level.String() + s := c.layer.String() require.EqualValues(t, c.expected, s) }) } diff --git a/internal/manifest/level.go b/internal/manifest/level.go deleted file mode 100644 index ce0e854fa4..0000000000 --- a/internal/manifest/level.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2020 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 manifest - -import "fmt" - -const ( - // 3 bits are necessary to represent level values from 0-6 or 7 for flushable - // ingests. - levelBits = 3 - levelMask = (1 << levelBits) - 1 - // invalidSublevel denotes an invalid or non-applicable sublevel. - invalidSublevel = -1 - flushableIngestLevelValue = 7 -) - -// Level encodes a level and optional sublevel. It can also represent the -// conceptual layer of flushable ingests as "level" -1. -// -// The encoding has the property that Level(0) == L0Sublevel(invalidSublevel). -type Level uint32 - -func makeLevel(level, sublevel int) Level { - return Level(((sublevel + 1) << levelBits) | level) -} - -// LevelToInt returns the int representation of a Level. Returns -1 if the Level -// refers to the flushable ingests pseudo-level. -func LevelToInt(l Level) int { - l &= levelMask - if l == flushableIngestLevelValue { - return -1 - } - return int(l) -} - -// L0Sublevel returns a Level representing the specified L0 sublevel. -func L0Sublevel(sublevel int) Level { - if sublevel < 0 { - panic(fmt.Sprintf("invalid L0 sublevel: %d", sublevel)) - } - return makeLevel(0, sublevel) -} - -// FlushableIngestLevel returns a Level that represents the flushable ingests -// pseudo-level. -func FlushableIngestLevel() Level { - return makeLevel(flushableIngestLevelValue, invalidSublevel) -} - -// FlushableIngestLevel returns true if l represents the flushable ingests -// pseudo-level. -func (l Level) FlushableIngestLevel() bool { - return LevelToInt(l) == -1 -} - -func (l Level) String() string { - level := int(l) & levelMask - sublevel := (int(l) >> levelBits) - 1 - if sublevel != invalidSublevel { - return fmt.Sprintf("L%d.%d", level, sublevel) - } - if level == flushableIngestLevelValue { - return "flushable-ingest" - } - return fmt.Sprintf("L%d", level) -} diff --git a/internal/manifest/version.go b/internal/manifest/version.go index a90717e836..8145387691 100644 --- a/internal/manifest/version.go +++ b/internal/manifest/version.go @@ -1527,12 +1527,12 @@ func (v *Version) Overlaps(level int, bounds base.UserKeyBounds) LevelSlice { // IterAllLevelsAndSublevels calls fn with an iterator for each L0 sublevel // (from top to bottom), then once for each level below L0. -func (v *Version) IterAllLevelsAndSublevels(fn func(it LevelIterator, level int, sublevel int)) { +func (v *Version) IterAllLevelsAndSublevels(fn func(it LevelIterator, level Layer)) { for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- { - fn(v.L0SublevelFiles[sublevel].Iter(), 0, sublevel) + fn(v.L0SublevelFiles[sublevel].Iter(), L0Sublevel(sublevel)) } for level := 1; level < NumLevels; level++ { - fn(v.Levels[level].Iter(), level, invalidSublevel) + fn(v.Levels[level].Iter(), Level(level)) } } @@ -1620,7 +1620,7 @@ func (l *VersionList) Remove(v *Version) { // CheckOrdering checks that the files are consistent with respect to // seqnums (for level 0 files -- see detailed comment below) and increasing and non- // overlapping internal key ranges (for non-level 0 files). -func CheckOrdering(cmp Compare, format base.FormatKey, level Level, files LevelIterator) error { +func CheckOrdering(cmp Compare, format base.FormatKey, level Layer, files LevelIterator) error { // The invariants to check for L0 sublevels are the same as the ones to // check for all other levels. However, if L0 is not organized into // sublevels, or if all L0 files are being passed in, we do the legacy L0 diff --git a/iterator.go b/iterator.go index 2a2e5ce24f..09c98026f3 100644 --- a/iterator.go +++ b/iterator.go @@ -835,10 +835,10 @@ func (i *Iterator) sampleRead() { } if len(mi.levels) > 1 { mi.ForEachLevelIter(func(li *levelIter) (done bool) { - if li.level.FlushableIngestLevel() { + if li.layer.IsFlushableIngests() { return false } - l := manifest.LevelToInt(li.level) + l := li.layer.Level() if f := li.iterFile; f != nil { var containsKey bool if i.pos == iterPosNext || i.pos == iterPosCurForward || diff --git a/level_checker.go b/level_checker.go index ce53be8f3a..70668b7ca8 100644 --- a/level_checker.go +++ b/level_checker.go @@ -401,7 +401,7 @@ func checkRangeTombstones(c *checkConfig) error { for f := files.First(); f != nil; f = files.Next() { lf := files.Take() iters, err := c.newIters( - context.Background(), lf.FileMetadata, &IterOptions{level: manifest.Level(lsmLevel)}, + context.Background(), lf.FileMetadata, &IterOptions{layer: manifest.Level(lsmLevel)}, internalIterOpts{}, iterRangeDeletions) if err != nil { return err diff --git a/level_iter.go b/level_iter.go index 0e0390e2b2..5255ece5f7 100644 --- a/level_iter.go +++ b/level_iter.go @@ -60,8 +60,9 @@ type levelIter struct { // tableOpts.{Lower,Upper}Bound are nil, the corresponding iteration boundary // does not lie within the table bounds. tableOpts IterOptions - // The LSM level this levelIter is initialized for. - level manifest.Level + // The layer this levelIter is initialized for. This can be either + // a level L1+, an L0 sublevel, or a flushable ingests layer. + layer manifest.Layer // combinedIterState may be set when a levelIter is used during user // iteration. Although levelIter only iterates over point keys, it's also // responsible for lazily constructing the combined range & point iterator @@ -133,11 +134,11 @@ func newLevelIter( comparer *Comparer, newIters tableNewIters, files manifest.LevelIterator, - level manifest.Level, + layer manifest.Layer, internalOpts internalIterOpts, ) *levelIter { l := &levelIter{} - l.init(ctx, opts, comparer, newIters, files, level, internalOpts) + l.init(ctx, opts, comparer, newIters, files, layer, internalOpts) return l } @@ -147,12 +148,12 @@ func (l *levelIter) init( comparer *Comparer, newIters tableNewIters, files manifest.LevelIterator, - level manifest.Level, + layer manifest.Layer, internalOpts internalIterOpts, ) { l.ctx = ctx l.err = nil - l.level = level + l.layer = layer l.logger = opts.getLogger() l.prefix = nil l.lower = opts.LowerBound @@ -164,7 +165,7 @@ func (l *levelIter) init( } l.tableOpts.UseL6Filters = opts.UseL6Filters l.tableOpts.CategoryAndQoS = opts.CategoryAndQoS - l.tableOpts.level = l.level + l.tableOpts.layer = l.layer l.tableOpts.snapshotForHideObsoletePoints = opts.snapshotForHideObsoletePoints l.comparer = comparer l.cmp = comparer.Compare @@ -619,10 +620,10 @@ func (l *levelIter) verify(kv *base.InternalKV) *base.InternalKV { // bounds as such keys are always range tombstones which will be skipped // by the Iterator. if l.lower != nil && kv != nil && !kv.K.IsExclusiveSentinel() && l.cmp(kv.K.UserKey, l.lower) < 0 { - l.logger.Fatalf("levelIter %s: lower bound violation: %s < %s\n%s", l.level, kv, l.lower, debug.Stack()) + l.logger.Fatalf("levelIter %s: lower bound violation: %s < %s\n%s", l.layer, kv, l.lower, debug.Stack()) } if l.upper != nil && kv != nil && !kv.K.IsExclusiveSentinel() && l.cmp(kv.K.UserKey, l.upper) > 0 { - l.logger.Fatalf("levelIter %s: upper bound violation: %s > %s\n%s", l.level, kv, l.upper, debug.Stack()) + l.logger.Fatalf("levelIter %s: upper bound violation: %s > %s\n%s", l.layer, kv, l.upper, debug.Stack()) } } return kv @@ -956,9 +957,9 @@ func (l *levelIter) DebugTree(tp treeprinter.Node) { func (l *levelIter) String() string { if l.iterFile != nil { - return fmt.Sprintf("%s: fileNum=%s", l.level, l.iterFile.FileNum.String()) + return fmt.Sprintf("%s: fileNum=%s", l.layer, l.iterFile.FileNum.String()) } - return fmt.Sprintf("%s: fileNum=", l.level) + return fmt.Sprintf("%s: fileNum=", l.layer) } var _ internalIterator = &levelIter{} diff --git a/options.go b/options.go index a7542755a3..df1cccf6ec 100644 --- a/options.go +++ b/options.go @@ -196,9 +196,9 @@ type IterOptions struct { // Internal options. logger Logger - // Level corresponding to this file. Only passed in if constructed by a + // Layer corresponding to this file. Only passed in if constructed by a // levelIter. - level manifest.Level + layer manifest.Layer // disableLazyCombinedIteration is an internal testing option. disableLazyCombinedIteration bool // snapshotForHideObsoletePoints is specified for/by levelIter when opening diff --git a/scan_internal.go b/scan_internal.go index 6081c1722a..7124600d1b 100644 --- a/scan_internal.go +++ b/scan_internal.go @@ -539,7 +539,7 @@ func (d *DB) truncateSharedFile( iters, err := d.newIters(ctx, file, &IterOptions{ LowerBound: lower, UpperBound: upper, - level: manifest.Level(level), + layer: manifest.Level(level), }, internalIterOpts{}, iterPointKeys|iterRangeDeletions|iterRangeKeys) if err != nil { return nil, false, err @@ -900,7 +900,7 @@ func (i *scanInternalIterator) constructPointIter( rangeDelLevels = rangeDelLevels[:numLevelIters] i.opts.IterOptions.snapshotForHideObsoletePoints = i.seqNum i.opts.IterOptions.CategoryAndQoS = categoryAndQoS - addLevelIterForFiles := func(files manifest.LevelIterator, level manifest.Level) { + addLevelIterForFiles := func(files manifest.LevelIterator, level manifest.Layer) { li := &levels[levelsIndex] rli := &rangeDelLevels[levelsIndex] diff --git a/table_cache.go b/table_cache.go index 32071f2095..c8690ce8e5 100644 --- a/table_cache.go +++ b/table_cache.go @@ -565,13 +565,15 @@ func (c *tableCacheShard) newPointIter( // By default, we don't use block filters for L6 and restrict the size for // flushable ingests, as these blocks can be very big. if !opts.UseL6Filters { - if opts.level == manifest.Level(6) { + if opts.layer == manifest.Level(6) { filterBlockSizeLimit = sstable.NeverUseFilterBlock - } else if opts.level.FlushableIngestLevel() { + } else if opts.layer.IsFlushableIngests() { filterBlockSizeLimit = filterBlockSizeLimitForFlushableIngests } } - ctx = objiotracing.WithLevel(ctx, manifest.LevelToInt(opts.level)) + if opts.layer.IsSet() && !opts.layer.IsFlushableIngests() { + ctx = objiotracing.WithLevel(ctx, opts.layer.Level()) + } } tableFormat, err := v.reader.TableFormat() if err != nil {