blob: b228fd5a61a87e26ff7eca4e59ef4bcba3d1dab6 [file] [edit]
/*
* 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 table
import (
"bytes"
"crypto/sha256"
"fmt"
"hash/crc32"
"math/rand"
"os"
"sort"
"strings"
"sync"
"testing"
"time"
"github.com/cespare/xxhash/v2"
"github.com/dgraph-io/badger/v3/options"
"github.com/dgraph-io/badger/v3/y"
"github.com/dgraph-io/ristretto"
"github.com/stretchr/testify/require"
)
func key(prefix string, i int) string {
return prefix + fmt.Sprintf("%04d", i)
}
func getTestTableOptions() Options {
return Options{
Compression: options.ZSTD,
ZSTDCompressionLevel: 15,
BlockSize: 4 * 1024,
BloomFalsePositive: 0.01,
}
}
func buildTestTable(t *testing.T, prefix string, n int, opts Options) *Table {
if opts.BlockSize == 0 {
opts.BlockSize = 4 * 1024
}
y.AssertTrue(n <= 10000)
keyValues := make([][]string, n)
for i := 0; i < n; i++ {
k := key(prefix, i)
v := fmt.Sprintf("%d", i)
keyValues[i] = []string{k, v}
}
return buildTable(t, keyValues, opts)
}
// keyValues is n by 2 where n is number of pairs.
func buildTable(t *testing.T, keyValues [][]string, opts Options) *Table {
b := NewTableBuilder(opts)
defer b.Close()
// TODO: Add test for file garbage collection here. No files should be left after the tests here.
filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Uint32())
sort.Slice(keyValues, func(i, j int) bool {
return keyValues[i][0] < keyValues[j][0]
})
for _, kv := range keyValues {
y.AssertTrue(len(kv) == 2)
b.Add(y.KeyWithTs([]byte(kv[0]), 0),
y.ValueStruct{Value: []byte(kv[1]), Meta: 'A', UserMeta: 0}, 0)
}
tbl, err := CreateTable(filename, b)
require.NoError(t, err, "writing to file failed")
return tbl
}
func TestTableIterator(t *testing.T) {
for _, n := range []int{99, 100, 101} {
t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", n, opts)
defer table.DecrRef()
it := table.NewIterator(0)
defer it.Close()
count := 0
for it.Rewind(); it.Valid(); it.Next() {
v := it.Value()
k := y.KeyWithTs([]byte(key("key", count)), 0)
require.EqualValues(t, k, it.Key())
require.EqualValues(t, fmt.Sprintf("%d", count), string(v.Value))
count++
}
require.Equal(t, count, n)
})
}
}
func TestSeekToFirst(t *testing.T) {
for _, n := range []int{99, 100, 101, 199, 200, 250, 9999, 10000} {
t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", n, opts)
defer table.DecrRef()
it := table.NewIterator(0)
defer it.Close()
it.seekToFirst()
require.True(t, it.Valid())
v := it.Value()
require.EqualValues(t, "0", string(v.Value))
require.EqualValues(t, 'A', v.Meta)
})
}
}
func TestSeekToLast(t *testing.T) {
for _, n := range []int{99, 100, 101, 199, 200, 250, 9999, 10000} {
t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", n, opts)
defer table.DecrRef()
it := table.NewIterator(0)
defer it.Close()
it.seekToLast()
require.True(t, it.Valid())
v := it.Value()
require.EqualValues(t, fmt.Sprintf("%d", n-1), string(v.Value))
require.EqualValues(t, 'A', v.Meta)
it.prev()
require.True(t, it.Valid())
v = it.Value()
require.EqualValues(t, fmt.Sprintf("%d", n-2), string(v.Value))
require.EqualValues(t, 'A', v.Meta)
})
}
}
func TestSeek(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "k", 10000, opts)
defer table.DecrRef()
it := table.NewIterator(0)
defer it.Close()
var data = []struct {
in string
valid bool
out string
}{
{"abc", true, "k0000"},
{"k0100", true, "k0100"},
{"k0100b", true, "k0101"}, // Test case where we jump to next block.
{"k1234", true, "k1234"},
{"k1234b", true, "k1235"},
{"k9999", true, "k9999"},
{"z", false, ""},
}
for _, tt := range data {
it.seek(y.KeyWithTs([]byte(tt.in), 0))
if !tt.valid {
require.False(t, it.Valid())
continue
}
require.True(t, it.Valid())
k := it.Key()
require.EqualValues(t, tt.out, string(y.ParseKey(k)))
}
}
func TestSeekForPrev(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "k", 10000, opts)
defer table.DecrRef()
it := table.NewIterator(0)
defer it.Close()
var data = []struct {
in string
valid bool
out string
}{
{"abc", false, ""},
{"k0100", true, "k0100"},
{"k0100b", true, "k0100"}, // Test case where we jump to next block.
{"k1234", true, "k1234"},
{"k1234b", true, "k1234"},
{"k9999", true, "k9999"},
{"z", true, "k9999"},
}
for _, tt := range data {
it.seekForPrev(y.KeyWithTs([]byte(tt.in), 0))
if !tt.valid {
require.False(t, it.Valid())
continue
}
require.True(t, it.Valid())
k := it.Key()
require.EqualValues(t, tt.out, string(y.ParseKey(k)))
}
}
func TestIterateFromStart(t *testing.T) {
// Vary the number of elements added.
for _, n := range []int{99, 100, 101, 199, 200, 250, 9999, 10000} {
t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", n, opts)
defer table.DecrRef()
ti := table.NewIterator(0)
defer ti.Close()
ti.reset()
ti.seekToFirst()
require.True(t, ti.Valid())
// No need to do a Next.
// ti.Seek brings us to the first key >= "". Essentially a SeekToFirst.
var count int
for ; ti.Valid(); ti.next() {
v := ti.Value()
require.EqualValues(t, fmt.Sprintf("%d", count), string(v.Value))
require.EqualValues(t, 'A', v.Meta)
count++
}
require.EqualValues(t, n, count)
})
}
}
func TestIterateFromEnd(t *testing.T) {
// Vary the number of elements added.
for _, n := range []int{99, 100, 101, 199, 200, 250, 9999, 10000} {
t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", n, opts)
defer table.DecrRef()
ti := table.NewIterator(0)
defer ti.Close()
ti.reset()
ti.seek(y.KeyWithTs([]byte("zzzzzz"), 0)) // Seek to end, an invalid element.
require.False(t, ti.Valid())
for i := n - 1; i >= 0; i-- {
ti.prev()
require.True(t, ti.Valid())
v := ti.Value()
require.EqualValues(t, fmt.Sprintf("%d", i), string(v.Value))
require.EqualValues(t, 'A', v.Meta)
}
ti.prev()
require.False(t, ti.Valid())
})
}
}
func TestTable(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", 10000, opts)
defer table.DecrRef()
ti := table.NewIterator(0)
defer ti.Close()
kid := 1010
seek := y.KeyWithTs([]byte(key("key", kid)), 0)
for ti.seek(seek); ti.Valid(); ti.next() {
k := ti.Key()
require.EqualValues(t, string(y.ParseKey(k)), key("key", kid))
kid++
}
if kid != 10000 {
t.Errorf("Expected kid: 10000. Got: %v", kid)
}
ti.seek(y.KeyWithTs([]byte(key("key", 99999)), 0))
require.False(t, ti.Valid())
ti.seek(y.KeyWithTs([]byte(key("key", -1)), 0))
require.True(t, ti.Valid())
k := ti.Key()
require.EqualValues(t, string(y.ParseKey(k)), key("key", 0))
}
func TestIterateBackAndForth(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", 10000, opts)
defer table.DecrRef()
seek := y.KeyWithTs([]byte(key("key", 1010)), 0)
it := table.NewIterator(0)
defer it.Close()
it.seek(seek)
require.True(t, it.Valid())
k := it.Key()
require.EqualValues(t, seek, k)
it.prev()
it.prev()
require.True(t, it.Valid())
k = it.Key()
require.EqualValues(t, key("key", 1008), string(y.ParseKey(k)))
it.next()
it.next()
require.True(t, it.Valid())
k = it.Key()
require.EqualValues(t, key("key", 1010), y.ParseKey(k))
it.seek(y.KeyWithTs([]byte(key("key", 2000)), 0))
require.True(t, it.Valid())
k = it.Key()
require.EqualValues(t, key("key", 2000), y.ParseKey(k))
it.prev()
require.True(t, it.Valid())
k = it.Key()
require.EqualValues(t, key("key", 1999), y.ParseKey(k))
it.seekToFirst()
k = it.Key()
require.EqualValues(t, key("key", 0), string(y.ParseKey(k)))
}
func TestUniIterator(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", 10000, opts)
defer table.DecrRef()
{
it := table.NewIterator(0)
defer it.Close()
var count int
for it.Rewind(); it.Valid(); it.Next() {
v := it.Value()
require.EqualValues(t, fmt.Sprintf("%d", count), string(v.Value))
require.EqualValues(t, 'A', v.Meta)
count++
}
require.EqualValues(t, 10000, count)
}
{
it := table.NewIterator(REVERSED)
defer it.Close()
var count int
for it.Rewind(); it.Valid(); it.Next() {
v := it.Value()
require.EqualValues(t, fmt.Sprintf("%d", 10000-1-count), string(v.Value))
require.EqualValues(t, 'A', v.Meta)
count++
}
require.EqualValues(t, 10000, count)
}
}
// Try having only one table.
func TestConcatIteratorOneTable(t *testing.T) {
opts := getTestTableOptions()
tbl := buildTable(t, [][]string{
{"k1", "a1"},
{"k2", "a2"},
}, opts)
defer tbl.DecrRef()
it := NewConcatIterator([]*Table{tbl}, 0)
defer it.Close()
it.Rewind()
require.True(t, it.Valid())
k := it.Key()
require.EqualValues(t, "k1", string(y.ParseKey(k)))
vs := it.Value()
require.EqualValues(t, "a1", string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
}
func TestConcatIterator(t *testing.T) {
opts := getTestTableOptions()
tbl := buildTestTable(t, "keya", 10000, opts)
tbl2 := buildTestTable(t, "keyb", 10000, opts)
tbl3 := buildTestTable(t, "keyc", 10000, opts)
defer tbl.DecrRef()
defer tbl2.DecrRef()
defer tbl3.DecrRef()
{
it := NewConcatIterator([]*Table{tbl, tbl2, tbl3}, 0)
defer it.Close()
it.Rewind()
require.True(t, it.Valid())
var count int
for ; it.Valid(); it.Next() {
vs := it.Value()
require.EqualValues(t, fmt.Sprintf("%d", count%10000), string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
count++
}
require.EqualValues(t, 30000, count)
it.Seek(y.KeyWithTs([]byte("a"), 0))
require.EqualValues(t, "keya0000", string(y.ParseKey(it.Key())))
vs := it.Value()
require.EqualValues(t, "0", string(vs.Value))
it.Seek(y.KeyWithTs([]byte("keyb"), 0))
require.EqualValues(t, "keyb0000", string(y.ParseKey(it.Key())))
vs = it.Value()
require.EqualValues(t, "0", string(vs.Value))
it.Seek(y.KeyWithTs([]byte("keyb9999b"), 0))
require.EqualValues(t, "keyc0000", string(y.ParseKey(it.Key())))
vs = it.Value()
require.EqualValues(t, "0", string(vs.Value))
it.Seek(y.KeyWithTs([]byte("keyd"), 0))
require.False(t, it.Valid())
}
{
it := NewConcatIterator([]*Table{tbl, tbl2, tbl3}, REVERSED)
defer it.Close()
it.Rewind()
require.True(t, it.Valid())
var count int
for ; it.Valid(); it.Next() {
vs := it.Value()
require.EqualValues(t, fmt.Sprintf("%d", 10000-(count%10000)-1), string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
count++
}
require.EqualValues(t, 30000, count)
it.Seek(y.KeyWithTs([]byte("a"), 0))
require.False(t, it.Valid())
it.Seek(y.KeyWithTs([]byte("keyb"), 0))
require.EqualValues(t, "keya9999", string(y.ParseKey(it.Key())))
vs := it.Value()
require.EqualValues(t, "9999", string(vs.Value))
it.Seek(y.KeyWithTs([]byte("keyb9999b"), 0))
require.EqualValues(t, "keyb9999", string(y.ParseKey(it.Key())))
vs = it.Value()
require.EqualValues(t, "9999", string(vs.Value))
it.Seek(y.KeyWithTs([]byte("keyd"), 0))
require.EqualValues(t, "keyc9999", string(y.ParseKey(it.Key())))
vs = it.Value()
require.EqualValues(t, "9999", string(vs.Value))
}
}
func TestMergingIterator(t *testing.T) {
opts := getTestTableOptions()
tbl1 := buildTable(t, [][]string{
{"k1", "a1"},
{"k4", "a4"},
{"k5", "a5"},
}, opts)
tbl2 := buildTable(t, [][]string{
{"k2", "b2"},
{"k3", "b3"},
{"k4", "b4"},
}, opts)
expected := []struct {
key string
value string
}{
{"k1", "a1"},
{"k2", "b2"},
{"k3", "b3"},
{"k4", "a4"},
{"k5", "a5"},
}
defer tbl1.DecrRef()
defer tbl2.DecrRef()
it1 := tbl1.NewIterator(0)
it2 := NewConcatIterator([]*Table{tbl2}, 0)
it := NewMergeIterator([]y.Iterator{it1, it2}, false)
defer it.Close()
var i int
for it.Rewind(); it.Valid(); it.Next() {
k := it.Key()
vs := it.Value()
require.EqualValues(t, expected[i].key, string(y.ParseKey(k)))
require.EqualValues(t, expected[i].value, string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
i++
}
require.Equal(t, i, len(expected))
require.False(t, it.Valid())
}
func TestMergingIteratorReversed(t *testing.T) {
opts := getTestTableOptions()
tbl1 := buildTable(t, [][]string{
{"k1", "a1"},
{"k2", "a2"},
{"k4", "a4"},
{"k5", "a5"},
}, opts)
tbl2 := buildTable(t, [][]string{
{"k1", "b2"},
{"k3", "b3"},
{"k4", "b4"},
{"k5", "b5"},
}, opts)
expected := []struct {
key string
value string
}{
{"k5", "a5"},
{"k4", "a4"},
{"k3", "b3"},
{"k2", "a2"},
{"k1", "a1"},
}
defer tbl1.DecrRef()
defer tbl2.DecrRef()
it1 := tbl1.NewIterator(REVERSED)
it2 := NewConcatIterator([]*Table{tbl2}, REVERSED)
it := NewMergeIterator([]y.Iterator{it1, it2}, true)
defer it.Close()
var i int
for it.Rewind(); it.Valid(); it.Next() {
k := it.Key()
vs := it.Value()
require.EqualValues(t, expected[i].key, string(y.ParseKey(k)))
require.EqualValues(t, expected[i].value, string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
i++
}
require.Equal(t, i, len(expected))
require.False(t, it.Valid())
}
// Take only the first iterator.
func TestMergingIteratorTakeOne(t *testing.T) {
opts := getTestTableOptions()
t1 := buildTable(t, [][]string{
{"k1", "a1"},
{"k2", "a2"},
}, opts)
t2 := buildTable(t, [][]string{{"l1", "b1"}}, opts)
defer t1.DecrRef()
defer t2.DecrRef()
it1 := NewConcatIterator([]*Table{t1}, 0)
it2 := NewConcatIterator([]*Table{t2}, 0)
it := NewMergeIterator([]y.Iterator{it1, it2}, false)
defer it.Close()
it.Rewind()
require.True(t, it.Valid())
k := it.Key()
require.EqualValues(t, "k1", string(y.ParseKey(k)))
vs := it.Value()
require.EqualValues(t, "a1", string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
it.Next()
require.True(t, it.Valid())
k = it.Key()
require.EqualValues(t, "k2", string(y.ParseKey(k)))
vs = it.Value()
require.EqualValues(t, "a2", string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
it.Next()
k = it.Key()
require.EqualValues(t, "l1", string(y.ParseKey(k)))
vs = it.Value()
require.EqualValues(t, "b1", string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
it.Next()
require.False(t, it.Valid())
}
// Take only the second iterator.
func TestMergingIteratorTakeTwo(t *testing.T) {
opts := getTestTableOptions()
t1 := buildTable(t, [][]string{{"l1", "b1"}}, opts)
t2 := buildTable(t, [][]string{
{"k1", "a1"},
{"k2", "a2"},
}, opts)
defer t1.DecrRef()
defer t2.DecrRef()
it1 := NewConcatIterator([]*Table{t1}, 0)
it2 := NewConcatIterator([]*Table{t2}, 0)
it := NewMergeIterator([]y.Iterator{it1, it2}, false)
defer it.Close()
it.Rewind()
require.True(t, it.Valid())
k := it.Key()
require.EqualValues(t, "k1", string(y.ParseKey(k)))
vs := it.Value()
require.EqualValues(t, "a1", string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
it.Next()
require.True(t, it.Valid())
k = it.Key()
require.EqualValues(t, "k2", string(y.ParseKey(k)))
vs = it.Value()
require.EqualValues(t, "a2", string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
it.Next()
require.True(t, it.Valid())
k = it.Key()
require.EqualValues(t, "l1", string(y.ParseKey(k)))
vs = it.Value()
require.EqualValues(t, "b1", string(vs.Value))
require.EqualValues(t, 'A', vs.Meta)
it.Next()
require.False(t, it.Valid())
}
func TestTableBigValues(t *testing.T) {
value := func(i int) []byte {
return []byte(fmt.Sprintf("%01048576d", i)) // Return 1MB value which is > math.MaxUint16.
}
rand.Seed(time.Now().UnixNano())
n := 100 // Insert 100 keys.
opts := Options{Compression: options.ZSTD, BlockSize: 4 * 1024, BloomFalsePositive: 0.01,
TableSize: uint64(n) * 1 << 20}
builder := NewTableBuilder(opts)
defer builder.Close()
for i := 0; i < n; i++ {
key := y.KeyWithTs([]byte(key("", i)), uint64(i+1))
vs := y.ValueStruct{Value: value(i)}
builder.Add(key, vs, 0)
}
filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Uint32())
tbl, err := CreateTable(filename, builder)
require.NoError(t, err, "unable to open table")
defer tbl.DecrRef()
itr := tbl.NewIterator(0)
require.True(t, itr.Valid())
count := 0
for itr.Rewind(); itr.Valid(); itr.Next() {
require.Equal(t, []byte(key("", count)), y.ParseKey(itr.Key()), "keys are not equal")
require.Equal(t, value(count), itr.Value().Value, "values are not equal")
count++
}
require.False(t, itr.Valid(), "table iterator should be invalid now")
require.Equal(t, n, count)
require.Equal(t, n, int(tbl.MaxVersion()))
}
// This test is for verifying checksum failure during table open.
func TestTableChecksum(t *testing.T) {
rand.Seed(time.Now().Unix())
// we are going to write random byte at random location in table file.
rb := make([]byte, 100)
rand.Read(rb)
opts := getTestTableOptions()
opts.ChkMode = options.OnTableAndBlockRead
tbl := buildTestTable(t, "k", 10000, opts)
// Write random bytes at random location.
start := rand.Intn(len(tbl.Data) - len(rb))
n := copy(tbl.Data[start:], rb)
require.Equal(t, n, len(rb))
require.Panics(t, func() {
// Either OpenTable will panic on corrupted data or the checksum verification will fail.
_, err := OpenTable(tbl.MmapFile, opts)
if strings.Contains(err.Error(), "checksum") {
panic("checksum mismatch")
}
})
}
var cacheConfig = ristretto.Config{
NumCounters: 1000000 * 10,
MaxCost: 1000000,
BufferItems: 64,
Metrics: true,
}
func BenchmarkRead(b *testing.B) {
n := int(5 * 1e6)
tbl := getTableForBenchmarks(b, n, nil)
defer tbl.DecrRef()
b.ResetTimer()
// Iterate b.N times over the entire table.
for i := 0; i < b.N; i++ {
func() {
it := tbl.NewIterator(0)
defer it.Close()
for it.seekToFirst(); it.Valid(); it.next() {
}
}()
}
}
func BenchmarkReadAndBuild(b *testing.B) {
n := int(5 * 1e6)
var cache, _ = ristretto.NewCache(&cacheConfig)
tbl := getTableForBenchmarks(b, n, cache)
defer tbl.DecrRef()
b.ResetTimer()
// Iterate b.N times over the entire table.
for i := 0; i < b.N; i++ {
func() {
opts := Options{Compression: options.ZSTD, BlockSize: 4 * 0124, BloomFalsePositive: 0.01}
opts.BlockCache = cache
newBuilder := NewTableBuilder(opts)
it := tbl.NewIterator(0)
defer it.Close()
for it.seekToFirst(); it.Valid(); it.next() {
vs := it.Value()
newBuilder.Add(it.Key(), vs, 0)
}
newBuilder.Finish()
}()
}
}
func BenchmarkReadMerged(b *testing.B) {
n := int(5 * 1e6)
m := 5 // Number of tables.
y.AssertTrue((n % m) == 0)
tableSize := n / m
var tables []*Table
var cache, err = ristretto.NewCache(&cacheConfig)
require.NoError(b, err)
for i := 0; i < m; i++ {
filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Uint32())
opts := Options{Compression: options.ZSTD, BlockSize: 4 * 1024, BloomFalsePositive: 0.01}
opts.BlockCache = cache
builder := NewTableBuilder(opts)
for j := 0; j < tableSize; j++ {
id := j*m + i // Arrays are interleaved.
// id := i*tableSize+j (not interleaved)
k := fmt.Sprintf("%016x", id)
v := fmt.Sprintf("%d", id)
builder.Add([]byte(k), y.ValueStruct{Value: []byte(v), Meta: 123, UserMeta: 0}, 0)
}
tbl, err := CreateTable(filename, builder)
y.Check(err)
builder.Close()
tables = append(tables, tbl)
defer tbl.DecrRef()
}
b.ResetTimer()
// Iterate b.N times over the entire table.
for i := 0; i < b.N; i++ {
func() {
var iters []y.Iterator
for _, tbl := range tables {
iters = append(iters, tbl.NewIterator(0))
}
it := NewMergeIterator(iters, false)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
}
}()
}
}
func BenchmarkChecksum(b *testing.B) {
keySz := []int{KB, 2 * KB, 4 * KB, 8 * KB, 16 * KB, 32 * KB, 64 * KB, 128 * KB, 256 * KB, MB}
for _, kz := range keySz {
key := make([]byte, kz)
b.Run(fmt.Sprintf("CRC %d", kz), func(b *testing.B) {
for i := 0; i < b.N; i++ {
crc32.ChecksumIEEE(key)
}
})
b.Run(fmt.Sprintf("xxHash64 %d", kz), func(b *testing.B) {
for i := 0; i < b.N; i++ {
xxhash.Sum64(key)
}
})
b.Run(fmt.Sprintf("SHA256 %d", kz), func(b *testing.B) {
for i := 0; i < b.N; i++ {
sha256.Sum256(key)
}
})
}
}
func BenchmarkRandomRead(b *testing.B) {
n := int(5 * 1e6)
tbl := getTableForBenchmarks(b, n, nil)
defer tbl.DecrRef()
r := rand.New(rand.NewSource(time.Now().Unix()))
b.ResetTimer()
for i := 0; i < b.N; i++ {
itr := tbl.NewIterator(0)
no := r.Intn(n)
k := []byte(fmt.Sprintf("%016x", no))
v := []byte(fmt.Sprintf("%d", no))
itr.Seek(k)
if !itr.Valid() {
b.Fatal("itr should be valid")
}
v1 := itr.Value().Value
if !bytes.Equal(v, v1) {
fmt.Println("value does not match")
b.Fatal()
}
itr.Close()
}
}
func getTableForBenchmarks(b *testing.B, count int, cache *ristretto.Cache) *Table {
rand.Seed(time.Now().Unix())
opts := Options{Compression: options.ZSTD, BlockSize: 4 * 1024, BloomFalsePositive: 0.01}
if cache == nil {
var err error
cache, err = ristretto.NewCache(&cacheConfig)
require.NoError(b, err)
}
opts.BlockCache = cache
builder := NewTableBuilder(opts)
defer builder.Close()
filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Uint32())
for i := 0; i < count; i++ {
k := fmt.Sprintf("%016x", i)
v := fmt.Sprintf("%d", i)
builder.Add([]byte(k), y.ValueStruct{Value: []byte(v)}, 0)
}
tbl, err := CreateTable(filename, builder)
require.NoError(b, err, "unable to open table")
return tbl
}
func TestMain(m *testing.M) {
rand.Seed(time.Now().UTC().UnixNano())
os.Exit(m.Run())
}
// Run this test with command "go test -race -run TestDoesNotHaveRace"
func TestDoesNotHaveRace(t *testing.T) {
opts := getTestTableOptions()
table := buildTestTable(t, "key", 10000, opts)
defer table.DecrRef()
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
require.True(t, table.DoesNotHave(uint32(1237882)))
wg.Done()
}()
}
wg.Wait()
}
func TestMaxVersion(t *testing.T) {
opt := getTestTableOptions()
b := NewTableBuilder(opt)
defer b.Close()
filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Uint32())
N := 1000
for i := 0; i < N; i++ {
b.Add(y.KeyWithTs([]byte(fmt.Sprintf("foo:%d", i)), uint64(i+1)), y.ValueStruct{}, 0)
}
table, err := CreateTable(filename, b)
require.NoError(t, err)
require.Equal(t, N, int(table.MaxVersion()))
}