Skip to content

Commit 1bddddf

Browse files
committed
base: move cache.Level to base
We also rework the implementation so it's less confusing.
1 parent 97df4e1 commit 1bddddf

File tree

15 files changed

+102
-74
lines changed

15 files changed

+102
-74
lines changed

db_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ func TestMemTableReservation(t *testing.T) {
858858
t.Fatalf("expected 2 refs, but found %d", refs)
859859
}
860860
// Verify the memtable reservation has caused our test block to be evicted.
861-
if cv := tmpHandle.Peek(base.DiskFileNum(0), 0, cache.MakeLevel(0), cache.CategoryBackground); cv != nil {
861+
if cv := tmpHandle.Peek(base.DiskFileNum(0), 0, base.MakeLevel(0), cache.CategoryBackground); cv != nil {
862862
t.Fatalf("expected failure, but found success: %#v", cv)
863863
}
864864

file_cache.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ func (h *fileCacheHandle) newIters(
591591
internalOpts.readEnv.IsSharedIngested = env.IsSharedIngested
592592
internalOpts.readEnv.InternalBounds = env.InternalBounds
593593
if opts != nil && opts.layer.IsSet() && !opts.layer.IsFlushableIngests() {
594-
internalOpts.readEnv.Block.Level = cache.MakeLevel(opts.layer.Level())
594+
internalOpts.readEnv.Block.Level = base.MakeLevel(opts.layer.Level())
595595
}
596596

597597
var iters iterSet

internal/base/level.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2025 The LevelDB-Go and Pebble Authors. All rights reserved. Use
2+
// of this source code is governed by a BSD-style license that can be found in
3+
// the LICENSE file.
4+
5+
package base
6+
7+
import (
8+
"fmt"
9+
10+
"github.com/cockroachdb/errors"
11+
"github.com/cockroachdb/pebble/internal/invariants"
12+
)
13+
14+
// Level identifies an LSM level. The zero value indicates that the level is
15+
// uninitialized or unknown.
16+
type Level struct {
17+
// v contains the level in the lowest 7 bits. The highest bit is set iff the
18+
// value is valid.
19+
v uint8
20+
}
21+
22+
const validBit = 1 << 7
23+
24+
func (l Level) Get() (level int, ok bool) {
25+
return int(l.v &^ validBit), l.Valid()
26+
}
27+
28+
func (l Level) Valid() bool {
29+
return l.v&validBit != 0
30+
}
31+
32+
func (l Level) String() string {
33+
if level, ok := l.Get(); ok {
34+
return fmt.Sprintf("L%d", level)
35+
}
36+
return "n/a"
37+
}
38+
39+
func MakeLevel(l int) Level {
40+
if invariants.Enabled && l < 0 || l >= validBit {
41+
panic(errors.AssertionFailedf("invalid level: %d", l))
42+
}
43+
return Level{uint8(l) | validBit}
44+
}

internal/cache/cache.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func (c *Handle) Cache() *Cache {
253253
// Peek supports the special CategoryHidden category, in which case the hit or
254254
// miss is not recorded in metrics.
255255
func (c *Handle) Peek(
256-
fileNum base.DiskFileNum, offset uint64, level Level, category Category,
256+
fileNum base.DiskFileNum, offset uint64, level base.Level, category Category,
257257
) *Value {
258258
k := makeKey(c.id, fileNum, offset)
259259
return c.cache.getShard(k).get(k, level, category, true /* peekOnly */)
@@ -265,7 +265,7 @@ const CategoryHidden Category = -1
265265
// Get retrieves the cache value for the specified file and offset, returning
266266
// nil if no value is present.
267267
func (c *Handle) Get(
268-
fileNum base.DiskFileNum, offset uint64, level Level, category Category,
268+
fileNum base.DiskFileNum, offset uint64, level base.Level, category Category,
269269
) *Value {
270270
k := makeKey(c.id, fileNum, offset)
271271
return c.cache.getShard(k).get(k, level, category, false /* peekOnly */)
@@ -295,7 +295,7 @@ func (c *Handle) Get(
295295
// While waiting, someone else may successfully read the value, which results
296296
// in a valid Handle being returned. This is a case where cacheHit=false.
297297
func (c *Handle) GetWithReadHandle(
298-
ctx context.Context, fileNum base.DiskFileNum, offset uint64, level Level, category Category,
298+
ctx context.Context, fileNum base.DiskFileNum, offset uint64, level base.Level, category Category,
299299
) (
300300
cv *Value,
301301
rh ReadHandle,

internal/cache/cache_test.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestCache(t *testing.T) {
4141
wantHit := fields[1][0] == 'h'
4242

4343
var hit bool
44-
cv := h.Get(base.DiskFileNum(key), 0, MakeLevel(0), CategorySSTableData)
44+
cv := h.Get(base.DiskFileNum(key), 0, base.MakeLevel(0), CategorySSTableData)
4545
if cv == nil {
4646
cv = Alloc(1)
4747
cv.RawBuffer()[0] = fields[0][0]
@@ -81,14 +81,14 @@ func TestCachePeek(t *testing.T) {
8181
setTestValue(h, 0, uint64(i), "a", 1)
8282
}
8383
for i := range size / 2 {
84-
v := h.Get(base.DiskFileNum(0), uint64(i), MakeLevel(0), CategoryBackground)
84+
v := h.Get(base.DiskFileNum(0), uint64(i), base.MakeLevel(0), CategoryBackground)
8585
if v == nil {
8686
t.Fatalf("expected to find block %d", i)
8787
}
8888
v.Release()
8989
}
9090
for i := size / 2; i < size; i++ {
91-
v := h.Peek(base.DiskFileNum(0), uint64(i), MakeLevel(0), CategoryBackground)
91+
v := h.Peek(base.DiskFileNum(0), uint64(i), base.MakeLevel(0), CategoryBackground)
9292
if v == nil {
9393
t.Fatalf("expected to find block %d", i)
9494
}
@@ -100,7 +100,7 @@ func TestCachePeek(t *testing.T) {
100100
}
101101
// Verify that the Gets still find their values, despite the Peeks.
102102
for i := range size / 2 {
103-
v := h.Get(base.DiskFileNum(0), uint64(i), MakeLevel(0), CategoryBackground)
103+
v := h.Get(base.DiskFileNum(0), uint64(i), base.MakeLevel(0), CategoryBackground)
104104
if v == nil {
105105
t.Fatalf("expected to find block %d", i)
106106
}
@@ -124,12 +124,12 @@ func TestCacheDelete(t *testing.T) {
124124
if expected, size := int64(10), cache.Size(); expected != size {
125125
t.Fatalf("expected cache size %d, but found %d", expected, size)
126126
}
127-
if v := h.Get(base.DiskFileNum(0), 0, MakeLevel(0), CategorySSTableData); v == nil {
127+
if v := h.Get(base.DiskFileNum(0), 0, base.MakeLevel(0), CategorySSTableData); v == nil {
128128
t.Fatalf("expected to find block 0/0")
129129
} else {
130130
v.Release()
131131
}
132-
if v := h.Get(base.DiskFileNum(1), 0, MakeLevel(0), CategorySSTableData); v != nil {
132+
if v := h.Get(base.DiskFileNum(1), 0, base.MakeLevel(0), CategorySSTableData); v != nil {
133133
t.Fatalf("expected to not find block 1/0")
134134
}
135135
// Deleting a non-existing block does nothing.
@@ -196,11 +196,11 @@ func TestMultipleDBs(t *testing.T) {
196196
if expected, size := int64(5), cache.Size(); expected != size {
197197
t.Fatalf("expected cache size %d, but found %d", expected, size)
198198
}
199-
v := h1.Get(base.DiskFileNum(0), 0, MakeLevel(0), CategorySSTableData)
199+
v := h1.Get(base.DiskFileNum(0), 0, base.MakeLevel(0), CategorySSTableData)
200200
if v != nil {
201201
t.Fatalf("expected not present, but found %#v", v)
202202
}
203-
v = h2.Get(base.DiskFileNum(0), 0, MakeLevel(0), CategorySSTableData)
203+
v = h2.Get(base.DiskFileNum(0), 0, base.MakeLevel(0), CategorySSTableData)
204204
if v := v.RawBuffer(); string(v) != "bbbbb" {
205205
t.Fatalf("expected bbbbb, but found %s", v)
206206
}
@@ -308,7 +308,10 @@ func BenchmarkCacheGet(b *testing.B) {
308308
for pb.Next() {
309309
randVal := pcg.Uint64()
310310
offset := randVal % size
311-
level := Level{levelPlusOne: int8((randVal >> 32) % NumLevels)}
311+
var level base.Level
312+
if l := int((randVal >> 32) % NumLevels); l > 0 {
313+
level = base.MakeLevel(l - 1)
314+
}
312315
category := Category((randVal >> 48) % uint64(NumCategories))
313316
v := h.Get(base.DiskFileNum(0), offset, level, category)
314317
if v == nil {

internal/cache/clockpro.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ func (c *shard) init(maxSize int64) {
138138
//
139139
// If peekOnly is true, the state of the cache is not modified to reflect the
140140
// access.
141-
func (c *shard) get(k key, level Level, category Category, peekOnly bool) *Value {
141+
func (c *shard) get(k key, level base.Level, category Category, peekOnly bool) *Value {
142142
c.mu.RLock()
143143
if e, _ := c.blocks.Get(k); e != nil {
144144
if value := e.acquireValue(); value != nil {
@@ -148,14 +148,14 @@ func (c *shard) get(k key, level Level, category Category, peekOnly bool) *Value
148148
}
149149
c.mu.RUnlock()
150150
if category != CategoryHidden {
151-
c.counters[level.index()][category].hits.Add(1)
151+
c.counters[levelIndex(level)][category].hits.Add(1)
152152
}
153153
return value
154154
}
155155
}
156156
c.mu.RUnlock()
157157
if category != CategoryHidden {
158-
c.counters[level.index()][category].misses.Add(1)
158+
c.counters[levelIndex(level)][category].misses.Add(1)
159159
}
160160
return nil
161161
}
@@ -165,7 +165,7 @@ func (c *shard) get(k key, level Level, category Category, peekOnly bool) *Value
165165
// is not in the cache (nil Value), a non-nil readEntry is returned (in which
166166
// case the caller is responsible to dereference the entry, via one of
167167
// unrefAndTryRemoveFromMap(), setReadValue(), setReadError()).
168-
func (c *shard) getWithReadEntry(k key, level Level, category Category) (*Value, *readEntry) {
168+
func (c *shard) getWithReadEntry(k key, level base.Level, category Category) (*Value, *readEntry) {
169169
c.mu.RLock()
170170
if e, _ := c.blocks.Get(k); e != nil {
171171
if value := e.acquireValue(); value != nil {
@@ -174,13 +174,13 @@ func (c *shard) getWithReadEntry(k key, level Level, category Category) (*Value,
174174
e.referenced.Store(true)
175175
}
176176
c.mu.RUnlock()
177-
c.counters[level.index()][category].hits.Add(1)
177+
c.counters[levelIndex(level)][category].hits.Add(1)
178178
return value, nil
179179
}
180180
}
181181
re := c.readShard.acquireReadEntry(k)
182182
c.mu.RUnlock()
183-
c.counters[level.index()][category].misses.Add(1)
183+
c.counters[levelIndex(level)][category].misses.Add(1)
184184
return nil, re
185185
}
186186

internal/cache/metrics.go

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,33 @@ import (
99
"iter"
1010

1111
"github.com/cockroachdb/crlib/crtime"
12-
"github.com/cockroachdb/errors"
13-
"github.com/cockroachdb/pebble/internal/invariants"
12+
"github.com/cockroachdb/pebble/internal/base"
1413
)
1514

16-
// Level is the LSM level associated with an accessed block. Used to maintain
17-
// granular cache hit/miss statistics.
18-
//
19-
// The zero value indicates that there is no level (e.g. flushable ingests) or
20-
// it is unknown.
21-
type Level struct {
22-
levelPlusOne int8
23-
}
15+
const NumLevels = 1 /* unknown level */ + 7
2416

25-
func (l Level) String() string {
26-
if l.levelPlusOne <= 0 {
27-
return "n/a"
17+
// Levels is an iter.Seq[base.Level] that produces all the levels in the
18+
// HitsAndMisses array.
19+
func Levels(yield func(l base.Level) bool) {
20+
if !yield(base.Level{}) {
21+
return
2822
}
29-
return fmt.Sprintf("L%d", l.levelPlusOne-1)
30-
}
31-
32-
// index returns a value between [0, NumLevels).
33-
func (l Level) index() int8 {
34-
return l.levelPlusOne
35-
}
36-
37-
func MakeLevel(l int) Level {
38-
if invariants.Enabled && (l < 0 || l >= NumLevels-1) {
39-
panic(errors.AssertionFailedf("invalid level: %d", l))
23+
for i := range NumLevels - 1 {
24+
if !yield(base.MakeLevel(i)) {
25+
return
26+
}
4027
}
41-
return Level{levelPlusOne: int8(l + 1)}
4228
}
4329

44-
const NumLevels = 1 /* unknown level */ + 7
45-
46-
// Levels is an iter.Seq[Level].
47-
func Levels(yield func(l Level) bool) {
48-
for i := range NumLevels {
49-
if !yield(Level{levelPlusOne: int8(i)}) {
50-
return
51-
}
30+
// levelIndex returns the index of level in the HitsAndMisses array.
31+
func levelIndex(level base.Level) int {
32+
if l, ok := level.Get(); ok {
33+
return l + 1
5234
}
35+
return 0
5336
}
5437

55-
var _ iter.Seq[Level] = Levels
38+
var _ iter.Seq[base.Level] = Levels
5639

5740
// Category is used to maintain granular cache hit/miss statistics.
5841
type Category int8
@@ -117,17 +100,17 @@ type HitsAndMisses [NumLevels][NumCategories]struct {
117100
Misses int64
118101
}
119102

120-
func (hm *HitsAndMisses) Get(level Level, category Category) (hits, misses int64) {
121-
v := hm[level.index()][category]
103+
func (hm *HitsAndMisses) Get(level base.Level, category Category) (hits, misses int64) {
104+
v := hm[levelIndex(level)][category]
122105
return v.Hits, v.Misses
123106
}
124107

125-
func (hm *HitsAndMisses) Hits(level Level, category Category) int64 {
126-
return hm[level.index()][category].Hits
108+
func (hm *HitsAndMisses) Hits(level base.Level, category Category) int64 {
109+
return hm[levelIndex(level)][category].Hits
127110
}
128111

129-
func (hm *HitsAndMisses) Misses(level Level, category Category) int64 {
130-
return hm[level.index()][category].Misses
112+
func (hm *HitsAndMisses) Misses(level base.Level, category Category) int64 {
113+
return hm[levelIndex(level)][category].Misses
131114
}
132115

133116
// Aggregate returns the total hits and misses across all categories and levels.
@@ -143,8 +126,8 @@ func (hm *HitsAndMisses) Aggregate() (hits, misses int64) {
143126

144127
// AggregateLevel returns the total hits and misses for a specific level (across
145128
// all categories).
146-
func (hm *HitsAndMisses) AggregateLevel(level Level) (hits, misses int64) {
147-
for _, v := range hm[level.index()] {
129+
func (hm *HitsAndMisses) AggregateLevel(level base.Level) (hits, misses int64) {
130+
for _, v := range hm[levelIndex(level)] {
148131
hits += v.Hits
149132
misses += v.Misses
150133
}

internal/cache/read_shard_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func newTestReader(
5050
}
5151

5252
func (r *testReader) getAsync(shard *shard) *string {
53-
v, re := shard.getWithReadEntry(r.key, MakeLevel(0), CategorySSTableData)
53+
v, re := shard.getWithReadEntry(r.key, base.MakeLevel(0), CategorySSTableData)
5454
if v != nil {
5555
str := string(v.RawBuffer())
5656
v.Release()
@@ -285,7 +285,7 @@ func TestReadShardConcurrent(t *testing.T) {
285285
for _, r := range differentReaders {
286286
for j := 0; j < r.numReaders; j++ {
287287
go func(r *testSyncReaders, index int) {
288-
v, rh, _, _, _, err := r.handle.GetWithReadHandle(context.Background(), r.fileNum, r.offset, MakeLevel(0), CategorySSTableData)
288+
v, rh, _, _, _, err := r.handle.GetWithReadHandle(context.Background(), r.fileNum, r.offset, base.MakeLevel(0), CategorySSTableData)
289289
require.NoError(t, err)
290290
if v != nil {
291291
require.Equal(t, r.val, v.RawBuffer())

sstable/block/block.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ type ReadEnv struct {
276276
// Level is the LSM level associated with the operation, when the operation
277277
// applies to a (possibly virtual) sstable. It is used when interacting with
278278
// the block cache.
279-
Level cache.Level
279+
Level base.Level
280280

281281
// ReportCorruptionFn is called with ReportCorruptionArg and the error
282282
// whenever an SSTable corruption is detected. The argument is used to avoid
@@ -566,7 +566,7 @@ func (r *Reader) Readable() objstorage.Readable {
566566
//
567567
// Users should prefer using Read, which handles reading from object storage on
568568
// a cache miss.
569-
func (r *Reader) GetFromCache(bh Handle, level cache.Level) *cache.Value {
569+
func (r *Reader) GetFromCache(bh Handle, level base.Level) *cache.Value {
570570
return r.opts.CacheOpts.CacheHandle.Peek(r.opts.CacheOpts.FileNum, bh.Offset, level, cache.CategoryBackground)
571571
}
572572

sstable/colblk/index_block_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func TestIndexIterInitHandle(t *testing.T) {
132132
}
133133

134134
getBlockAndIterate := func(it *IndexIter) {
135-
cv := ch.Get(base.DiskFileNum(1), 0, cache.MakeLevel(0), cache.CategorySSTableData)
135+
cv := ch.Get(base.DiskFileNum(1), 0, base.MakeLevel(0), cache.CategorySSTableData)
136136
require.NotNil(t, cv)
137137
require.NoError(t, it.InitHandle(testkeys.Comparer, block.CacheBufferHandle(cv), blockiter.NoTransforms))
138138
defer it.Close()

0 commit comments

Comments
 (0)