Files
VictoriaMetrics/lib/lrucache/lrucache_test.go
Artem Fetishev 95175e00b4 lib/lrucache: sizeBytes should also include key length (#10679)
There are cases then the key sizeBytes is much greater than the value
sizeBytes. Therefore it is important to include the key sizeBytes into
the total.

Also fix some code comments.

Signed-off-by: Artem Fetishev <rtm@victoriametrics.com>
2026-03-24 12:54:31 +01:00

160 lines
4.5 KiB
Go

package lrucache
import (
"fmt"
"sync"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/cgroup"
)
func TestCache(t *testing.T) {
sizeMaxBytes := uint64(64 * 1024)
// Multiply sizeMaxBytes by the square of available CPU cores
// in order to get proper distribution of sizes between cache shards.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2204
cpus := cgroup.AvailableCPUs()
sizeMaxBytes *= uint64(cpus * cpus)
getMaxSize := func() uint64 {
return sizeMaxBytes
}
c := NewCache(getMaxSize)
defer c.MustStop()
if n := c.SizeBytes(); n != 0 {
t.Fatalf("unexpected SizeBytes(); got %d; want %d", n, 0)
}
if n := c.SizeMaxBytes(); n != sizeMaxBytes {
t.Fatalf("unexpected SizeMaxBytes(); got %d; want %d", n, sizeMaxBytes)
}
if n := c.Resets(); n != 0 {
t.Fatalf("unexpected Resets(); got %d; want %d", n, 0)
}
k := "foobar"
var e testEntry
keySize := uint64(len(k))
entrySize := e.SizeBytes()
keyEntrySize := keySize + entrySize
// Put a single entry into cache
c.PutEntry(k, &e)
if n := c.Len(); n != 1 {
t.Fatalf("unexpected number of items in the cache; got %d; want %d", n, 1)
}
if n := c.SizeBytes(); n != keyEntrySize {
t.Fatalf("unexpected SizeBytes(); got %d; want %d", n, keyEntrySize)
}
if n := c.Requests(); n != 0 {
t.Fatalf("unexpected number of requests; got %d; want %d", n, 0)
}
if n := c.Misses(); n != 0 {
t.Fatalf("unexpected number of misses; got %d; want %d", n, 0)
}
if n := c.Resets(); n != 0 {
t.Fatalf("unexpected Resets(); got %d; want %d", n, 0)
}
// Obtain this entry from the cache
if e1 := c.GetEntry(k); e1 != &e {
t.Fatalf("unexpected entry obtained; got %v; want %v", e1, &e)
}
if n := c.Requests(); n != 1 {
t.Fatalf("unexpected number of requests; got %d; want %d", n, 1)
}
if n := c.Misses(); n != 0 {
t.Fatalf("unexpected number of misses; got %d; want %d", n, 0)
}
if n := c.Resets(); n != 0 {
t.Fatalf("unexpected Resets(); got %d; want %d", n, 0)
}
// Obtain non-existing entry from the cache
if e1 := c.GetEntry("non-existing-key"); e1 != nil {
t.Fatalf("unexpected non-nil block obtained for non-existing key: %v", e1)
}
if n := c.Requests(); n != 2 {
t.Fatalf("unexpected number of requests; got %d; want %d", n, 2)
}
if n := c.Misses(); n != 1 {
t.Fatalf("unexpected number of misses; got %d; want %d", n, 1)
}
if n := c.Resets(); n != 0 {
t.Fatalf("unexpected Resets(); got %d; want %d", n, 0)
}
// Store the entry again.
c.PutEntry(k, &e)
if n := c.SizeBytes(); n != keyEntrySize {
t.Fatalf("unexpected SizeBytes(); got %d; want %d", n, keyEntrySize)
}
if e1 := c.GetEntry(k); e1 != &e {
t.Fatalf("unexpected entry obtained; got %v; want %v", e1, &e)
}
if n := c.Requests(); n != 3 {
t.Fatalf("unexpected number of requests; got %d; want %d", n, 3)
}
if n := c.Misses(); n != 1 {
t.Fatalf("unexpected number of misses; got %d; want %d", n, 1)
}
if n := c.Resets(); n != 0 {
t.Fatalf("unexpected Resets(); got %d; want %d", n, 0)
}
// Manually clean the cache. The entry shouldn't be deleted because it was recently accessed.
c.cleanByTimeout()
if n := c.SizeBytes(); n != keyEntrySize {
t.Fatalf("unexpected SizeBytes(); got %d; want %d", n, keyEntrySize)
}
// Reset cache.
c.Reset()
if n := c.SizeMaxBytes(); n != sizeMaxBytes {
t.Fatalf("unexpected SizeMaxBytes(); got %d; want %d", n, sizeMaxBytes)
}
if n := c.SizeBytes(); n != 0 {
t.Fatalf("unexpected SizeBytes(); got %d; want %d", n, 0)
}
if n := c.Requests(); n != 3 {
t.Fatalf("unexpected number of requests; got %d; want %d", n, 0)
}
if n := c.Misses(); n != 1 {
t.Fatalf("unexpected number of misses; got %d; want %d", n, 0)
}
if n := c.Resets(); n != 1 {
t.Fatalf("unexpected Resets(); got %d; want %d", n, 1)
}
}
func TestCacheConcurrentAccess(_ *testing.T) {
const sizeMaxBytes = uint64(16 * 1024 * 1024)
getMaxSize := func() uint64 {
return sizeMaxBytes
}
c := NewCache(getMaxSize)
defer c.MustStop()
workers := 5
var wg sync.WaitGroup
for worker := range workers {
wg.Go(func() {
testCacheSetGet(c, worker)
})
}
wg.Wait()
}
func testCacheSetGet(c *Cache, worker int) {
for i := range 1000 {
e := testEntry{}
k := fmt.Sprintf("key_%d_%d", worker, i)
c.PutEntry(k, &e)
if e1 := c.GetEntry(k); e1 != &e {
panic(fmt.Errorf("unexpected entry obtained; got %v; want %v", e1, &e))
}
if e1 := c.GetEntry("non-existing-key"); e1 != nil {
panic(fmt.Errorf("unexpected non-nil entry obtained: %v", e1))
}
}
}
type testEntry struct{}
func (tb *testEntry) SizeBytes() uint64 {
return 42
}