Skip to content

Commit 326772e

Browse files
committed
consolidate PrefixUpperbound
1 parent 90a2426 commit 326772e

File tree

10 files changed

+57
-92
lines changed

10 files changed

+57
-92
lines changed

storage/batch.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import (
44
"github.com/dgraph-io/badger/v2"
55
)
66

7-
// deprecated
8-
// use Writer instead
7+
// Deprecated: Transaction is being deprecated as part of the transition from Badger to Pebble.
8+
// Use Writer instead of Transaction for all new code.
99
type Transaction interface {
1010
Set(key, val []byte) error
1111
}
1212

13-
// deprecated
14-
// use ReaderBatchWriter instead
1513
// BatchStorage serves as an abstraction over batch storage, adding ability to add ability to add extra
1614
// callbacks which fire after the batch is successfully flushed.
15+
// Deprecated: BatchStorage is being deprecated as part of the transition from Badger to Pebble.
16+
// Use ReaderBatchWriter instead of BatchStorage for all new code.
1717
type BatchStorage interface {
1818
GetWriter() *badger.WriteBatch
1919

storage/operation/badgerimpl/iterator.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func newBadgerIterator(db *badger.DB, startPrefix, endPrefix []byte, ops storage
3434
}
3535
}
3636

37-
func (i *badgerIterator) SeekGE() {
37+
func (i *badgerIterator) First() {
3838
i.iter.Seek(i.lowerBound)
3939
}
4040

@@ -44,7 +44,7 @@ func (i *badgerIterator) Valid() bool {
4444
return false
4545
}
4646
key := i.iter.Item().Key()
47-
// "< 0" means the upperBound is exclusive
47+
// "< 0" means "key < upperBound"
4848
valid := bytes.Compare(key, i.upperBound) < 0
4949
return valid
5050
}

storage/operation/badgerimpl/reader.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ func (b dbReader) Get(key []byte) ([]byte, io.Closer, error) {
3232
return nil, nil, irrecoverable.NewExceptionf("could not load data: %w", err)
3333
}
3434

35-
var value []byte
36-
err = item.Value(func(val []byte) error {
37-
value = append([]byte{}, val...)
38-
return nil
39-
})
35+
value, err := item.ValueCopy(nil)
4036
if err != nil {
4137
return nil, nil, irrecoverable.NewExceptionf("could not load value: %w", err)
4238
}

storage/operation/pebbleimpl/iterator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func newPebbleIterator(reader pebble.Reader, startPrefix, endPrefix []byte, ops
3232
}, nil
3333
}
3434

