mirror of
https://github.com/luxfi/zapdb.git
synced 2026-10-30 15:12:20 +00:00
This reverts commit a7942af976.
This commit is being reverted because we have seen some crashes which
could be caused by it. We haven't been able to reproduce the crashes yet.
Related to #1389, #1388, #1387
Also, see https://discuss.dgraph.io/t/current-state-of-badger-crashes/7602
This commit had some conflicts. See PR description for details.
979 lines
25 KiB
Go
979 lines
25 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 table
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"hash/crc32"
|
|
"math/rand"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cespare/xxhash"
|
|
"github.com/dgraph-io/badger/v2/options"
|
|
"github.com/dgraph-io/badger/v2/pb"
|
|
"github.com/dgraph-io/badger/v2/y"
|
|
"github.com/dgraph-io/ristretto"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
KB = 1024
|
|
MB = KB * 1024
|
|
)
|
|
|
|
func key(prefix string, i int) string {
|
|
return prefix + fmt.Sprintf("%04d", i)
|
|
}
|
|
|
|
func getTestTableOptions() Options {
|
|
return Options{
|
|
Compression: options.ZSTD,
|
|
ZSTDCompressionLevel: 15,
|
|
LoadingMode: options.LoadToRAM,
|
|
BlockSize: 4 * 1024,
|
|
BloomFalsePositive: 0.01,
|
|
}
|
|
|
|
}
|
|
func buildTestTable(t *testing.T, prefix string, n int, opts Options) *os.File {
|
|
if opts.BloomFalsePositive == 0 {
|
|
opts.BloomFalsePositive = 0.01
|
|
}
|
|
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) *os.File {
|
|
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())
|
|
f, err := y.CreateSyncedFile(filename, true)
|
|
require.NoError(t, err)
|
|
|
|
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)
|
|
}
|
|
_, err = f.Write(b.Finish())
|
|
require.NoError(t, err, "writing to file failed")
|
|
f.Close()
|
|
f, _ = y.OpenSyncedFile(filename, true)
|
|
return f
|
|
}
|
|
|
|
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()
|
|
f := buildTestTable(t, "key", n, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
it := table.NewIterator(false)
|
|
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()
|
|
f := buildTestTable(t, "key", n, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
it := table.NewIterator(false)
|
|
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()
|
|
f := buildTestTable(t, "key", n, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
it := table.NewIterator(false)
|
|
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()
|
|
f := buildTestTable(t, "k", 10000, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
|
|
it := table.NewIterator(false)
|
|
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()
|
|
f := buildTestTable(t, "k", 10000, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
|
|
it := table.NewIterator(false)
|
|
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()
|
|
f := buildTestTable(t, "key", n, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
ti := table.NewIterator(false)
|
|
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()
|
|
f := buildTestTable(t, "key", n, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
ti := table.NewIterator(false)
|
|
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()
|
|
opts.LoadingMode = options.FileIO
|
|
f := buildTestTable(t, "key", 10000, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
ti := table.NewIterator(false)
|
|
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()
|
|
f := buildTestTable(t, "key", 10000, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
|
|
seek := y.KeyWithTs([]byte(key("key", 1010)), 0)
|
|
it := table.NewIterator(false)
|
|
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), y.ParseKey(k))
|
|
}
|
|
|
|
func TestUniIterator(t *testing.T) {
|
|
opts := getTestTableOptions()
|
|
f := buildTestTable(t, "key", 10000, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
{
|
|
it := table.NewIterator(false)
|
|
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(true)
|
|
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()
|
|
f := buildTable(t, [][]string{
|
|
{"k1", "a1"},
|
|
{"k2", "a2"},
|
|
}, opts)
|
|
|
|
tbl, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer tbl.DecrRef()
|
|
|
|
it := NewConcatIterator([]*Table{tbl}, 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)
|
|
}
|
|
|
|
func TestConcatIterator(t *testing.T) {
|
|
opts := getTestTableOptions()
|
|
f := buildTestTable(t, "keya", 10000, opts)
|
|
f2 := buildTestTable(t, "keyb", 10000, opts)
|
|
f3 := buildTestTable(t, "keyc", 10000, opts)
|
|
tbl, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer tbl.DecrRef()
|
|
tbl2, err := OpenTable(f2, opts)
|
|
require.NoError(t, err)
|
|
defer tbl2.DecrRef()
|
|
tbl3, err := OpenTable(f3, opts)
|
|
require.NoError(t, err)
|
|
defer tbl3.DecrRef()
|
|
|
|
{
|
|
it := NewConcatIterator([]*Table{tbl, tbl2, tbl3}, false)
|
|
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}, true)
|
|
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()
|
|
f1 := buildTable(t, [][]string{
|
|
{"k1", "a1"},
|
|
{"k4", "a4"},
|
|
{"k5", "a5"},
|
|
}, opts)
|
|
f2 := 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"},
|
|
}
|
|
tbl1, err := OpenTable(f1, opts)
|
|
require.NoError(t, err)
|
|
defer tbl1.DecrRef()
|
|
tbl2, err := OpenTable(f2, opts)
|
|
require.NoError(t, err)
|
|
defer tbl2.DecrRef()
|
|
it1 := tbl1.NewIterator(false)
|
|
it2 := NewConcatIterator([]*Table{tbl2}, false)
|
|
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()
|
|
f1 := buildTable(t, [][]string{
|
|
{"k1", "a1"},
|
|
{"k2", "a2"},
|
|
{"k4", "a4"},
|
|
{"k5", "a5"},
|
|
}, opts)
|
|
f2 := 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"},
|
|
}
|
|
tbl1, err := OpenTable(f1, opts)
|
|
require.NoError(t, err)
|
|
defer tbl1.DecrRef()
|
|
tbl2, err := OpenTable(f2, opts)
|
|
require.NoError(t, err)
|
|
defer tbl2.DecrRef()
|
|
it1 := tbl1.NewIterator(true)
|
|
it2 := NewConcatIterator([]*Table{tbl2}, true)
|
|
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()
|
|
f1 := buildTable(t, [][]string{
|
|
{"k1", "a1"},
|
|
{"k2", "a2"},
|
|
}, opts)
|
|
f2 := buildTable(t, [][]string{{"l1", "b1"}}, opts)
|
|
|
|
t1, err := OpenTable(f1, opts)
|
|
require.NoError(t, err)
|
|
defer t1.DecrRef()
|
|
t2, err := OpenTable(f2, opts)
|
|
require.NoError(t, err)
|
|
defer t2.DecrRef()
|
|
|
|
it1 := NewConcatIterator([]*Table{t1}, false)
|
|
it2 := NewConcatIterator([]*Table{t2}, false)
|
|
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()
|
|
f1 := buildTable(t, [][]string{{"l1", "b1"}}, opts)
|
|
f2 := buildTable(t, [][]string{
|
|
{"k1", "a1"},
|
|
{"k2", "a2"},
|
|
}, opts)
|
|
|
|
t1, err := OpenTable(f1, opts)
|
|
require.NoError(t, err)
|
|
defer t1.DecrRef()
|
|
t2, err := OpenTable(f2, opts)
|
|
require.NoError(t, err)
|
|
defer t2.DecrRef()
|
|
|
|
it1 := NewConcatIterator([]*Table{t1}, false)
|
|
it2 := NewConcatIterator([]*Table{t2}, false)
|
|
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())
|
|
filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Int63())
|
|
f, err := y.OpenSyncedFile(filename, true)
|
|
require.NoError(t, err, "unable to create file")
|
|
|
|
n := 100 // Insert 100 keys.
|
|
opts := Options{Compression: options.ZSTD, BlockSize: 4 * 1024, BloomFalsePositive: 0.01}
|
|
builder := NewTableBuilder(opts)
|
|
for i := 0; i < n; i++ {
|
|
key := y.KeyWithTs([]byte(key("", i)), 0)
|
|
vs := y.ValueStruct{Value: value(i)}
|
|
builder.Add(key, vs, 0)
|
|
}
|
|
|
|
_, err = f.Write(builder.Finish())
|
|
require.NoError(t, err, "unable to write to file")
|
|
tbl, err := OpenTable(f, opts)
|
|
require.NoError(t, err, "unable to open table")
|
|
defer tbl.DecrRef()
|
|
|
|
itr := tbl.NewIterator(false)
|
|
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)
|
|
}
|
|
|
|
// 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
|
|
f := buildTestTable(t, "k", 10000, opts)
|
|
fi, err := f.Stat()
|
|
require.NoError(t, err, "unable to get file information")
|
|
// Write random bytes at random location.
|
|
n, err := f.WriteAt(rb, rand.Int63n(fi.Size()))
|
|
require.NoError(t, err)
|
|
require.Equal(t, n, len(rb))
|
|
|
|
_, err = OpenTable(f, opts)
|
|
if err == nil || !strings.Contains(err.Error(), "checksum") {
|
|
t.Fatal("Test should have been failed with checksum mismatch error")
|
|
}
|
|
}
|
|
|
|
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(false)
|
|
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.Cache = cache
|
|
newBuilder := NewTableBuilder(opts)
|
|
it := tbl.NewIterator(false)
|
|
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.Int63())
|
|
opts := Options{Compression: options.ZSTD, BlockSize: 4 * 1024, BloomFalsePositive: 0.01}
|
|
opts.Cache = cache
|
|
builder := NewTableBuilder(opts)
|
|
f, err := y.OpenSyncedFile(filename, true)
|
|
y.Check(err)
|
|
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)
|
|
}
|
|
_, err = f.Write(builder.Finish())
|
|
require.NoError(b, err, "unable to write to file")
|
|
tbl, err := OpenTable(f, opts)
|
|
y.Check(err)
|
|
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(false))
|
|
}
|
|
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(false)
|
|
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.Cache = cache
|
|
builder := NewTableBuilder(opts)
|
|
filename := fmt.Sprintf("%s%s%d.sst", os.TempDir(), string(os.PathSeparator), rand.Int63())
|
|
f, err := y.OpenSyncedFile(filename, true)
|
|
require.NoError(b, err)
|
|
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)
|
|
}
|
|
|
|
_, err = f.Write(builder.Finish())
|
|
require.NoError(b, err, "unable to write to file")
|
|
tbl, err := OpenTable(f, opts)
|
|
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())
|
|
}
|
|
|
|
func TestOpenKVSize(t *testing.T) {
|
|
opts := getTestTableOptions()
|
|
table, err := OpenTable(buildTestTable(t, "foo", 1, opts), opts)
|
|
require.NoError(t, err)
|
|
|
|
// The following values might change if the table/header structure is changed.
|
|
var entrySize uint64 = 15 /* DiffKey len */ + 4 /* Header Size */ + 4 /* Encoded vp */
|
|
require.Equal(t, entrySize, table.EstimatedSize())
|
|
}
|
|
|
|
// Run this test with command "go test -race -run TestDoesNotHaveRace"
|
|
func TestDoesNotHaveRace(t *testing.T) {
|
|
opts := getTestTableOptions()
|
|
f := buildTestTable(t, "key", 10000, opts)
|
|
table, err := OpenTable(f, opts)
|
|
require.NoError(t, err)
|
|
defer table.DecrRef()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(5)
|
|
for i := 0; i < 5; i++ {
|
|
go func() {
|
|
require.True(t, table.DoesNotHave(uint64(1237882)))
|
|
wg.Done()
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
var ko *pb.BlockOffset
|
|
|
|
// Use this benchmark to manually verify block offset size calculation
|
|
func BenchmarkBlockOffsetSizeCalculation(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
ko = &pb.BlockOffset{
|
|
Key: []byte{1, 23},
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBlockOffsetSizeCalculation(t *testing.T) {
|
|
// Empty struct testing.
|
|
require.Equal(t, calculateOffsetsSize([]*pb.BlockOffset{&pb.BlockOffset{}}), int64(88))
|
|
// Testing with key bytes
|
|
require.Equal(t, calculateOffsetsSize([]*pb.BlockOffset{&pb.BlockOffset{Key: []byte{1, 1}}}),
|
|
int64(90))
|
|
}
|