mirror of
https://github.com/luxfi/zapdb.git
synced 2026-11-02 06:01:15 +00:00
* Cast sz to uint32 to fix compilation on 32 bit (#1175) `env GOOS=linux GOARCH=arm GOARM=7 go build` no longer fails with overflow. Similar to commitc7b7ca38c4. Signed-off-by: Christian Stewart <[email protected]> (cherry picked from commit04b944f813) * Fix commit sha for WithInMemory in CHANGELOG. (#1172) (cherry picked from commitd4d3b81eca) * Fix windows build (#1177) Fix windows build and some deepsource warnings (cherry picked from commit4235768d3d) * Fix checkOverlap in compaction (#1166) The overlap check in compaction would keep additional keys in case the levels under compaction had overlap amongst themselves. This commit fixes it. This commit also fixes the `numVersionsToKeep` check. Without this commit, we would end up keeping `2` versions of a key even when the number of versions to keep was set to `1`. See `level 0 to level 1 with lower overlap` test. Fixes https://github.com/dgraph-io/badger/issues/1053 The following test fails on master but works with this commit ```go func TestCompaction(t *testing.T) { t.Run("level 0 to level 1", func(t *testing.T) { dir, err := ioutil.TempDir("", "badger-test") require.NoError(t, err) defer removeDir(dir) // Disable compactions and keep single version of each key. opt := DefaultOptions(dir).WithNumCompactors(0).WithNumVersionsToKeep(1) db, err := OpenManaged(opt) require.NoError(t, err) l0 := []keyValVersion{{"foo", "bar", 3}, {"fooz", "baz", 1}} l01 := []keyValVersion{{"foo", "bar", 2}} l1 := []keyValVersion{{"foo", "bar", 1}} // Level 0 has table l0 and l01. createAndOpen(db, l0, 0) createAndOpen(db, l01, 0) // Level 1 has table l1. createAndOpen(db, l1, 1) // Set a high discard timestamp so that all the keys are below the discard timestamp. db.SetDiscardTs(10) getAllAndCheck(t, db, []keyValVersion{ {"foo", "bar", 3}, {"foo", "bar", 2}, {"foo", "bar", 1}, {"fooz", "baz", 1}, }) cdef := compactDef{ thisLevel: db.lc.levels[0], nextLevel: db.lc.levels[1], top: db.lc.levels[0].tables, bot: db.lc.levels[1].tables, } require.NoError(t, db.lc.runCompactDef(0, cdef)) // foo version 2 should be dropped after compaction. getAllAndCheck(t, db, []keyValVersion{{"foo", "bar", 3}, {"fooz", "baz", 1}}) }) } ``` (cherry picked from commitd930b60174) * Remove the 'this entry should've caught' log from value.go (#1170) Fixes - https://github.com/dgraph-io/badger/issues/1031 (There wasn't a bug to fix. The log statement shouldn't have been there) This PR removes the warning message `WARNING: This entry should have been caught.`. The warning message assumed that we will always find the **newest key if two keys have the same version** This assumption is valid in case of a normal key but it's **NOT TRUE** in case of **move keys**. Here's how we can end up fetching the older version of a move key if two move keys have the same version. ``` It might be possible that the entry read from LSM Tree points to an older vlog file. This can happen in the following situation. Assume DB is opened with numberOfVersionsToKeep=1 Now, if we have ONLY one key in the system "FOO" which has been updated 3 times and the same key has been garbage collected 3 times, we'll have 3 versions of the movekey for the same key "FOO". NOTE: moveKeyi is the moveKey with version i Assume we have 3 move keys in L0. - moveKey1 (points to vlog file 10), - moveKey2 (points to vlog file 14) and - moveKey3 (points to vlog file 15). Also, assume there is another move key "moveKey1" (points to vlog file 6) (this is also a move Key for key "FOO" ) on upper levels (let's say level 3). The move key "moveKey1" on level 0 was inserted because vlog file 6 was GCed. Here's what the arrangement looks like L0 => (moveKey1 => vlog10), (moveKey2 => vlog14), (moveKey3 => vlog15) L1 => .... L2 => .... L3 => (moveKey1 => vlog6) When L0 compaction runs, it keeps only moveKey3 because the number of versions to keep is set to 1. (we've dropped moveKey1's latest version) The new arrangement of keys is L0 => .... L1 => (moveKey3 => vlog15) L2 => .... L3 => (moveKey1 => vlog6) Now if we try to GC vlog file 10, the entry read from vlog file will point to vlog10 but the entry read from LSM Tree will point to vlog6. The move key read from LSM tree will point to vlog6 because we've asked for version 1 of the move key. This might seem like an issue but it's not really an issue because the user has set the number of versions to keep to 1 and the latest version of moveKey points to the correct vlog file and offset. The stale move key on L3 will be eventually dropped by compaction because there is a newer version in the upper levels. ``` (cherry picked from commit9b82bb4077) * Avoid sync in inmemory mode (#1190) This makes db.Sync() no-op when badger is running in in-memory mode. The previous code would unnecessarily load up an atomic and acquire locks. (cherry picked from commitd007d96e8a) * Use fastRand instead of locked-rand in skiplist (#1173) The math/rand package (https://golang.org/src/math/rand/rand.go) uses a global lock to allow concurrent access to the rand object. The PR replaces `math.Rand` with `ristretto/z.FastRand()`. `FastRand` is much faster than `math.Rand` and `rand.New(..)` generators. The change improves concurrent writes to skiplist by ~30% ```go func BenchmarkWrite(b *testing.B) { value := newValue(123) l := NewSkiplist(int64((b.N + 1) * MaxNodeSize)) defer l.DecrRef() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { rng := rand.New(rand.NewSource(time.Now().UnixNano())) for pb.Next() { l.Put(randomKey(rng), y.ValueStruct{Value: value, Meta: 0, UserMeta: 0}) } }) } ``` ``` name old time/op new time/op delta Write-16 657ns ± 3% 441ns ± 1% -32.94% (p=0.000 n=9+10) ``` (cherry picked from commit6ade9fc5c2) * Add Jaegar to list of projects (#1192) (cherry picked from commit8577ab566b) * Run all tests on CI (#1189) This could possibly be a bug in `go test` command https://github.com/golang/go/issues/36527 . The `go test` command would skip tests in sub-packages if the top-level package has a `custom flag` defined and the sub-packages don't define it. Issue https://github.com/golang/go/issues/36527#issue-548887632 has an example of this. This PR also removes the code from the test that would unnecessary start a web server. I see two problems here 1. An unnecessary web server running. 2. We cannot run multiple tests are the same time since the second run of the test would try to start a web server and crash saying `port already in use`. (cherry picked from commitdd7888efdd) * Improve write stalling on level 0 and 1 We don't need to stall writes if Level 1 does not have enough space. Level 1 is stored on the disk and it should be okay to have more number of tables (more size) on Level 1 than the `max level 1 size`. These tables will eventually be compacted to lower levels. This commit changes the following - We no longer stall writes if L1 doesn't have enough space. - We stall writes on level 0 only if `KeepL0InMemory` is true. - Upper levels (L0, L1, etc) get priority in compaction (previously, level with higher priority score would get preference) (cherry picked from commit22bd329d25) * Update ristretto to version 8f368f2 (#1195) This commit pulls following changes from ristretto ``` git log c1f00be0418e...8f368f2 --oneline ``` ``` 8f368f2 (HEAD -> master) Fix DeepSource warnings adb35f0 delete item immediately fce5e91 Support nil *Cache values in Clear and Close 4e224f9 Add .travis.yml 8350826 Fix the way metrics are handled for deletions 99d1bbb (tag: v0.0.1) default to 128bit hashing for collision checks 1eea1b1 atomic Get-Delete operation for eviction 8d6a8a7 add cache test for identifying MaxCost overflows ae09250 use the custom KeyToHash function if one is set 1dd5a4d #19 Fixes memory leak due to Oct 1st regression in processItems ``` (cherry picked from commit0cef58737f) * Support disabling the cache completely. (#1183) (#1185) The cache can be disabled by setting `opt.MaxCacheSize=0` (cherry picked from commitfae7479694) * Fix L0/L1 stall test (#1201) The test `TestL0Stall` and `TestL1Stall` would never fail because of errors in the manifest file. This commit fixes it. (cherry picked from commit07f48fa3ee) * Disable compression and set ZSTD Compression Level to 1 (#1191) This PR - Disables compression. By default, badger does not use any compression. - Set default ZSTD compression level to 1 Level 15 is very slow for any practical use of badger. ``` no_compression-16 10 502848865 ns/op 165.46 MB/s zstd_compression/level_1-16 7 739037966 ns/op 112.58 MB/s zstd_compression/level_3-16 7 756950250 ns/op 109.91 MB/s zstd_compression/level_15-16 1 11135686219 ns/op 7.47 MB/s ``` (cherry picked from commitd7dcc762d3) * Add support for caching bloomfilters (#1204) This PR adds support for caching bloom filters in ristretto. The bloom filters and blocks are removed from the cache when the table is deleted. (cherry picked from commite7bd7d3839) * Rework concurrency semantics of valueLog.maxFid (#1184) (#1187) Move all access to `valueLog.maxFid` under `valueLog.filesLock`, while all mutations happen either with writes stopped or sequentially under valueLog.write. Fixes a concurrency issue in `valueLog.Read` where the maxFid variable and the `writableLogOffset` variable could point to two different log files. (cherry picked from commita1fb718648) * Change else-if statements to idiomatic switch statements. (#1207) (cherry picked from commitfe42d6af3a) * Fix flaky TestPageBufferReader2 test (#1210) Fixes https://github.com/dgraph-io/badger/issues/1197 The `TestPageBufferReader2` test would fail often because of an `off-by-1` issue. The problem can be reproduced by setting `randOffset` to the biggest number that randInt31n may return statically like: ``` //randOffset := int(rand.Int31n(int32(b.length))) randOffset := int(int32(b.length-1)) ``` This makes the problem reliably reproducible as the offset is now pointing at EOF. Thus changing the line to this should hopefully solve the problem: `randOffset := int(rand.Int31n(int32(b.length-1 (cherry picked from commit134fccb154) * Replace t.Fatal with require.NoError in tests (#1213) We are using the following pattern in tests that can be replaced with `require.NoError(t, err)`. ```go if err != nil { t.Fatal(err) } ``` (cherry picked from commit32e9cc40b6) * Add missing package to README for badger.NewEntry (#1223) (cherry picked from commit7fb88f4c1c) * Remove ExampleDB_Subscribe Test (#1214) The test ExampleDB_Subscribe doesn't run reliably on appveyor. This commit removes it. (cherry picked from commitd704ffef18) * Fix int overflow for 32bit (#1216) - Fix tests for 32 bit systems - Enable 32-bit builds on Travis (cherry picked from commit5cc3caede8) * Update CHANGELOG for Badger 2.0.2 release. (#1230) Co-authored-by: Ibrahim Jarif <[email protected]> (cherry picked from commit4de1f11d13) * Fix changelog for v2.0.2 (cherry picked from commit a795c2dbeb093ed8b31badc39bb45d8e7ebb9b36) Co-authored-by: Christian Stewart <[email protected]> Co-authored-by: Leyla Galatin <[email protected]> Co-authored-by: Damien Tournoud <[email protected]> Co-authored-by: Martin Martinez Rivera <[email protected]> Co-authored-by: Dieter Plaetinck <[email protected]> Co-authored-by: Apoorv Vardhan <[email protected]>
532 lines
15 KiB
Go
532 lines
15 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/v2/y"
|
|
)
|
|
|
|
const arenaSize = 1 << 20
|
|
|
|
func (s *Skiplist) valid() bool { return s.arena != nil }
|
|
|
|
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)
|
|
val5 := []byte(fmt.Sprintf("%0102400d", 1)) // Have size 100 KB which is > math.MaxUint16.
|
|
|
|
// 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)
|
|
|
|
l.Put(y.KeyWithTs([]byte("key4"), 1), y.ValueStruct{Value: val5, Meta: 60, UserMeta: 0})
|
|
v = l.Get(y.KeyWithTs([]byte("key4"), 1))
|
|
require.NotNil(t, v.Value)
|
|
require.EqualValues(t, val5, v.Value)
|
|
require.EqualValues(t, 60, 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))
|
|
}
|
|
|
|
func TestConcurrentBasicBigValues(t *testing.T) {
|
|
const n = 100
|
|
arenaSize := int64(120 << 20) // 120 MB
|
|
l := NewSkiplist(arenaSize)
|
|
var wg sync.WaitGroup
|
|
key := func(i int) []byte {
|
|
return y.KeyWithTs([]byte(fmt.Sprintf("%05d", i)), 0)
|
|
}
|
|
BigValue := func(i int) []byte {
|
|
return []byte(fmt.Sprintf("%01048576d", i)) // Have 1 MB value which is > math.MaxUint16.
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
l.Put(key(i),
|
|
y.ValueStruct{Value: BigValue(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.NotNil(t, v.Value)
|
|
require.EqualValues(t, BigValue(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.AddInt32(&sawValue, 1)
|
|
v, err := strconv.Atoi(string(p.Value))
|
|
require.NoError(t, err)
|
|
require.True(t, 0 <= v && v < n, fmt.Sprintf("invalid value %d", v))
|
|
}()
|
|
}
|
|
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 rng.Float32() < readFrac {
|
|
mutex.RLock()
|
|
_, ok := m[string(randomKey(rng))]
|
|
mutex.RUnlock()
|
|
if ok {
|
|
count++
|
|
}
|
|
} else {
|
|
mutex.Lock()
|
|
m[string(randomKey(rng))] = value
|
|
mutex.Unlock()
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkWrite(b *testing.B) {
|
|
value := newValue(123)
|
|
l := NewSkiplist(int64((b.N + 1) * MaxNodeSize))
|
|
defer l.DecrRef()
|
|
b.ResetTimer()
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
for pb.Next() {
|
|
l.Put(randomKey(rng), y.ValueStruct{Value: value, Meta: 0, UserMeta: 0})
|
|
}
|
|
})
|
|
}
|