github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/raftentry/cache_test.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package raftentry 12 13 import ( 14 "math" 15 "math/rand" 16 "reflect" 17 "sync" 18 "testing" 19 "time" 20 21 "github.com/cockroachdb/cockroach/pkg/roachpb" 22 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 23 "go.etcd.io/etcd/raft/raftpb" 24 ) 25 26 const noLimit = math.MaxUint64 27 28 func newEntry(index, size uint64) raftpb.Entry { 29 data := make([]byte, size) 30 if _, err := rand.Read(data); err != nil { 31 panic(err) 32 } 33 return raftpb.Entry{ 34 Index: index, 35 Data: data, 36 } 37 } 38 39 func newEntries(lo, hi, size uint64) []raftpb.Entry { 40 ents := []raftpb.Entry{} 41 for i := lo; i < hi; i++ { 42 ents = append(ents, newEntry(i, size)) 43 } 44 return ents 45 } 46 47 func addEntries(c *Cache, rangeID roachpb.RangeID, lo, hi uint64) []raftpb.Entry { 48 ents := newEntries(lo, hi, 1) 49 c.Add(rangeID, ents, false) 50 return ents 51 } 52 53 func verifyGet( 54 t *testing.T, 55 c *Cache, 56 rangeID roachpb.RangeID, 57 lo, hi uint64, 58 expEnts []raftpb.Entry, 59 expNextIndex uint64, 60 allowEviction bool, 61 ) { 62 t.Helper() 63 ents, _, nextIndex, _ := c.Scan(nil, rangeID, lo, hi, noLimit) 64 if allowEviction && len(ents) == 0 { 65 return 66 } 67 if !(len(expEnts) == 0 && len(ents) == 0) && !reflect.DeepEqual(expEnts, ents) { 68 t.Fatalf("expected entries %+v; got %+v", expEnts, ents) 69 } 70 if nextIndex != expNextIndex { 71 t.Fatalf("expected next index %d; got %d", expNextIndex, nextIndex) 72 } 73 for _, e := range ents { 74 found, ok := c.Get(rangeID, e.Index) 75 if !ok { 76 if allowEviction { 77 break 78 } 79 t.Fatalf("expected to be able to retrieve entry") 80 } 81 if !reflect.DeepEqual(found, e) { 82 t.Fatalf("expected entry %v, but got %v", e, found) 83 } 84 } 85 } 86 87 func TestEntryCache(t *testing.T) { 88 defer leaktest.AfterTest(t)() 89 c := NewCache(100 + 2*uint64(partitionSize)) 90 rangeID := roachpb.RangeID(2) 91 otherRangeID := rangeID + 1 92 // Note 9 bytes per entry with data size of 1 93 verify := func(rangeID roachpb.RangeID, lo, hi uint64, ents []raftpb.Entry, expNextIndex uint64) { 94 t.Helper() 95 verifyGet(t, c, rangeID, lo, hi, ents, expNextIndex, false) 96 } 97 // Add entries for range 1, indexes [1-9). 98 ents := addEntries(c, rangeID, 1, 9) 99 verifyMetrics(t, c, 8, 72+int64(partitionSize)) 100 // Fetch all data with an exact match. 101 verify(rangeID, 1, 9, ents, 9) 102 // Fetch point entry. 103 verify(rangeID, 1, 2, ents[0:1], 2) 104 // Fetch overlapping first half. 105 verify(rangeID, 0, 3, []raftpb.Entry{}, 0) 106 // Fetch overlapping second half. 107 verify(rangeID, 5, 9, ents[4:], 9) 108 // Fetch data from earlier range. 109 verify(roachpb.RangeID(1), 1, 11, []raftpb.Entry{}, 1) 110 // Fetch data from later range. 111 verify(roachpb.RangeID(3), 1, 11, []raftpb.Entry{}, 1) 112 // Add another range which we can evict. 113 otherEnts := addEntries(c, otherRangeID, 1, 3) 114 verifyMetrics(t, c, 10, 90+2*int64(partitionSize)) 115 verify(otherRangeID, 1, 3, otherEnts[:], 3) 116 // Add overlapping entries which will lead to eviction. 117 newEnts := addEntries(c, rangeID, 8, 11) 118 ents = append(ents[:7], newEnts...) 119 verifyMetrics(t, c, 10, 90+int64(partitionSize)) 120 // Ensure other range got evicted but first range did not. 121 verify(rangeID, 1, 11, ents[:], 11) 122 verify(otherRangeID, 1, 11, []raftpb.Entry{}, 1) 123 // Clear and show that it makes space. 124 c.Clear(rangeID, 10) 125 verify(rangeID, 10, 11, ents[9:], 11) 126 verifyMetrics(t, c, 1, 9+int64(partitionSize)) 127 // Clear again and show that it still works. 128 c.Clear(rangeID, 10) 129 verify(rangeID, 10, 11, ents[9:], 11) 130 verifyMetrics(t, c, 1, 9+int64(partitionSize)) 131 // Add entries from before and show that they get cached. 132 c.Add(rangeID, ents[5:], false) 133 verify(rangeID, 6, 11, ents[5:], 11) 134 verifyMetrics(t, c, 5, 45+int64(partitionSize)) 135 // Add a few repeat entries and show that nothing changes. 136 c.Add(rangeID, ents[6:8], false) 137 verify(rangeID, 6, 11, ents[5:], 11) 138 verifyMetrics(t, c, 5, 45+int64(partitionSize)) 139 // Add a few repeat entries while truncating. 140 // Non-overlapping tail should be evicted. 141 c.Add(rangeID, ents[6:8], true) 142 verify(rangeID, 6, 11, ents[5:8], 9) 143 verifyMetrics(t, c, 3, 27+int64(partitionSize)) 144 } 145 146 func verifyMetrics(t *testing.T, c *Cache, expectedEntries, expectedBytes int64) { 147 t.Helper() 148 // NB: Cache gauges are updated asynchronously. In the face of concurrent 149 // updates gauges may not reflect the current cache state. Force 150 // synchrononization of the metrics before using them to validate cache state. 151 c.syncGauges() 152 if got := c.Metrics().Size.Value(); got != expectedEntries { 153 t.Errorf("expected cache to have %d entries, got %d", expectedEntries, got) 154 } 155 if got := c.Metrics().Bytes.Value(); got != expectedBytes { 156 t.Errorf("expected cache to have %d bytes, got %d", expectedBytes, got) 157 } 158 } 159 160 func (c *Cache) syncGauges() { 161 c.updateGauges(c.addBytes(0), c.addEntries(0)) 162 } 163 164 func TestIgnoredAdd(t *testing.T) { 165 defer leaktest.AfterTest(t)() 166 rangeID := roachpb.RangeID(1) 167 c := NewCache(100 + uint64(partitionSize)) 168 // Show that adding entries which are larger than maxBytes is ignored. 169 _ = addEntries(c, rangeID, 1, 41) 170 verifyGet(t, c, rangeID, 1, 41, nil, 1, false) 171 verifyMetrics(t, c, 0, 0) 172 // Add some entries so we can show that a non-overlapping add is ignored. 173 ents := addEntries(c, rangeID, 4, 7) 174 verifyGet(t, c, rangeID, 4, 7, ents, 7, false) 175 verifyMetrics(t, c, 3, 27+int64(partitionSize)) 176 addEntries(c, rangeID, 1, 3) 177 verifyMetrics(t, c, 3, 27+int64(partitionSize)) 178 } 179 180 func TestAddAndTruncate(t *testing.T) { 181 defer leaktest.AfterTest(t)() 182 rangeID := roachpb.RangeID(1) 183 c := NewCache(200 + uint64(partitionSize)) 184 ents := addEntries(c, rangeID, 1, 10) 185 verifyGet(t, c, rangeID, 1, 10, ents, 10, false) 186 verifyMetrics(t, c, 9, 81+int64(partitionSize)) 187 // Show that, if specified, adding an overlapping set of entries 188 // truncates any at a larger index that is not overwritten. 189 c.Add(rangeID, ents[2:6], true /* truncate */) 190 verifyGet(t, c, rangeID, 1, 10, ents[:6], 7, false) 191 verifyMetrics(t, c, 6, 54+int64(partitionSize)) 192 // Show that even if the addition is ignored due to size, entries 193 // with an equal or larger index are truncated. 194 largeEnts := newEntries(5, 6, 300) 195 c.Add(rangeID, largeEnts, true /* truncate */) 196 verifyGet(t, c, rangeID, 1, 10, ents[:4], 5, false) 197 verifyMetrics(t, c, 4, 36+int64(partitionSize)) 198 } 199 200 func TestDrop(t *testing.T) { 201 defer leaktest.AfterTest(t)() 202 const ( 203 r1 roachpb.RangeID = 1 204 r2 roachpb.RangeID = 2 205 206 sizeOf9Entries = 81 207 partitionSize = int64(sizeOf9Entries + partitionSize) 208 ) 209 c := NewCache(1 << 10) 210 ents1 := addEntries(c, r1, 1, 10) 211 verifyGet(t, c, r1, 1, 10, ents1, 10, false) 212 verifyMetrics(t, c, 9, partitionSize) 213 ents2 := addEntries(c, r2, 1, 10) 214 verifyGet(t, c, r2, 1, 10, ents2, 10, false) 215 verifyMetrics(t, c, 18, 2*partitionSize) 216 c.Drop(r1) 217 verifyMetrics(t, c, 9, partitionSize) 218 c.Drop(r2) 219 verifyMetrics(t, c, 0, 0) 220 } 221 222 func TestCacheLaterEntries(t *testing.T) { 223 c := NewCache(1000) 224 rangeID := roachpb.RangeID(1) 225 ents := addEntries(c, rangeID, 1, 10) 226 verifyGet(t, c, rangeID, 1, 10, ents, 10, false) 227 // The previous entries are evicted because they would not have been 228 // contiguous with the new entries. 229 ents = addEntries(c, rangeID, 11, 21) 230 if got, _, _, _ := c.Scan(nil, rangeID, 1, 10, noLimit); len(got) != 0 { 231 t.Fatalf("Expected not to get entries from range which preceded new values") 232 } 233 verifyGet(t, c, rangeID, 11, 21, ents, 21, false) 234 } 235 236 func TestExceededMaxBytes(t *testing.T) { 237 defer leaktest.AfterTest(t)() 238 rangeID := roachpb.RangeID(1) 239 c := NewCache(100) 240 addEntries(c, rangeID, 1, 10) 241 ents, _, next, exceeded := c.Scan(nil, rangeID, 1, 10, 18) 242 if len(ents) != 2 || next != 3 || !exceeded { 243 t.Errorf("expected 2 entries with next=3 and to have exceededMaxBytes, got %d, %d, %v", 244 len(ents), next, exceeded) 245 } 246 } 247 248 func TestEntryCacheClearTo(t *testing.T) { 249 defer leaktest.AfterTest(t)() 250 rangeID := roachpb.RangeID(1) 251 c := NewCache(100) 252 c.Add(rangeID, []raftpb.Entry{newEntry(20, 1), newEntry(21, 1)}, true) 253 c.Clear(rangeID, 21) 254 c.Clear(rangeID, 18) 255 if ents, _, _, _ := c.Scan(nil, rangeID, 2, 21, noLimit); len(ents) != 0 { 256 t.Errorf("expected no entries after clearTo") 257 } 258 if ents, _, _, _ := c.Scan(nil, rangeID, 21, 22, noLimit); len(ents) != 1 { 259 t.Errorf("expected entry 22 to remain in the cache clearTo") 260 } 261 c.Clear(rangeID, 23) // past the end 262 if ents, _, _, _ := c.Scan(nil, rangeID, 21, 22, noLimit); len(ents) != 0 { 263 t.Errorf("expected e//ntry 22 to be cleared") 264 } 265 if _, ok := c.Get(rangeID, 21); ok { 266 t.Errorf("didn't expect to get any entry") 267 } 268 verifyMetrics(t, c, 0, int64(partitionSize)) 269 c.Clear(rangeID, 22) 270 } 271 272 func TestMaxBytesLimit(t *testing.T) { 273 c := NewCache(1 << 32) 274 if c.maxBytes != (1<<31 - 1) { 275 t.Fatalf("maxBytes cannot be larger than %d", 1<<31) 276 } 277 } 278 279 func TestConcurrentEvictions(t *testing.T) { 280 // This tests for safety in the face of concurrent updates. 281 // The main goroutine randomly chooses a free partition for a read or write. 282 // Concurrent operations will lead to evictions. Reads verify that either the 283 // data is read and correct or is not read. At the end, all the ranges are 284 // cleared and we ensure that the entry count is zero. 285 286 // NB: N is chosen based on the race detector's limit of 8128 goroutines. 287 const N = 8000 288 const numRanges = 200 289 const maxEntriesPerWrite = 111 290 rangeData := make(map[roachpb.RangeID][]raftpb.Entry) 291 rangeInUse := make(map[roachpb.RangeID]bool) 292 c := NewCache(1000) 293 rangeDoneChan := make(chan roachpb.RangeID) 294 pickRange := func() (r roachpb.RangeID) { 295 for { 296 r = roachpb.RangeID(rand.Intn(numRanges)) 297 if !rangeInUse[r] { 298 break 299 } 300 } 301 rangeInUse[r] = true 302 return r 303 } 304 var wg sync.WaitGroup 305 doRead := func(r roachpb.RangeID) { 306 ents := rangeData[r] 307 offset := rand.Intn(len(ents)) 308 length := rand.Intn(len(ents) - offset) 309 lo := ents[offset].Index 310 hi := lo + uint64(length) 311 wg.Add(1) 312 go func() { 313 time.Sleep(time.Duration(rand.Intn(int(time.Microsecond)))) 314 verifyGet(t, c, r, lo, hi, ents[offset:offset+length], hi, true) 315 rangeDoneChan <- r 316 wg.Done() 317 }() 318 } 319 doWrite := func(r roachpb.RangeID) { 320 ents := rangeData[r] 321 offset := rand.Intn(len(ents)+1) - 1 322 length := rand.Intn(maxEntriesPerWrite) 323 var toAdd []raftpb.Entry 324 if offset >= 0 && offset < len(ents) { 325 lo := ents[offset].Index 326 hi := lo + uint64(length) 327 toAdd = newEntries(lo, hi, 1) 328 ents = append(ents[:offset], toAdd...) 329 } else { 330 lo := uint64(offset + 2) 331 hi := lo + uint64(length) 332 toAdd = newEntries(lo, hi, 1) 333 ents = toAdd 334 } 335 rangeData[r] = ents 336 wg.Add(1) 337 go func() { 338 time.Sleep(time.Duration(rand.Intn(int(time.Microsecond)))) 339 c.Add(r, toAdd, true) 340 rangeDoneChan <- r 341 wg.Done() 342 }() 343 } 344 for i := 0; i < N; i++ { 345 for len(rangeInUse) > numRanges/2 { 346 delete(rangeInUse, <-rangeDoneChan) 347 } 348 r := pickRange() 349 if read := rand.Intn(2) == 1; read && len(rangeData[r]) > 0 { 350 doRead(r) 351 } else { 352 doWrite(r) 353 } 354 } 355 go func() { wg.Wait(); close(rangeDoneChan) }() 356 for r := range rangeDoneChan { 357 delete(rangeInUse, r) 358 } 359 // Clear the data and ensure that the cache stats are valid. 360 for r, data := range rangeData { 361 if len(data) == 0 { 362 continue 363 } 364 c.Clear(r, data[len(data)-1].Index+1) 365 } 366 verifyMetrics(t, c, 0, int64(len(c.parts))*int64(partitionSize)) 367 } 368 369 func TestHeadWrappingForward(t *testing.T) { 370 defer leaktest.AfterTest(t)() 371 rangeID := roachpb.RangeID(1) 372 c := NewCache(200 + uint64(partitionSize)) 373 ents := addEntries(c, rangeID, 1, 8) 374 // Clear some space at the front of the ringBuf. 375 c.Clear(rangeID, 4) 376 verifyMetrics(t, c, 4, 36+int64(partitionSize)) 377 // Fill in space at the front of the ringBuf. 378 ents = append(ents[3:4], addEntries(c, rangeID, 5, 20)...) 379 verifyMetrics(t, c, 16, 144+int64(partitionSize)) 380 verifyGet(t, c, rangeID, 4, 20, ents, 20, false) 381 // Realloc copying from the wrapped around ringBuf. 382 ents = append(ents, addEntries(c, rangeID, 20, 22)...) 383 verifyGet(t, c, rangeID, 18, 22, ents[14:], 22, false) 384 } 385 386 func TestHeadWrappingBackwards(t *testing.T) { 387 defer leaktest.AfterTest(t)() 388 rangeID := roachpb.RangeID(1) 389 c := NewCache(100 + uint64(partitionSize)) 390 ents := addEntries(c, rangeID, 3, 5) 391 c.Clear(rangeID, 4) 392 ents = append(addEntries(c, rangeID, 1, 4), ents[1:]...) 393 verifyGet(t, c, rangeID, 1, 5, ents, 5, false) 394 } 395 396 func TestPanicOnNonContiguousRange(t *testing.T) { 397 defer leaktest.AfterTest(t)() 398 c := NewCache(100) 399 defer func() { 400 if r := recover(); r == nil { 401 t.Errorf("Expected panic with non-contiguous range") 402 } 403 }() 404 c.Add(1, []raftpb.Entry{newEntry(1, 1), newEntry(3, 1)}, true) 405 } 406 407 func TestEntryCacheEviction(t *testing.T) { 408 defer leaktest.AfterTest(t)() 409 rangeID, rangeID2 := roachpb.RangeID(1), roachpb.RangeID(2) 410 c := NewCache(140 + uint64(partitionSize)) 411 c.Add(rangeID, []raftpb.Entry{newEntry(1, 40), newEntry(2, 40)}, true) 412 ents, _, hi, _ := c.Scan(nil, rangeID, 1, 3, noLimit) 413 if len(ents) != 2 || hi != 3 { 414 t.Errorf("expected both entries; got %+v, %d", ents, hi) 415 } 416 if c.entries != 2 { 417 t.Errorf("expected size=2; got %d", c.entries) 418 } 419 // Add another entry to the same range. This will exceed the size limit and 420 // lead to eviction. 421 c.Add(rangeID, []raftpb.Entry{newEntry(3, 40)}, true) 422 ents, _, hi, _ = c.Scan(nil, rangeID, 1, 4, noLimit) 423 if len(ents) != 0 || hi != 1 { 424 t.Errorf("expected no entries; got %+v, %d", ents, hi) 425 } 426 if _, ok := c.Get(rangeID, 1); ok { 427 t.Errorf("didn't expect to get evicted entry") 428 } 429 if c.entries != 1 { 430 t.Errorf("expected size=1; got %d", c.entries) 431 } 432 ents, _, hi, _ = c.Scan(nil, rangeID, 3, 4, noLimit) 433 if len(ents) != 1 || hi != 4 { 434 t.Errorf("expected the new entry; got %+v, %d", ents, hi) 435 } 436 c.Add(rangeID, []raftpb.Entry{newEntry(3, 1)}, true) 437 verifyMetrics(t, c, 1, c.Metrics().Bytes.Value()) 438 c.Add(rangeID2, []raftpb.Entry{newEntry(20, 1), newEntry(21, 1)}, true) 439 ents, _, hi, _ = c.Scan(nil, rangeID2, 20, 22, noLimit) 440 if len(ents) != 2 || hi != 22 { 441 t.Errorf("expected both entries; got %+v, %d", ents, hi) 442 } 443 verifyMetrics(t, c, 3, c.Metrics().Bytes.Value()) 444 // Evict from rangeID by adding more to rangeID 2. 445 c.Add(rangeID2, []raftpb.Entry{newEntry(20, 35), newEntry(21, 35)}, true) 446 if _, ok := c.Get(rangeID, 3); ok { 447 t.Errorf("didn't expect to get evicted entry") 448 } 449 } 450 451 // TestConcurrentUpdates ensures that concurrent updates to the same do not 452 // race with each other. 453 func TestConcurrentUpdates(t *testing.T) { 454 defer leaktest.AfterTest(t)() 455 c := NewCache(10000) 456 const r1 roachpb.RangeID = 1 457 ents := []raftpb.Entry{newEntry(20, 35), newEntry(21, 35)} 458 // Test using both Clear and Drop to remove the added entries. 459 for _, clearMethod := range []struct { 460 name string 461 clear func() 462 }{ 463 {"drop", func() { c.Drop(r1) }}, 464 {"clear", func() { c.Clear(r1, ents[len(ents)-1].Index+1) }}, 465 } { 466 t.Run(clearMethod.name, func(t *testing.T) { 467 // NB: N is chosen based on the race detector's limit of 8128 goroutines. 468 const N = 8000 469 var wg sync.WaitGroup 470 wg.Add(N) 471 for i := 0; i < N; i++ { 472 go func(i int) { 473 if i%2 == 1 { 474 c.Add(r1, ents, true) 475 } else { 476 clearMethod.clear() 477 } 478 wg.Done() 479 }(i) 480 } 481 wg.Wait() 482 clearMethod.clear() 483 // Clear does not evict the partition struct itself so we expect the cache 484 // to have a partition's initial byte size when using Clear and nothing 485 // when using Drop. 486 switch clearMethod.name { 487 case "drop": 488 verifyMetrics(t, c, 0, 0) 489 case "clear": 490 verifyMetrics(t, c, 0, int64(initialSize.bytes())) 491 } 492 }) 493 } 494 } 495 496 func TestPartitionList(t *testing.T) { 497 var l partitionList 498 first := l.pushFront(1) 499 l.remove(first) 500 if l.back() != nil { 501 t.Fatalf("Expected back to be nil after removing the only element") 502 } 503 defer func() { 504 if r := recover(); r == nil { 505 t.Fatalf("Expected panic when removing list root") 506 } 507 }() 508 l.remove(&l.root) 509 } 510 511 // TestConcurrentClearAddGet exercises the case where a partition is 512 // concurrently written to as well as read and evicted from. Partitions are 513 // created and added to the cache lazily upon calls to Add. Because of the two 514 // level locking, a partition may be created and added to the cache before the 515 // Add call proceeds to lock the partition and the entries to the buffer. 516 // During this period a concurrent read operation may discover the uninitialized 517 // empty partition. This test attempts to exercise these scenarios and ensure 518 // that they are safe. 519 func TestConcurrentAddGetAndEviction(t *testing.T) { 520 defer leaktest.AfterTest(t)() 521 const N = 1000 522 var wg sync.WaitGroup 523 doAction := func(action func()) { 524 wg.Add(1) 525 go func() { 526 defer wg.Done() 527 for i := 0; i < N; i++ { 528 action() 529 } 530 }() 531 } 532 // A cache size of 1000 is chosen relative to the below entry size of 500 533 // so that each add operation will lead to the eviction of the other 534 // partition. 535 c := NewCache(1000) 536 ents := []raftpb.Entry{newEntry(1, 500)} 537 doAddAndGetToRange := func(rangeID roachpb.RangeID) { 538 doAction(func() { c.Add(rangeID, ents, true) }) 539 doAction(func() { c.Get(rangeID, ents[0].Index) }) 540 } 541 doAddAndGetToRange(1) 542 doAddAndGetToRange(2) 543 wg.Wait() 544 } 545 546 func BenchmarkEntryCache(b *testing.B) { 547 rangeID := roachpb.RangeID(1) 548 ents := make([]raftpb.Entry, 1000) 549 for i := range ents { 550 ents[i] = newEntry(uint64(i+1), 8) 551 } 552 b.ResetTimer() 553 for i := 0; i < b.N; i++ { 554 b.StopTimer() 555 c := NewCache(uint64(15 * len(ents) * len(ents[0].Data))) 556 for i := roachpb.RangeID(0); i < 10; i++ { 557 if i != rangeID { 558 c.Add(i, ents, true) 559 } 560 } 561 b.StartTimer() 562 c.Add(rangeID, ents, true) 563 _, _, _, _ = c.Scan(nil, rangeID, 0, uint64(len(ents)-10), noLimit) 564 c.Clear(rangeID, uint64(len(ents)-10)) 565 } 566 } 567 568 func BenchmarkEntryCacheClearTo(b *testing.B) { 569 rangeID := roachpb.RangeID(1) 570 ents := make([]raftpb.Entry, 1000) 571 for i := range ents { 572 ents[i] = newEntry(uint64(i+1), 8) 573 } 574 b.ResetTimer() 575 for i := 0; i < b.N; i++ { 576 b.StopTimer() 577 c := NewCache(uint64(10 * len(ents) * len(ents[0].Data))) 578 c.Add(rangeID, ents, true) 579 b.StartTimer() 580 c.Clear(rangeID, uint64(len(ents)-10)) 581 } 582 }