| /* |
| * Copyright 2018 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 badger |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io/ioutil" |
| "math/rand" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "github.com/dgraph-io/badger/options" |
| "github.com/dgraph-io/badger/y" |
| "github.com/stretchr/testify/require" |
| ) |
| |
| type tableMock struct { |
| left, right []byte |
| } |
| |
| func (tm *tableMock) Smallest() []byte { return tm.left } |
| func (tm *tableMock) Biggest() []byte { return tm.right } |
| func (tm *tableMock) DoesNotHave(key []byte) bool { return false } |
| |
| func TestPickTables(t *testing.T) { |
| opt := DefaultIteratorOptions |
| |
| within := func(prefix, left, right string) { |
| opt.Prefix = []byte(prefix) |
| tm := &tableMock{left: []byte(left), right: []byte(right)} |
| require.True(t, opt.pickTable(tm)) |
| } |
| outside := func(prefix, left, right string) { |
| opt.Prefix = []byte(prefix) |
| tm := &tableMock{left: []byte(left), right: []byte(right)} |
| require.False(t, opt.pickTable(tm)) |
| } |
| within("abc", "ab", "ad") |
| within("abc", "abc", "ad") |
| within("abc", "abb123", "ad") |
| within("abc", "abc123", "abd234") |
| within("abc", "abc123", "abc456") |
| |
| outside("abd", "abe", "ad") |
| outside("abd", "ac", "ad") |
| outside("abd", "b", "e") |
| outside("abd", "a", "ab") |
| outside("abd", "ab", "abc") |
| outside("abd", "ab", "abc123") |
| } |
| |
| func TestIteratePrefix(t *testing.T) { |
| runBadgerTest(t, nil, func(t *testing.T, db *DB) { |
| bkey := func(i int) []byte { |
| return []byte(fmt.Sprintf("%04d", i)) |
| } |
| val := []byte("OK") |
| n := 10000 |
| |
| batch := db.NewWriteBatch() |
| for i := 0; i < n; i++ { |
| if (i % 1000) == 0 { |
| t.Logf("Put i=%d\n", i) |
| } |
| require.NoError(t, batch.Set(bkey(i), val, 0)) |
| } |
| require.NoError(t, batch.Flush()) |
| |
| countKeys := func(prefix string) int { |
| t.Logf("Testing with prefix: %s", prefix) |
| var count int |
| opt := DefaultIteratorOptions |
| opt.Prefix = []byte(prefix) |
| err := db.View(func(txn *Txn) error { |
| itr := txn.NewIterator(opt) |
| defer itr.Close() |
| for itr.Rewind(); itr.Valid(); itr.Next() { |
| item := itr.Item() |
| err := item.Value(func(v []byte) error { |
| require.Equal(t, val, v) |
| return nil |
| }) |
| require.NoError(t, err) |
| require.True(t, bytes.HasPrefix(item.Key(), opt.Prefix)) |
| count++ |
| } |
| return nil |
| }) |
| require.NoError(t, err) |
| return count |
| } |
| |
| countOneKey := func(key []byte) int { |
| var count int |
| err := db.View(func(txn *Txn) error { |
| itr := txn.NewKeyIterator(key, DefaultIteratorOptions) |
| defer itr.Close() |
| for itr.Rewind(); itr.Valid(); itr.Next() { |
| item := itr.Item() |
| err := item.Value(func(v []byte) error { |
| require.Equal(t, val, v) |
| return nil |
| }) |
| require.NoError(t, err) |
| require.Equal(t, key, item.Key()) |
| count++ |
| } |
| return nil |
| }) |
| require.NoError(t, err) |
| return count |
| } |
| |
| for i := 0; i <= 9; i++ { |
| require.Equal(t, 1, countKeys(fmt.Sprintf("%d%d%d%d", i, i, i, i))) |
| require.Equal(t, 10, countKeys(fmt.Sprintf("%d%d%d", i, i, i))) |
| require.Equal(t, 100, countKeys(fmt.Sprintf("%d%d", i, i))) |
| require.Equal(t, 1000, countKeys(fmt.Sprintf("%d", i))) |
| } |
| require.Equal(t, 10000, countKeys("")) |
| |
| t.Logf("Testing each key with key iterator") |
| for i := 0; i < n; i++ { |
| require.Equal(t, 1, countOneKey(bkey(i))) |
| } |
| }) |
| } |
| |
| // go test -v -run=XXX -bench=BenchmarkIterate -benchtime=3s |
| // Benchmark with opt.Prefix set === |
| // goos: linux |
| // goarch: amd64 |
| // pkg: github.com/dgraph-io/badger |
| // BenchmarkIteratePrefixSingleKey/Key_lookups-4 10000 365539 ns/op |
| // --- BENCH: BenchmarkIteratePrefixSingleKey/Key_lookups-4 |
| // iterator_test.go:147: Inner b.N: 1 |
| // iterator_test.go:147: Inner b.N: 100 |
| // iterator_test.go:147: Inner b.N: 10000 |
| // --- BENCH: BenchmarkIteratePrefixSingleKey |
| // iterator_test.go:143: LSM files: 79 |
| // iterator_test.go:145: Outer b.N: 1 |
| // PASS |
| // ok github.com/dgraph-io/badger 41.586s |
| // |
| // Benchmark with NO opt.Prefix set === |
| // goos: linux |
| // goarch: amd64 |
| // pkg: github.com/dgraph-io/badger |
| // BenchmarkIteratePrefixSingleKey/Key_lookups-4 10000 460924 ns/op |
| // --- BENCH: BenchmarkIteratePrefixSingleKey/Key_lookups-4 |
| // iterator_test.go:147: Inner b.N: 1 |
| // iterator_test.go:147: Inner b.N: 100 |
| // iterator_test.go:147: Inner b.N: 10000 |
| // --- BENCH: BenchmarkIteratePrefixSingleKey |
| // iterator_test.go:143: LSM files: 83 |
| // iterator_test.go:145: Outer b.N: 1 |
| // PASS |
| // ok github.com/dgraph-io/badger 41.836s |
| // |
| // Only my laptop there's a 20% improvement in latency with ~80 files. |
| func BenchmarkIteratePrefixSingleKey(b *testing.B) { |
| dir, err := ioutil.TempDir(".", "badger-test") |
| y.Check(err) |
| defer os.RemoveAll(dir) |
| opts := getTestOptions(dir) |
| opts.TableLoadingMode = options.LoadToRAM |
| db, err := Open(opts) |
| y.Check(err) |
| defer db.Close() |
| |
| N := 100000 // Should generate around 80 SSTables. |
| val := []byte("OK") |
| bkey := func(i int) []byte { |
| return []byte(fmt.Sprintf("%06d", i)) |
| } |
| |
| batch := db.NewWriteBatch() |
| for i := 0; i < N; i++ { |
| y.Check(batch.Set(bkey(i), val, 0)) |
| } |
| y.Check(batch.Flush()) |
| var lsmFiles int |
| err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { |
| if strings.HasSuffix(path, ".sst") { |
| lsmFiles++ |
| } |
| if err != nil { |
| return err |
| } |
| return nil |
| }) |
| y.Check(err) |
| b.Logf("LSM files: %d", lsmFiles) |
| b.Logf("Key splits: %v", db.KeySplits(nil)) |
| b.Logf("Key splits with prefix: %v", db.KeySplits([]byte("09"))) |
| |
| b.Logf("Outer b.N: %d", b.N) |
| b.Run("Key lookups", func(b *testing.B) { |
| b.Logf("Inner b.N: %d", b.N) |
| for i := 0; i < b.N; i++ { |
| key := bkey(rand.Intn(N)) |
| err := db.View(func(txn *Txn) error { |
| opt := DefaultIteratorOptions |
| // NOTE: Comment opt.Prefix out here to compare the performance |
| // difference between providing Prefix as an option, v/s not. I |
| // see a 20% improvement when there are ~80 SSTables. |
| opt.Prefix = key |
| opt.AllVersions = true |
| |
| itr := txn.NewIterator(opt) |
| defer itr.Close() |
| |
| var count int |
| for itr.Seek(key); itr.ValidForPrefix(key); itr.Next() { |
| count++ |
| } |
| if count != 1 { |
| b.Fatalf("Count must be one key: %s. Found: %d", key, count) |
| } |
| return nil |
| }) |
| if err != nil { |
| b.Fatalf("Error while View: %v", err) |
| } |
| } |
| }) |
| } |