476 lines
13 KiB
Go

/*
* Copyright 2017 Dgraph Labs, Inc. and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package skl
import (
"encoding/binary"
"fmt"
"math/rand"
"strconv"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/dgraph-io/badger/y"
)
const arenaSize = 1 << 20
func newValue(v int) []byte {
return []byte(fmt.Sprintf("%05d", v))
}
// length iterates over skiplist to give exact size.
func length(s *Skiplist) int {
x := s.getNext(s.head, 0)
count := 0
for x != nil {
count++
x = s.getNext(x, 0)
}
return count
}
func TestEmpty(t *testing.T) {
key := []byte("aaa")
l := NewSkiplist(arenaSize)
v := l.Get(key)
require.True(t, v.Value == nil) // Cannot use require.Nil for unsafe.Pointer nil.
for _, less := range []bool{true, false} {
for _, allowEqual := range []bool{true, false} {
n, found := l.findNear(key, less, allowEqual)
require.Nil(t, n)
require.False(t, found)
}
}
it := l.NewIterator()
require.False(t, it.Valid())
it.SeekToFirst()
require.False(t, it.Valid())
it.SeekToLast()
require.False(t, it.Valid())
it.Seek(key)
require.False(t, it.Valid())
l.DecrRef()
require.True(t, l.valid()) // Check the reference counting.
it.Close()
require.False(t, l.valid()) // Check the reference counting.
}
// TestBasic tests single-threaded inserts and updates and gets.
func TestBasic(t *testing.T) {
l := NewSkiplist(arenaSize)
val1 := newValue(42)
val2 := newValue(52)
val3 := newValue(62)
val4 := newValue(72)
// Try inserting values.
// Somehow require.Nil doesn't work when checking for unsafe.Pointer(nil).
l.Put(y.KeyWithTs([]byte("key1"), 0), y.ValueStruct{Value: val1, Meta: 55, UserMeta: 0})
l.Put(y.KeyWithTs([]byte("key2"), 2), y.ValueStruct{Value: val2, Meta: 56, UserMeta: 0})
l.Put(y.KeyWithTs([]byte("key3"), 0), y.ValueStruct{Value: val3, Meta: 57, UserMeta: 0})
v := l.Get(y.KeyWithTs([]byte("key"), 0))
require.True(t, v.Value == nil)
v = l.Get(y.KeyWithTs([]byte("key1"), 0))
require.True(t, v.Value != nil)
require.EqualValues(t, "00042", string(v.Value))
require.EqualValues(t, 55, v.Meta)
v = l.Get(y.KeyWithTs([]byte("key2"), 0))
require.True(t, v.Value == nil)
v = l.Get(y.KeyWithTs([]byte("key3"), 0))
require.True(t, v.Value != nil)
require.EqualValues(t, "00062", string(v.Value))
require.EqualValues(t, 57, v.Meta)
l.Put(y.KeyWithTs([]byte("key3"), 1), y.ValueStruct{Value: val4, Meta: 12, UserMeta: 0})
v = l.Get(y.KeyWithTs([]byte("key3"), 1))
require.True(t, v.Value != nil)
require.EqualValues(t, "00072", string(v.Value))
require.EqualValues(t, 12, v.Meta)
}
// TestConcurrentBasic tests concurrent writes followed by concurrent reads.
func TestConcurrentBasic(t *testing.T) {
const n = 1000
l := NewSkiplist(arenaSize)
var wg sync.WaitGroup
key := func(i int) []byte {
return y.KeyWithTs([]byte(fmt.Sprintf("%05d", i)), 0)
}
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
l.Put(key(i),
y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0})
}(i)
}
wg.Wait()
// Check values. Concurrent reads.
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
v := l.Get(key(i))
require.True(t, v.Value != nil)
require.EqualValues(t, newValue(i), v.Value)
}(i)
}
wg.Wait()
require.EqualValues(t, n, length(l))
}
// TestOneKey will read while writing to one single key.
func TestOneKey(t *testing.T) {
const n = 100
key := y.KeyWithTs([]byte("thekey"), 0)
l := NewSkiplist(arenaSize)
defer l.DecrRef()
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
l.Put(key, y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0})
}(i)
}
// We expect that at least some write made it such that some read returns a value.
var sawValue int32
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
p := l.Get(key)
if p.Value == nil {
return
}
atomic.StoreInt32(&sawValue, 1)
v, err := strconv.Atoi(string(p.Value))
require.NoError(t, err)
require.True(t, 0 <= v && v < n)
}()
}
wg.Wait()
require.True(t, sawValue > 0)
require.EqualValues(t, 1, length(l))
}
func TestFindNear(t *testing.T) {
l := NewSkiplist(arenaSize)
defer l.DecrRef()
for i := 0; i < 1000; i++ {
key := fmt.Sprintf("%05d", i*10+5)
l.Put(y.KeyWithTs([]byte(key), 0), y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0})
}
n, eq := l.findNear(y.KeyWithTs([]byte("00001"), 0), false, false)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("00005"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("00001"), 0), false, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("00005"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("00001"), 0), true, false)
require.Nil(t, n)
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("00001"), 0), true, true)
require.Nil(t, n)
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("00005"), 0), false, false)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("00015"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("00005"), 0), false, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("00005"), 0), string(n.key(l.arena)))
require.True(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("00005"), 0), true, false)
require.Nil(t, n)
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("00005"), 0), true, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("00005"), 0), string(n.key(l.arena)))
require.True(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("05555"), 0), false, false)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("05565"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("05555"), 0), false, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("05555"), 0), string(n.key(l.arena)))
require.True(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("05555"), 0), true, false)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("05545"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("05555"), 0), true, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("05555"), 0), string(n.key(l.arena)))
require.True(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("05558"), 0), false, false)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("05565"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("05558"), 0), false, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("05565"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("05558"), 0), true, false)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("05555"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("05558"), 0), true, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("05555"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("09995"), 0), false, false)
require.Nil(t, n)
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("09995"), 0), false, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("09995"), 0), string(n.key(l.arena)))
require.True(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("09995"), 0), true, false)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("09985"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("09995"), 0), true, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("09995"), 0), string(n.key(l.arena)))
require.True(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("59995"), 0), false, false)
require.Nil(t, n)
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("59995"), 0), false, true)
require.Nil(t, n)
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("59995"), 0), true, false)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("09995"), 0), string(n.key(l.arena)))
require.False(t, eq)
n, eq = l.findNear(y.KeyWithTs([]byte("59995"), 0), true, true)
require.NotNil(t, n)
require.EqualValues(t, y.KeyWithTs([]byte("09995"), 0), string(n.key(l.arena)))
require.False(t, eq)
}
// TestIteratorNext tests a basic iteration over all nodes from the beginning.
func TestIteratorNext(t *testing.T) {
const n = 100
l := NewSkiplist(arenaSize)
defer l.DecrRef()
it := l.NewIterator()
defer it.Close()
require.False(t, it.Valid())
it.SeekToFirst()
require.False(t, it.Valid())
for i := n - 1; i >= 0; i-- {
l.Put(y.KeyWithTs([]byte(fmt.Sprintf("%05d", i)), 0),
y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0})
}
it.SeekToFirst()
for i := 0; i < n; i++ {
require.True(t, it.Valid())
v := it.Value()
require.EqualValues(t, newValue(i), v.Value)
it.Next()
}
require.False(t, it.Valid())
}
// TestIteratorPrev tests a basic iteration over all nodes from the end.
func TestIteratorPrev(t *testing.T) {
const n = 100
l := NewSkiplist(arenaSize)
defer l.DecrRef()
it := l.NewIterator()
defer it.Close()
require.False(t, it.Valid())
it.SeekToFirst()
require.False(t, it.Valid())
for i := 0; i < n; i++ {
l.Put(y.KeyWithTs([]byte(fmt.Sprintf("%05d", i)), 0),
y.ValueStruct{Value: newValue(i), Meta: 0, UserMeta: 0})
}
it.SeekToLast()
for i := n - 1; i >= 0; i-- {
require.True(t, it.Valid())
v := it.Value()
require.EqualValues(t, newValue(i), v.Value)
it.Prev()
}
require.False(t, it.Valid())
}
// TestIteratorSeek tests Seek and SeekForPrev.
func TestIteratorSeek(t *testing.T) {
const n = 100
l := NewSkiplist(arenaSize)
defer l.DecrRef()
it := l.NewIterator()
defer it.Close()
require.False(t, it.Valid())
it.SeekToFirst()
require.False(t, it.Valid())
// 1000, 1010, 1020, ..., 1990.
for i := n - 1; i >= 0; i-- {
v := i*10 + 1000
l.Put(y.KeyWithTs([]byte(fmt.Sprintf("%05d", i*10+1000)), 0),
y.ValueStruct{Value: newValue(v), Meta: 0, UserMeta: 0})
}
it.SeekToFirst()
require.True(t, it.Valid())
v := it.Value()
require.EqualValues(t, "01000", v.Value)
it.Seek(y.KeyWithTs([]byte("01000"), 0))
require.True(t, it.Valid())
v = it.Value()
require.EqualValues(t, "01000", v.Value)
it.Seek(y.KeyWithTs([]byte("01005"), 0))
require.True(t, it.Valid())
v = it.Value()
require.EqualValues(t, "01010", v.Value)
it.Seek(y.KeyWithTs([]byte("01010"), 0))
require.True(t, it.Valid())
v = it.Value()
require.EqualValues(t, "01010", v.Value)
it.Seek(y.KeyWithTs([]byte("99999"), 0))
require.False(t, it.Valid())
// Try SeekForPrev.
it.SeekForPrev(y.KeyWithTs([]byte("00"), 0))
require.False(t, it.Valid())
it.SeekForPrev(y.KeyWithTs([]byte("01000"), 0))
require.True(t, it.Valid())
v = it.Value()
require.EqualValues(t, "01000", v.Value)
it.SeekForPrev(y.KeyWithTs([]byte("01005"), 0))
require.True(t, it.Valid())
v = it.Value()
require.EqualValues(t, "01000", v.Value)
it.SeekForPrev(y.KeyWithTs([]byte("01010"), 0))
require.True(t, it.Valid())
v = it.Value()
require.EqualValues(t, "01010", v.Value)
it.SeekForPrev(y.KeyWithTs([]byte("99999"), 0))
require.True(t, it.Valid())
v = it.Value()
require.EqualValues(t, "01990", v.Value)
}
func randomKey(rng *rand.Rand) []byte {
b := make([]byte, 8)
key := rng.Uint32()
key2 := rng.Uint32()
binary.LittleEndian.PutUint32(b, key)
binary.LittleEndian.PutUint32(b[4:], key2)
return y.KeyWithTs(b, 0)
}
// Standard test. Some fraction is read. Some fraction is write. Writes have
// to go through mutex lock.
func BenchmarkReadWrite(b *testing.B) {
value := newValue(123)
for i := 0; i <= 10; i++ {
readFrac := float32(i) / 10.0
b.Run(fmt.Sprintf("frac_%d", i), func(b *testing.B) {
l := NewSkiplist(int64((b.N + 1) * MaxNodeSize))
defer l.DecrRef()
b.ResetTimer()
var count int
b.RunParallel(func(pb *testing.PB) {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
for pb.Next() {
if rng.Float32() < readFrac {
v := l.Get(randomKey(rng))
if v.Value != nil {
count++
}
} else {
l.Put(randomKey(rng), y.ValueStruct{Value: value, Meta: 0, UserMeta: 0})
}
}
})
})
}
}
// Standard test. Some fraction is read. Some fraction is write. Writes have
// to go through mutex lock.
func BenchmarkReadWriteMap(b *testing.B) {
value := newValue(123)
for i := 0; i <= 10; i++ {
readFrac := float32(i) / 10.0
b.Run(fmt.Sprintf("frac_%d", i), func(b *testing.B) {
m := make(map[string][]byte)
var mutex sync.RWMutex
b.ResetTimer()
var count int
b.RunParallel(func(pb *testing.PB) {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
for pb.Next() {
if rand.Float32() < readFrac {
mutex.RLock()
_, ok := m[string(randomKey(rng))]
mutex.RUnlock()
if ok {
count++
}
} else {
mutex.Lock()
m[string(randomKey(rng))] = value
mutex.Unlock()
}
}
})
})
}
}