influxdb/tsdb/tsi1/cache_test.go

255 lines
7.8 KiB
Go

package tsi1
import (
"math/rand"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/influxdata/influxdb/tsdb"
)
func newSeriesIDSet(ids ...int) *tsdb.SeriesIDSet {
out := make([]tsdb.SeriesID, 0, len(ids))
for _, v := range ids {
out = append(out, tsdb.NewSeriesID(uint64(v)))
}
return tsdb.NewSeriesIDSet(out...)
}
func TestTagValueSeriesIDCache(t *testing.T) {
m0k0v0 := newSeriesIDSet(1, 2, 3, 4, 5)
m0k0v1 := newSeriesIDSet(10, 20, 30, 40, 50)
m0k1v2 := newSeriesIDSet()
m1k3v0 := newSeriesIDSet(900, 0, 929)
cache := TestCache{NewTagValueSeriesIDCache(10)}
cache.Has(t, "m0", "k0", "v0", nil)
// Putting something in the cache makes it retrievable.
cache.PutByString("m0", "k0", "v0", m0k0v0)
cache.Has(t, "m0", "k0", "v0", m0k0v0)
// Putting something else under the same key will not replace the original item.
cache.PutByString("m0", "k0", "v0", newSeriesIDSet(100, 200))
cache.Has(t, "m0", "k0", "v0", m0k0v0)
// Add another item to the cache.
cache.PutByString("m0", "k0", "v1", m0k0v1)
cache.Has(t, "m0", "k0", "v0", m0k0v0)
cache.Has(t, "m0", "k0", "v1", m0k0v1)
// Add some more items
cache.PutByString("m0", "k1", "v2", m0k1v2)
cache.PutByString("m1", "k3", "v0", m1k3v0)
cache.Has(t, "m0", "k0", "v0", m0k0v0)
cache.Has(t, "m0", "k0", "v1", m0k0v1)
cache.Has(t, "m0", "k1", "v2", m0k1v2)
cache.Has(t, "m1", "k3", "v0", m1k3v0)
}
func TestTagValueSeriesIDCache_eviction(t *testing.T) {
m0k0v0 := newSeriesIDSet(1, 2, 3, 4, 5)
m0k0v1 := newSeriesIDSet(10, 20, 30, 40, 50)
m0k1v2 := newSeriesIDSet()
m1k3v0 := newSeriesIDSet(900, 0, 929)
cache := TestCache{NewTagValueSeriesIDCache(4)}
cache.PutByString("m0", "k0", "v0", m0k0v0)
cache.PutByString("m0", "k0", "v1", m0k0v1)
cache.PutByString("m0", "k1", "v2", m0k1v2)
cache.PutByString("m1", "k3", "v0", m1k3v0)
cache.Has(t, "m0", "k0", "v0", m0k0v0)
cache.Has(t, "m0", "k0", "v1", m0k0v1)
cache.Has(t, "m0", "k1", "v2", m0k1v2)
cache.Has(t, "m1", "k3", "v0", m1k3v0)
// Putting another item in the cache will evict m0k0v0
m2k0v0 := newSeriesIDSet(8, 8, 8)
cache.PutByString("m2", "k0", "v0", m2k0v0)
if got, exp := cache.evictor.Len(), 4; got != exp {
t.Fatalf("cache size was %d, expected %d", got, exp)
}
cache.HasNot(t, "m0", "k0", "v0")
cache.Has(t, "m0", "k0", "v1", m0k0v1)
cache.Has(t, "m0", "k1", "v2", m0k1v2)
cache.Has(t, "m1", "k3", "v0", m1k3v0)
cache.Has(t, "m2", "k0", "v0", m2k0v0)
// Putting another item in the cache will evict m0k0v1. That will mean
// there will be no values left under the tuple {m0, k0}
if _, ok := cache.cache[string("m0")][string("k0")]; !ok {
t.Fatalf("Map missing for key %q", "k0")
}
m2k0v1 := newSeriesIDSet(8, 8, 8)
cache.PutByString("m2", "k0", "v1", m2k0v1)
if got, exp := cache.evictor.Len(), 4; got != exp {
t.Fatalf("cache size was %d, expected %d", got, exp)
}
cache.HasNot(t, "m0", "k0", "v0")
cache.HasNot(t, "m0", "k0", "v1")
cache.Has(t, "m0", "k1", "v2", m0k1v2)
cache.Has(t, "m1", "k3", "v0", m1k3v0)
cache.Has(t, "m2", "k0", "v0", m2k0v0)
cache.Has(t, "m2", "k0", "v1", m2k0v1)
// Further, the map for all tag values for the tuple {m0, k0} should be removed.
if _, ok := cache.cache[string("m0")][string("k0")]; ok {
t.Fatalf("Map present for key %q, should be removed", "k0")
}
// Putting another item in the cache will evict m0k1v2. That will mean
// there will be no values left under the tuple {m0}
if _, ok := cache.cache[string("m0")]; !ok {
t.Fatalf("Map missing for key %q", "k0")
}
m2k0v2 := newSeriesIDSet(8, 9, 9)
cache.PutByString("m2", "k0", "v2", m2k0v2)
cache.HasNot(t, "m0", "k0", "v0")
cache.HasNot(t, "m0", "k0", "v1")
cache.HasNot(t, "m0", "k1", "v2")
cache.Has(t, "m1", "k3", "v0", m1k3v0)
cache.Has(t, "m2", "k0", "v0", m2k0v0)
cache.Has(t, "m2", "k0", "v1", m2k0v1)
cache.Has(t, "m2", "k0", "v2", m2k0v2)
// The map for all tag values for the tuple {m0} should be removed.
if _, ok := cache.cache[string("m0")]; ok {
t.Fatalf("Map present for key %q, should be removed", "k0")
}
// Putting another item in the cache will evict m2k0v0 if we first get m1k3v0
// because m2k0v0 will have been used less recently...
m3k0v0 := newSeriesIDSet(1000)
cache.Has(t, "m1", "k3", "v0", m1k3v0) // This makes it the most recently used rather than the least.
cache.PutByString("m3", "k0", "v0", m3k0v0)
cache.HasNot(t, "m0", "k0", "v0")
cache.HasNot(t, "m0", "k0", "v1")
cache.HasNot(t, "m0", "k1", "v2")
cache.HasNot(t, "m2", "k0", "v0") // This got pushed to the back.
cache.Has(t, "m1", "k3", "v0", m1k3v0) // This got saved because we looked at it before we added to the cache
cache.Has(t, "m2", "k0", "v1", m2k0v1)
cache.Has(t, "m2", "k0", "v2", m2k0v2)
cache.Has(t, "m3", "k0", "v0", m3k0v0)
}
func TestTagValueSeriesIDCache_addToSet(t *testing.T) {
cache := TestCache{NewTagValueSeriesIDCache(4)}
cache.PutByString("m0", "k0", "v0", nil) // Puts a nil set in the cache.
s2 := newSeriesIDSet(100)
cache.PutByString("m0", "k0", "v1", s2)
cache.Has(t, "m0", "k0", "v0", nil)
cache.Has(t, "m0", "k0", "v1", s2)
cache.addToSet([]byte("m0"), []byte("k0"), []byte("v0"), tsdb.NewSeriesID(20)) // No non-nil set exists so one will be created
cache.addToSet([]byte("m0"), []byte("k0"), []byte("v1"), tsdb.NewSeriesID(101)) // No non-nil set exists so one will be created
cache.Has(t, "m0", "k0", "v1", newSeriesIDSet(100, 101))
ss := cache.GetByString("m0", "k0", "v0")
if !newSeriesIDSet(20).Equals(ss) {
t.Fatalf("series id set was %v", ss)
}
}
func TestTagValueSeriesIDCache_ConcurrentGetPutDelete(t *testing.T) {
// Exercise concurrent operations against a series ID cache.
// This will catch any likely data races, when run with the race detector.
if testing.Short() {
t.Skip("Skipping long test")
}
t.Parallel()
const letters = "abcde"
rnd := func(rng *rand.Rand) []byte {
return []byte{letters[rng.Intn(len(letters)-1)]}
}
cache := TestCache{NewTagValueSeriesIDCache(100)}
done := make(chan struct{})
var wg sync.WaitGroup
var seriesIDCounter int32 // Atomic counter to ensure unique series IDs.
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// Local rng to avoid lock contention.
rng := rand.New(rand.NewSource(rand.Int63()))
for {
select {
case <-done:
return
default:
}
nextID := int(atomic.AddInt32(&seriesIDCounter, 1))
cache.Put(rnd(rng), rnd(rng), rnd(rng), newSeriesIDSet(nextID))
}
}()
}
var gets, deletes int32
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// Local rng to avoid lock contention.
rng := rand.New(rand.NewSource(rand.Int63()))
for {
select {
case <-done:
return
default:
}
name, key, value := rnd(rng), rnd(rng), rnd(rng)
if set := cache.Get(name, key, value); set != nil {
ids := set.Slice()
for _, id := range ids {
cache.Delete(name, key, value, tsdb.NewSeriesID(id))
atomic.AddInt32(&deletes, 1)
}
}
atomic.AddInt32(&gets, 1)
}
}()
}
time.Sleep(10 * time.Second)
close(done)
wg.Wait()
t.Logf("Concurrently executed against series ID cache: gets=%d puts=%d deletes=%d", gets, seriesIDCounter, deletes)
}
type TestCache struct {
*TagValueSeriesIDCache
}
func (c TestCache) Has(t *testing.T, name, key, value string, ss *tsdb.SeriesIDSet) {
if got, exp := c.Get([]byte(name), []byte(key), []byte(value)), ss; !got.Equals(exp) {
t.Helper()
t.Fatalf("got set %v, expected %v", got, exp)
}
}
func (c TestCache) HasNot(t *testing.T, name, key, value string) {
if got := c.Get([]byte(name), []byte(key), []byte(value)); got != nil {
t.Helper()
t.Fatalf("got non-nil set %v for {%q, %q, %q}", got, name, key, value)
}
}
func (c TestCache) GetByString(name, key, value string) *tsdb.SeriesIDSet {
return c.Get([]byte(name), []byte(key), []byte(value))
}
func (c TestCache) PutByString(name, key, value string, ss *tsdb.SeriesIDSet) {
c.Put([]byte(name), []byte(key), []byte(value), ss)
}