35-
func (i *pebbleIterator) SeekGE() {
35+
func (i *pebbleIterator) First() {
3636
i.iter.First()
3737
}
3838

storage/operation/pebbleimpl/writer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (b *ReaderBatchWriter) Delete(key []byte) error {
7474
return b.batch.Delete(key, pebble.Sync)
7575
}
7676

77-
// DeleteByRange deletes all keys with the given prefix defined by [startPrefix, endPrefix] (both inclusive).
77+
// DeleteByRange deletes all keys with a prefix in the range [startPrefix, endPrefix] (both inclusive).
7878
func (b *ReaderBatchWriter) DeleteByRange(_ storage.Reader, startPrefix, endPrefix []byte) error {
7979
// DeleteRange takes the prefix range with start (inclusive) and end (exclusive, note: not inclusive).
8080
// therefore, we need to increment the endPrefix to make it inclusive.

storage/operation/reads.go

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type CreateFunc func() interface{}
2828
type HandleFunc func() error
2929
type IterationFunc func() (CheckFunc, CreateFunc, HandleFunc)
3030

31-
// IterateKeysInPrefixRange will iterate over all keys in the given range [startPrefix, endPrefix] (both inclusive)
31+
// IterateKeysInPrefixRange will iterate over all keys with prefixes in the range [startPrefix, endPrefix] (both inclusive)
3232
func IterateKeysInPrefixRange(startPrefix []byte, endPrefix []byte, check func(key []byte) error) func(storage.Reader) error {
3333
return Iterate(startPrefix, endPrefix, func() (CheckFunc, CreateFunc, HandleFunc) {
3434
return func(key []byte) (bool, error) {
@@ -41,7 +41,7 @@ func IterateKeysInPrefixRange(startPrefix []byte, endPrefix []byte, check func(k
4141
}, storage.IteratorOption{IterateKeyOnly: true})
4242
}
4343

44-
// Iterate will iterate over all keys in the given range [startPrefix, endPrefix] (both inclusive)
44+
// Iterate will iterate over all keys with prefixes in the given range [startPrefix, endPrefix] (both inclusive)
4545
func Iterate(startPrefix []byte, endPrefix []byte, iterFunc IterationFunc, opt storage.IteratorOption) func(storage.Reader) error {
4646
return func(r storage.Reader) error {
4747

@@ -64,14 +64,17 @@ func Iterate(startPrefix []byte, endPrefix []byte, iterFunc IterationFunc, opt s
6464
}
6565
defer it.Close()
6666

67-
for it.SeekGE(); it.Valid(); it.Next() {
67+
for it.First(); it.Valid(); it.Next() {
6868
item := it.IterItem()
6969
key := item.Key()
7070

7171
// initialize processing functions for iteration
7272
check, create, handle := iterFunc()
7373

7474
keyCopy := make([]byte, len(key))
75+
76+
// The underlying database may re-use and modify the backing memory of the returned key.
77+
// Tor safety we proactively make a copy before passing the key to the upper layer.
7578
copy(keyCopy, key)
7679

7780
// check if we should process the item at all
@@ -112,25 +115,7 @@ func Iterate(startPrefix []byte, endPrefix []byte, iterFunc IterationFunc, opt s
112115

113116
// Traverse will iterate over all keys with the given prefix
114117
func Traverse(prefix []byte, iterFunc IterationFunc, opt storage.IteratorOption) func(storage.Reader) error {
115-
return Iterate(prefix, PrefixUpperBound(prefix), iterFunc, opt)
116-
}
117-
118-
// PrefixUpperBound returns a key K such that all possible keys beginning with the input prefix
119-
// sort lower than K according to the byte-wise lexicographic key ordering used by Pebble.
120-
// This is used to define an upper bound for iteration, when we want to iterate over
121-
// all keys beginning with a given prefix.
122-
// referred to https://pkg.go.dev/github.com/cockroachdb/pebble#example-Iterator-PrefixIteration
123-
func PrefixUpperBound(prefix []byte) []byte {
124-
end := make([]byte, len(prefix))
125-
copy(end, prefix)
126-
for i := len(end) - 1; i >= 0; i-- {
127-
// increment the bytes by 1
128-
end[i] = end[i] + 1
129-
if end[i] != 0 {
130-
return end[:i+1]
131-
}
132-
}
133-
return nil // no upper-bound
118+
return Iterate(prefix, prefix, iterFunc, opt)
134119
}
135120

136121
// Exists returns true if a key exists in the database.
@@ -155,7 +140,7 @@ func Exists(key []byte, keyExists *bool) func(storage.Reader) error {
155140
}
156141
}
157142

158-
// retrieve will retrieve the binary data under the given key from the badger DB
143+
// Retrieve will retrieve the binary data under the given key from the database
159144
// and decode it into the given entity. The provided entity needs to be a
160145
// pointer to an initialized entity of the correct type.
161146
// Error returns:
@@ -196,7 +181,7 @@ func FindHighestAtOrBelow(prefix []byte, height uint64, entity interface{}) func
196181

197182
var highestKey []byte
198183
// find highest value below the given height
199-
for it.SeekGE(); it.Valid(); it.Next() {
184+
for it.First(); it.Valid(); it.Next() {
200185
highestKey = it.IterItem().Key()
201186
}
202187

storage/operation/reads_test.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ func TestIterateKeysInPrefixRange(t *testing.T) {
3535
{0x21, 0x00},
3636
}
3737

38-
// Keys expected to be in the prefix range
39-
lastNToExclude := 1
40-
keysInRange := keys[1 : len(keys)-lastNToExclude] // these keys are between the start and end
38+
// The first and last keys are outside the prefix range, so we omit them
39+
// from keysInRange, which is the set of keys we expect in the iteration
40+
keysInRange := keys[1 : len(keys)-1]
4141

4242
// Insert the keys into the storage
4343
require.NoError(t, withWriter(func(writer storage.Writer) error {
@@ -63,28 +63,29 @@ func TestIterateKeysInPrefixRange(t *testing.T) {
6363

6464
func TestTraverse(t *testing.T) {
6565
dbtest.RunWithStorages(t, func(t *testing.T, r storage.Reader, withWriter dbtest.WithWriter) {
66-
keys := [][]byte{
67-
{0x42, 0x00},
68-
{0xff},
69-
{0x42, 0x56},
70-
{0x00},
71-
{0x42, 0xff},
66+
keyVals := map[[2]byte]uint64{
67+
{0x41, 0xff}: 3,
68+
{0x42, 0x00}: 11,
69+
{0xff}: 13,
70+
{0x42, 0x56}: 17,
71+
{0x00}: 19,
72+
{0x42, 0xff}: 23,
73+
{0x43, 0x00}: 33,
7274
}
73-
vals := []uint64{11, 13, 17, 19, 23}
7475
expected := []uint64{11, 23}
7576

7677
// Insert the keys and values into storage
7778
require.NoError(t, withWriter(func(writer storage.Writer) error {
78-
for i, key := range keys {
79-
err := operation.Upsert(key, vals[i])(writer)
79+
for key, val := range keyVals {
80+
err := operation.Upsert(key[:], val)(writer)
8081
if err != nil {
8182
return err
8283
}
8384
}
8485
return nil
8586
}))
8687

87-
actual := make([]uint64, 0, len(keys))
88+
actual := make([]uint64, 0, len(keyVals))
8889

8990
// Define the iteration logic
9091
iterationFunc := func() (operation.CheckFunc, operation.CreateFunc, operation.HandleFunc) {

storage/operation/writes_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/onflow/flow-go/storage"
1313
"github.com/onflow/flow-go/storage/operation"
1414
"github.com/onflow/flow-go/storage/operation/dbtest"
15-
"github.com/onflow/flow-go/utils/unittest"
1615
)
1716

1817
func TestReadWrite(t *testing.T) {
@@ -230,16 +229,15 @@ func TestRemoveRange(t *testing.T) {
230229
// Remove the keys in the prefix range
231230
require.NoError(t, withWriter(operation.RemoveByPrefix(r, prefix)))
232231

233-
lg := unittest.Logger().With().Logger()
234232
// Verify that the keys in the prefix range have been removed
235233
for i, key := range keys {
236234
var exists bool
237235
require.NoError(t, operation.Exists(key, &exists)(r))
238-
lg.Info().Msgf("key %x exists: %t", key, exists)
236+
t.Logf("key %x exists: %t", key, exists)
239237

240238
deleted := includeStart <= i && i <= includeEnd
241239

242-
// deleted item should not exist
240+
// An item that was not deleted must exist
243241
require.Equal(t, !deleted, exists,
244242
"expected key %x to be %s", key, map[bool]string{true: "deleted", false: "not deleted"})
245243
}

storage/operations.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ import (
66

77
// Iterator is an interface for iterating over key-value pairs in a storage backend.
88
type Iterator interface {
9-
// SeekGE seeks to the smallest key greater than or equal to the given key.
10-
SeekGE()
9+
// First seeks to the smallest key greater than or equal to the given key.
10+
First()
1111

1212
// Valid returns whether the iterator is positioned at a valid key-value pair.
1313
Valid() bool
1414

1515
// Next advances the iterator to the next key-value pair.
1616
Next()
1717

18-
// Key returns the key of the current key-value pair, or nil if done.
18+
// IterItem returns the current key-value pair, or nil if done.
1919
IterItem() IterItem
2020

2121
// Close closes the iterator. Iterator must be closed, otherwise it causes memory leak.
22+
// No errors expected during normal operation
2223
Close() error
2324
}
2425

@@ -28,6 +29,7 @@ type IterItem interface {
2829

2930
// Value returns the value of the current key-value pair
3031
// The reason it takes a function is to follow badgerDB's API pattern
32+
// No errors expected during normal operation
3133
Value(func(val []byte) error) error
3234
}
3335

@@ -44,14 +46,19 @@ func DefaultIteratorOptions() IteratorOption {
4446
type Reader interface {
4547
// Get gets the value for the given key. It returns ErrNotFound if the DB
4648
// does not contain the key.
49+
// other errors are exceptions
4750
//
4851
// The caller should not modify the contents of the returned slice, but it is
4952
// safe to modify the contents of the argument after Get returns. The
5053
// returned slice will remain valid until the returned Closer is closed. On
5154
// success, the caller MUST call closer.Close() or a memory leak will occur.
5255
Get(key []byte) (value []byte, closer io.Closer, err error)
5356

54-
// NewIter returns a new Iterator for the given key range [startPrefix, endPrefix], both inclusive.
57+
// NewIter returns a new Iterator for the given key prefix range [startPrefix, endPrefix], both inclusive.
58+
// Specifically, all keys that meet ANY of the following conditions are included in the iteration:
59+
// - have a prefix equal to startPrefix OR
60+
// - have a prefix equal to the endPrefix OR
61+
// - have a prefix that is lexicographically between startPrefix and endPrefix
5562
NewIter(startPrefix, endPrefix []byte, ops IteratorOption) (Iterator, error)
5663
}
5764

@@ -61,20 +68,26 @@ type Writer interface {
6168
// for that key; a DB is not a multi-map.
6269
//
6370
// It is safe to modify the contents of the arguments after Set returns.
71+
// No errors expected during normal operation
6472
Set(k, v []byte) error
6573

6674
// Delete deletes the value for the given key. Deletes are blind all will
6775
// succeed even if the given key does not exist.
6876
//
6977
// It is safe to modify the contents of the arguments after Delete returns.
78+
// No errors expected during normal operation
7079
Delete(key []byte) error
7180

7281
// DeleteByRange removes all keys with a prefix that falls within the
7382
// range [start, end], both inclusive.
83+
// No errors expected during normal operation
7484
DeleteByRange(globalReader Reader, startPrefix, endPrefix []byte) error
7585
}
7686

7787
// ReaderBatchWriter is an interface for reading and writing to a storage backend.
88+
// It is useful for performing a related sequence of reads and writes, after which you would like
89+
// to modify some non-database state if the sequence completed successfully (via AddCallback).
90+
// If you are not using AddCallback, avoid using ReaderBatchWriter: use Reader and Writer directly.
7891
type ReaderBatchWriter interface {
7992
// GlobalReader returns a database-backed reader which reads the latest committed global database state ("read-committed isolation").
8093
// This reader will not read writes written to ReaderBatchWriter.Writer until the write batch is committed.
@@ -104,21 +117,21 @@ func OnCommitSucceed(b ReaderBatchWriter, onSuccessFn func()) {
104117
}
105118

106119
func StartEndPrefixToLowerUpperBound(startPrefix, endPrefix []byte) (lowerBound, upperBound []byte) {
107-
// LowerBound specifies the smallest key to iterate and it's inclusive.
108-
// UpperBound specifies the largest key to iterate and it's exclusive (not inclusive)
109-
// in order to match all keys prefixed with the `end` bytes, we increment the bytes of end by 1,
120+
// Return value lowerBound specifies the smallest key to iterate and it's inclusive.
121+
// Return value upperBound specifies the largest key to iterate and it's exclusive (not inclusive)
122+
// in order to match all keys prefixed with `endPrefix`, we increment the bytes of `endPrefix` by 1,
110123
// for instance, to iterate keys between "hello" and "world",
111124
// we use "hello" as LowerBound, "worle" as UpperBound, so that "world", "world1", "worldffff...ffff"
112125
// will all be included.
113-
return startPrefix, prefixUpperBound(endPrefix)
126+
return startPrefix, PrefixUpperBound(endPrefix)
114127
}
115128

116-
// prefixUpperBound returns a key K such that all possible keys beginning with the input prefix
129+
// PrefixUpperBound returns a key K such that all possible keys beginning with the input prefix
117130
// sort lower than K according to the byte-wise lexicographic key ordering used by Pebble.
118131
// This is used to define an upper bound for iteration, when we want to iterate over
119132
// all keys beginning with a given prefix.
120133
// referred to https://pkg.go.dev/github.com/cockroachdb/pebble#example-Iterator-PrefixIteration
121-
func prefixUpperBound(prefix []byte) []byte {
134+
func PrefixUpperBound(prefix []byte) []byte {
122135
end := make([]byte, len(prefix))
123136
copy(end, prefix)
124137
for i := len(end) - 1; i >= 0; i-- {

utils/unittest/unittest.go

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -408,34 +408,6 @@ func TypedPebbleDB(t testing.TB, dir string, create func(string, *pebble.Options
408408
return db
409409
}
410410

411-
type PebbleWrapper struct {
412-
db *pebble.DB
413-
}
414-
415-
func (p *PebbleWrapper) View(fn func(pebble.Reader) error) error {
416-
return fn(p.db)
417-
}
418-
419-
func (p *PebbleWrapper) Update(fn func(pebble.Writer) error) error {
420-
return fn(p.db)
421-
}
422-
423-
func (p *PebbleWrapper) DB() *pebble.DB {
424-
return p.db
425-
}
426-
427-
func RunWithWrappedPebbleDB(t testing.TB, f func(p *PebbleWrapper)) {
428-
RunWithTempDir(t, func(dir string) {
429-
db, err := pebble.Open(dir, &pebble.Options{})
430-
require.NoError(t, err)
431-
defer func() {
432-
assert.NoError(t, db.Close())
433-
}()
434-
f(&PebbleWrapper{db})
435-
})
436-
437-
}
438-
439411
func RunWithTypedPebbleDB(
440412
t testing.TB,
441413
create func(string, *pebble.Options) (*pebble.DB, error),

0 commit comments

Comments
 (0)