255 lines
7.8 KiB
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)
|
|
}
|