github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/internal/cache/clockpro_test.go (about) 1 // Copyright 2018. All rights reserved. Use of this source code is governed by 2 // an MIT-style license that can be found in the LICENSE file. 3 4 package cache 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "os" 11 "runtime" 12 "strconv" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/cockroachdb/pebble/internal/base" 18 "github.com/stretchr/testify/require" 19 "golang.org/x/exp/rand" 20 ) 21 22 func TestCache(t *testing.T) { 23 // Test data was generated from the python code 24 f, err := os.Open("testdata/cache") 25 require.NoError(t, err) 26 27 cache := newShards(200, 1) 28 defer cache.Unref() 29 30 scanner := bufio.NewScanner(f) 31 line := 1 32 33 for scanner.Scan() { 34 fields := bytes.Fields(scanner.Bytes()) 35 36 key, err := strconv.Atoi(string(fields[0])) 37 require.NoError(t, err) 38 39 wantHit := fields[1][0] == 'h' 40 41 var hit bool 42 h := cache.Get(1, base.FileNum(uint64(key)).DiskFileNum(), 0) 43 if v := h.Get(); v == nil { 44 value := Alloc(1) 45 value.Buf()[0] = fields[0][0] 46 cache.Set(1, base.FileNum(uint64(key)).DiskFileNum(), 0, value).Release() 47 } else { 48 hit = true 49 if !bytes.Equal(v, fields[0][:1]) { 50 t.Errorf("%d: cache returned bad data: got %s , want %s\n", line, v, fields[0][:1]) 51 } 52 } 53 h.Release() 54 if hit != wantHit { 55 t.Errorf("%d: cache hit mismatch: got %v, want %v\n", line, hit, wantHit) 56 } 57 line++ 58 } 59 } 60 61 func testValue(cache *Cache, s string, repeat int) *Value { 62 b := bytes.Repeat([]byte(s), repeat) 63 v := Alloc(len(b)) 64 copy(v.Buf(), b) 65 return v 66 } 67 68 func TestCacheDelete(t *testing.T) { 69 cache := newShards(100, 1) 70 defer cache.Unref() 71 72 cache.Set(1, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 5)).Release() 73 cache.Set(1, base.FileNum(1).DiskFileNum(), 0, testValue(cache, "a", 5)).Release() 74 cache.Set(1, base.FileNum(2).DiskFileNum(), 0, testValue(cache, "a", 5)).Release() 75 if expected, size := int64(15), cache.Size(); expected != size { 76 t.Fatalf("expected cache size %d, but found %d", expected, size) 77 } 78 cache.Delete(1, base.FileNum(1).DiskFileNum(), 0) 79 if expected, size := int64(10), cache.Size(); expected != size { 80 t.Fatalf("expected cache size %d, but found %d", expected, size) 81 } 82 if h := cache.Get(1, base.FileNum(0).DiskFileNum(), 0); h.Get() == nil { 83 t.Fatalf("expected to find block 0/0") 84 } else { 85 h.Release() 86 } 87 if h := cache.Get(1, base.FileNum(1).DiskFileNum(), 0); h.Get() != nil { 88 t.Fatalf("expected to not find block 1/0") 89 } else { 90 h.Release() 91 } 92 // Deleting a non-existing block does nothing. 93 cache.Delete(1, base.FileNum(1).DiskFileNum(), 0) 94 if expected, size := int64(10), cache.Size(); expected != size { 95 t.Fatalf("expected cache size %d, but found %d", expected, size) 96 } 97 } 98 99 func TestEvictFile(t *testing.T) { 100 cache := newShards(100, 1) 101 defer cache.Unref() 102 103 cache.Set(1, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 5)).Release() 104 cache.Set(1, base.FileNum(1).DiskFileNum(), 0, testValue(cache, "a", 5)).Release() 105 cache.Set(1, base.FileNum(2).DiskFileNum(), 0, testValue(cache, "a", 5)).Release() 106 cache.Set(1, base.FileNum(2).DiskFileNum(), 1, testValue(cache, "a", 5)).Release() 107 cache.Set(1, base.FileNum(2).DiskFileNum(), 2, testValue(cache, "a", 5)).Release() 108 if expected, size := int64(25), cache.Size(); expected != size { 109 t.Fatalf("expected cache size %d, but found %d", expected, size) 110 } 111 cache.EvictFile(1, base.FileNum(0).DiskFileNum()) 112 if expected, size := int64(20), cache.Size(); expected != size { 113 t.Fatalf("expected cache size %d, but found %d", expected, size) 114 } 115 cache.EvictFile(1, base.FileNum(1).DiskFileNum()) 116 if expected, size := int64(15), cache.Size(); expected != size { 117 t.Fatalf("expected cache size %d, but found %d", expected, size) 118 } 119 cache.EvictFile(1, base.FileNum(2).DiskFileNum()) 120 if expected, size := int64(0), cache.Size(); expected != size { 121 t.Fatalf("expected cache size %d, but found %d", expected, size) 122 } 123 } 124 125 func TestEvictAll(t *testing.T) { 126 // Verify that it is okay to evict all of the data from a cache. Previously 127 // this would trigger a nil-pointer dereference. 128 cache := newShards(100, 1) 129 defer cache.Unref() 130 131 cache.Set(1, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 101)).Release() 132 cache.Set(1, base.FileNum(1).DiskFileNum(), 0, testValue(cache, "a", 101)).Release() 133 } 134 135 func TestMultipleDBs(t *testing.T) { 136 cache := newShards(100, 1) 137 defer cache.Unref() 138 139 cache.Set(1, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 5)).Release() 140 cache.Set(2, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "b", 5)).Release() 141 if expected, size := int64(10), cache.Size(); expected != size { 142 t.Fatalf("expected cache size %d, but found %d", expected, size) 143 } 144 cache.EvictFile(1, base.FileNum(0).DiskFileNum()) 145 if expected, size := int64(5), cache.Size(); expected != size { 146 t.Fatalf("expected cache size %d, but found %d", expected, size) 147 } 148 h := cache.Get(1, base.FileNum(0).DiskFileNum(), 0) 149 if v := h.Get(); v != nil { 150 t.Fatalf("expected not present, but found %s", v) 151 } 152 h = cache.Get(2, base.FileNum(0).DiskFileNum(), 0) 153 if v := h.Get(); string(v) != "bbbbb" { 154 t.Fatalf("expected bbbbb, but found %s", v) 155 } else { 156 h.Release() 157 } 158 } 159 160 func TestZeroSize(t *testing.T) { 161 cache := newShards(0, 1) 162 defer cache.Unref() 163 164 cache.Set(1, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 5)).Release() 165 } 166 167 func TestReserve(t *testing.T) { 168 cache := newShards(4, 2) 169 defer cache.Unref() 170 171 cache.Set(1, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 172 cache.Set(2, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 173 require.EqualValues(t, 2, cache.Size()) 174 r := cache.Reserve(1) 175 require.EqualValues(t, 0, cache.Size()) 176 cache.Set(1, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 177 cache.Set(2, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 178 cache.Set(3, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 179 cache.Set(4, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 180 require.EqualValues(t, 2, cache.Size()) 181 r() 182 require.EqualValues(t, 2, cache.Size()) 183 cache.Set(1, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 184 cache.Set(2, base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 185 require.EqualValues(t, 4, cache.Size()) 186 } 187 188 func TestReserveDoubleRelease(t *testing.T) { 189 cache := newShards(100, 1) 190 defer cache.Unref() 191 192 r := cache.Reserve(10) 193 r() 194 195 result := func() (result string) { 196 defer func() { 197 if v := recover(); v != nil { 198 result = fmt.Sprint(v) 199 } 200 }() 201 r() 202 return "" 203 }() 204 const expected = "pebble: cache reservation already released" 205 if expected != result { 206 t.Fatalf("expected %q, but found %q", expected, result) 207 } 208 } 209 210 func TestCacheStressSetExisting(t *testing.T) { 211 cache := newShards(1, 1) 212 defer cache.Unref() 213 214 var wg sync.WaitGroup 215 for i := 0; i < 10; i++ { 216 wg.Add(1) 217 go func(i int) { 218 defer wg.Done() 219 for j := 0; j < 10000; j++ { 220 cache.Set(1, base.FileNum(0).DiskFileNum(), uint64(i), testValue(cache, "a", 1)).Release() 221 runtime.Gosched() 222 } 223 }(i) 224 } 225 wg.Wait() 226 } 227 228 func BenchmarkCacheGet(b *testing.B) { 229 const size = 100000 230 231 cache := newShards(size, 1) 232 defer cache.Unref() 233 234 for i := 0; i < size; i++ { 235 v := testValue(cache, "a", 1) 236 cache.Set(1, base.FileNum(0).DiskFileNum(), uint64(i), v).Release() 237 } 238 239 b.ResetTimer() 240 b.RunParallel(func(pb *testing.PB) { 241 rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano()))) 242 243 for pb.Next() { 244 h := cache.Get(1, base.FileNum(0).DiskFileNum(), uint64(rng.Intn(size))) 245 if h.Get() == nil { 246 b.Fatal("failed to lookup value") 247 } 248 h.Release() 249 } 250 }) 251 } 252 253 func TestReserveColdTarget(t *testing.T) { 254 // If coldTarget isn't updated when we call shard.Reserve, 255 // then we unnecessarily remove nodes from the 256 // cache. 257 258 cache := newShards(100, 1) 259 defer cache.Unref() 260 261 for i := 0; i < 50; i++ { 262 cache.Set(uint64(i+1), base.FileNum(0).DiskFileNum(), 0, testValue(cache, "a", 1)).Release() 263 } 264 265 if cache.Size() != 50 { 266 require.Equal(t, 50, cache.Size(), "nodes were unnecessarily evicted from the cache") 267 } 268 269 // There won't be enough space left for 50 nodes in the cache after 270 // we call shard.Reserve. This should trigger a call to evict. 271 cache.Reserve(51) 272 273 // If we don't update coldTarget in Reserve then the cache gets emptied to 274 // size 0. In shard.Evict, we loop until shard.Size() < shard.targetSize(). 275 // Therefore, 100 - 51 = 49, but we evict one more node. 276 if cache.Size() != 48 { 277 t.Fatalf("expected positive cache size %d, but found %d", 48, cache.Size()) 278 } 279 }