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  }