From ae9834e60f4be41a1a7fe67829eb8768bbc1db3e Mon Sep 17 00:00:00 2001 From: Roy Crihfield Date: Wed, 15 Sep 2021 10:58:10 +0800 Subject: [PATCH] feat: ADR-040: Implement BadgerDB backend (#9848) ## Description Partially resolves: https://github.com/vulcanize/cosmos-sdk/issues/14 Implements a [BadgerDB](https://pkg.go.dev/github.com/dgraph-io/badger/v3)-based backend for the DB interface introduced by https://github.com/cosmos/cosmos-sdk/pull/9573 and specified by [ADR-040](https://github.com/cosmos/cosmos-sdk/blob/eb7d939f86c6cd7b4218492364cdda3f649f06b5/docs/architecture/adr-040-storage-and-smt-state-commitments.md). This uses Badger's "managed" mode for version management, and supports optimistically concurrent transactions (required one [patch](https://github.com/dgraph-io/badger/pull/1716) upstream to fix handling of write conflicts). --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - n/a - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) --- CHANGELOG.md | 1 + db/README.md | 25 +++ db/badgerdb/db.go | 407 +++++++++++++++++++++++++++++++++++++++++ db/badgerdb/db_test.go | 36 ++++ db/dbtest/testcases.go | 67 ++++--- db/go.mod | 1 + db/go.sum | 135 +++++++++++++- db/version_manager.go | 14 +- 8 files changed, 650 insertions(+), 36 deletions(-) create mode 100644 db/badgerdb/db.go create mode 100644 db/badgerdb/db_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a7940f3b84d..75fbea1283e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,6 +151,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#8518](https://github.com/cosmos/cosmos-sdk/pull/8518) Help users of multisig wallets debug signature issues. * [\#9573](https://github.com/cosmos/cosmos-sdk/pull/9573) ADR 040 implementation: New DB interface * [\#9952](https://github.com/cosmos/cosmos-sdk/pull/9952) ADR 040: Implement in-memory DB backend +* [\#9848](https://github.com/cosmos/cosmos-sdk/pull/9848) ADR-040: Implement BadgerDB backend ### Client Breaking Changes diff --git a/db/README.md b/db/README.md index 2fda2a85b0c5..ab766bcbbc3c 100644 --- a/db/README.md +++ b/db/README.md @@ -37,3 +37,28 @@ This represents a self-contained and immutable view of a database's version hist The in-memory DB in the `db/memdb` package cannot be persisted to disk. It is implemented using the Google [btree](https://pkg.go.dev/github.com/google/btree) library. * This currently does not perform write conflict detection, so it only supports a single open write-transaction at a time. Multiple and concurrent read-transactions are supported. + +### BadgerDB + +A [BadgerDB](https://pkg.go.dev/github.com/dgraph-io/badger/v3)-based backend. Internally, this uses BadgerDB's ["managed" mode](https://pkg.go.dev/github.com/dgraph-io/badger/v3#OpenManaged) for version management. +Note that Badger only recognizes write conflicts for rows that are read _after_ a conflicting transaction was opened. In other words, the following will raise an error: + +```go +tx1, tx2 := db.Writer(), db.ReadWriter() +key := []byte("key") +tx2.Get(key) +tx1.Set(key, []byte("a")) +tx2.Set(key, []byte("b")) +tx1.Commit() // ok +err := tx2.Commit() // err is non-nil +``` + +But this will not: +```go +tx1, tx2 := db.Writer(), db.ReadWriter() +key := []byte("key") +tx1.Set(key, []byte("a")) +tx2.Set(key, []byte("b")) +tx1.Commit() // ok +tx2.Commit() // ok +``` diff --git a/db/badgerdb/db.go b/db/badgerdb/db.go new file mode 100644 index 000000000000..413553000667 --- /dev/null +++ b/db/badgerdb/db.go @@ -0,0 +1,407 @@ +package badgerdb + +import ( + "bytes" + "encoding/csv" + "math" + "os" + "path/filepath" + "strconv" + "sync" + "sync/atomic" + + dbm "github.com/cosmos/cosmos-sdk/db" + + "github.com/dgraph-io/badger/v3" +) + +var ( + versionsFilename string = "versions.csv" +) + +var ( + _ dbm.DBConnection = (*BadgerDB)(nil) + _ dbm.DBReader = (*badgerTxn)(nil) + _ dbm.DBWriter = (*badgerWriter)(nil) + _ dbm.DBReadWriter = (*badgerWriter)(nil) +) + +// BadgerDB is a connection to a BadgerDB key-value database. +type BadgerDB struct { + db *badger.DB + vmgr *versionManager + mtx sync.RWMutex + openWriters int32 +} + +type badgerTxn struct { + txn *badger.Txn + db *BadgerDB +} + +type badgerWriter struct { + badgerTxn +} + +type badgerIterator struct { + reverse bool + start, end []byte + iter *badger.Iterator + lastErr error + primed bool +} + +// Map our versions to Badger timestamps. +// +// A badger Txn's commit TS must be strictly greater than a record's "last-read" +// TS in order to detect conflicts, and a Txn must be read at a TS after last +// commit to see current state. So we must use commit increments that are more +// granular than our version interval, and map versions to the corresponding TS. +type versionManager struct { + *dbm.VersionManager + vmap map[uint64]uint64 + lastTs uint64 +} + +// NewDB creates or loads a BadgerDB key-value database inside the given directory. +// If dir does not exist, it will be created. +func NewDB(dir string) (*BadgerDB, error) { + // Since Badger doesn't support database names, we join both to obtain + // the final directory to use for the database. + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, err + } + opts := badger.DefaultOptions(dir) + opts.SyncWrites = false // note that we have Sync methods + opts.Logger = nil // badger is too chatty by default + return NewDBWithOptions(opts) +} + +// NewDBWithOptions creates a BadgerDB key-value database with the specified Options +// (https://pkg.go.dev/github.com/dgraph-io/badger/v3#Options) +func NewDBWithOptions(opts badger.Options) (*BadgerDB, error) { + db, err := badger.OpenManaged(opts) + if err != nil { + return nil, err + } + vmgr, err := readVersionsFile(filepath.Join(opts.Dir, versionsFilename)) + if err != nil { + return nil, err + } + return &BadgerDB{ + db: db, + vmgr: vmgr, + }, nil +} + +// Load metadata CSV file containing valid versions +func readVersionsFile(path string) (*versionManager, error) { + file, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0644) + if err != nil { + return nil, err + } + defer file.Close() + r := csv.NewReader(file) + r.FieldsPerRecord = 2 + rows, err := r.ReadAll() + if err != nil { + return nil, err + } + var versions []uint64 + vmap := map[uint64]uint64{} + for _, row := range rows { + version, err := strconv.ParseUint(row[0], 10, 64) + if err != nil { + return nil, err + } + ts, err := strconv.ParseUint(row[1], 10, 64) + if err != nil { + return nil, err + } + versions = append(versions, version) + vmap[version] = ts + } + vmgr := dbm.NewVersionManager(versions) + return &versionManager{ + VersionManager: vmgr, + vmap: vmap, + lastTs: vmgr.Last(), + }, nil +} + +// Write version metadata to CSV file +func writeVersionsFile(vm *versionManager, path string) error { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + defer file.Close() + w := csv.NewWriter(file) + var rows [][]string + for it := vm.Iterator(); it.Next(); { + version := it.Value() + ts, ok := vm.vmap[version] + if !ok { + panic("version not mapped to ts") + } + rows = append(rows, []string{ + strconv.FormatUint(it.Value(), 10), + strconv.FormatUint(ts, 10), + }) + } + return w.WriteAll(rows) +} + +func (b *BadgerDB) Reader() dbm.DBReader { + return &badgerTxn{txn: b.db.NewTransactionAt(math.MaxUint64, false), db: b} +} + +func (b *BadgerDB) ReaderAt(version uint64) (dbm.DBReader, error) { + b.mtx.RLock() + defer b.mtx.RUnlock() + if !b.vmgr.Exists(version) { + return nil, dbm.ErrVersionDoesNotExist + } + return &badgerTxn{txn: b.db.NewTransactionAt(b.vmgr.versionTs(version), false), db: b}, nil +} + +func (b *BadgerDB) ReadWriter() dbm.DBReadWriter { + atomic.AddInt32(&b.openWriters, 1) + b.mtx.RLock() + ts := b.vmgr.lastCommitTs() + b.mtx.RUnlock() + return &badgerWriter{badgerTxn{txn: b.db.NewTransactionAt(ts, true), db: b}} +} + +func (b *BadgerDB) Writer() dbm.DBWriter { + // Badger has a WriteBatch, but it doesn't support conflict detection + return b.ReadWriter() +} + +func (b *BadgerDB) Close() error { + b.mtx.Lock() + defer b.mtx.Unlock() + writeVersionsFile(b.vmgr, filepath.Join(b.db.Opts().Dir, versionsFilename)) + return b.db.Close() +} + +// Versions implements DBConnection. +// Returns a VersionSet that is valid until the next call to SaveVersion or DeleteVersion. +func (b *BadgerDB) Versions() (dbm.VersionSet, error) { + b.mtx.RLock() + defer b.mtx.RUnlock() + return b.vmgr, nil +} + +func (b *BadgerDB) save(target uint64) (uint64, error) { + b.mtx.Lock() + defer b.mtx.Unlock() + if b.openWriters > 0 { + return 0, dbm.ErrOpenTransactions + } + b.vmgr = b.vmgr.Copy() + return b.vmgr.Save(target) +} + +// SaveVersion implements DBConnection. +func (b *BadgerDB) SaveNextVersion() (uint64, error) { + return b.save(0) +} + +// SaveNextVersion implements DBConnection. +func (b *BadgerDB) SaveVersion(target uint64) error { + if target == 0 { + return dbm.ErrInvalidVersion + } + _, err := b.save(target) + return err +} + +func (b *BadgerDB) DeleteVersion(target uint64) error { + b.mtx.Lock() + defer b.mtx.Unlock() + if !b.vmgr.Exists(target) { + return dbm.ErrVersionDoesNotExist + } + b.vmgr = b.vmgr.Copy() + b.vmgr.Delete(target) + return nil +} + +func (b *BadgerDB) Stats() map[string]string { return nil } + +func (tx *badgerTxn) Get(key []byte) ([]byte, error) { + if len(key) == 0 { + return nil, dbm.ErrKeyEmpty + } + + item, err := tx.txn.Get(key) + if err == badger.ErrKeyNotFound { + return nil, nil + } else if err != nil { + return nil, err + } + val, err := item.ValueCopy(nil) + if err == nil && val == nil { + val = []byte{} + } + return val, err +} + +func (tx *badgerTxn) Has(key []byte) (bool, error) { + if len(key) == 0 { + return false, dbm.ErrKeyEmpty + } + + _, err := tx.txn.Get(key) + if err != nil && err != badger.ErrKeyNotFound { + return false, err + } + return (err != badger.ErrKeyNotFound), nil +} + +func (tx *badgerWriter) Set(key, value []byte) error { + if len(key) == 0 { + return dbm.ErrKeyEmpty + } + if value == nil { + return dbm.ErrValueNil + } + return tx.txn.Set(key, value) +} + +func (tx *badgerWriter) Delete(key []byte) error { + if len(key) == 0 { + return dbm.ErrKeyEmpty + } + return tx.txn.Delete(key) +} + +func (tx *badgerWriter) Commit() error { + // Commit to the current commit TS, after ensuring it is > ReadTs + tx.db.vmgr.updateCommitTs(tx.txn.ReadTs()) + defer tx.Discard() + return tx.txn.CommitAt(tx.db.vmgr.lastCommitTs(), nil) +} + +func (tx *badgerTxn) Discard() { + tx.txn.Discard() +} + +func (tx *badgerWriter) Discard() { + defer atomic.AddInt32(&tx.db.openWriters, -1) + tx.badgerTxn.Discard() +} + +func (tx *badgerTxn) iteratorOpts(start, end []byte, opts badger.IteratorOptions) (*badgerIterator, error) { + if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { + return nil, dbm.ErrKeyEmpty + } + iter := tx.txn.NewIterator(opts) + iter.Rewind() + iter.Seek(start) + if opts.Reverse && iter.Valid() && bytes.Equal(iter.Item().Key(), start) { + // If we're going in reverse, our starting point was "end", which is exclusive. + iter.Next() + } + return &badgerIterator{ + reverse: opts.Reverse, + start: start, + end: end, + iter: iter, + primed: false, + }, nil +} + +func (tx *badgerTxn) Iterator(start, end []byte) (dbm.Iterator, error) { + opts := badger.DefaultIteratorOptions + return tx.iteratorOpts(start, end, opts) +} + +func (tx *badgerTxn) ReverseIterator(start, end []byte) (dbm.Iterator, error) { + opts := badger.DefaultIteratorOptions + opts.Reverse = true + return tx.iteratorOpts(end, start, opts) +} + +func (i *badgerIterator) Close() error { + i.iter.Close() + return nil +} + +func (i *badgerIterator) Domain() (start, end []byte) { return i.start, i.end } +func (i *badgerIterator) Error() error { return i.lastErr } + +func (i *badgerIterator) Next() bool { + if !i.primed { + i.primed = true + } else { + i.iter.Next() + } + return i.Valid() +} + +func (i *badgerIterator) Valid() bool { + if !i.iter.Valid() { + return false + } + if len(i.end) > 0 { + key := i.iter.Item().Key() + if c := bytes.Compare(key, i.end); (!i.reverse && c >= 0) || (i.reverse && c < 0) { + // We're at the end key, or past the end. + return false + } + } + return true +} + +func (i *badgerIterator) Key() []byte { + if !i.Valid() { + panic("iterator is invalid") + } + return i.iter.Item().KeyCopy(nil) +} + +func (i *badgerIterator) Value() []byte { + if !i.Valid() { + panic("iterator is invalid") + } + val, err := i.iter.Item().ValueCopy(nil) + if err != nil { + i.lastErr = err + } + return val +} + +func (vm *versionManager) versionTs(ver uint64) uint64 { + return vm.vmap[ver] +} + +// Atomically accesses the last commit timestamp used as a version marker. +func (vm *versionManager) lastCommitTs() uint64 { + return atomic.LoadUint64(&vm.lastTs) +} +func (vm *versionManager) Copy() *versionManager { + vmap := map[uint64]uint64{} + for ver, ts := range vm.vmap { + vmap[ver] = ts + } + return &versionManager{ + VersionManager: vm.VersionManager.Copy(), + vmap: vmap, + lastTs: vm.lastCommitTs(), + } +} + +// updateCommitTs atomically increments the lastTs if equal to readts. +func (vm *versionManager) updateCommitTs(readts uint64) { + atomic.CompareAndSwapUint64(&vm.lastTs, readts, readts+1) +} +func (vm *versionManager) Save(target uint64) (uint64, error) { + id, err := vm.VersionManager.Save(target) + if err != nil { + return 0, err + } + vm.vmap[id] = vm.lastTs // non-atomic, already guarded by the vmgr mutex + return id, nil +} diff --git a/db/badgerdb/db_test.go b/db/badgerdb/db_test.go new file mode 100644 index 000000000000..d451a63f4334 --- /dev/null +++ b/db/badgerdb/db_test.go @@ -0,0 +1,36 @@ +package badgerdb + +import ( + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/cosmos/cosmos-sdk/db" + "github.com/cosmos/cosmos-sdk/db/dbtest" +) + +func load(t *testing.T, dir string) dbm.DBConnection { + db, err := NewDB(dir) + require.NoError(t, err) + return db +} + +func TestGetSetHasDelete(t *testing.T) { + dbtest.DoTestGetSetHasDelete(t, load) +} + +func TestIterators(t *testing.T) { + dbtest.DoTestIterators(t, load) +} + +func TestTransactions(t *testing.T) { + dbtest.DoTestTransactions(t, load, true) +} + +func TestVersioning(t *testing.T) { + dbtest.DoTestVersioning(t, load) +} + +func TestReloadDB(t *testing.T) { + dbtest.DoTestReloadDB(t, load) +} diff --git a/db/dbtest/testcases.go b/db/dbtest/testcases.go index 475950111d83..dedd0409229e 100644 --- a/db/dbtest/testcases.go +++ b/db/dbtest/testcases.go @@ -369,16 +369,19 @@ func DoTestReloadDB(t *testing.T, load Loader) { dirname := t.TempDir() db := load(t, dirname) - txn := db.Writer() - for i := 0; i < 100; i++ { + var firstVersions []uint64 + + for i := 0; i < 10; i++ { + txn := db.Writer() require.NoError(t, txn.Set(ikey(i), ival(i))) + require.NoError(t, txn.Commit()) + ver, err := db.SaveNextVersion() + require.NoError(t, err) + firstVersions = append(firstVersions, ver) } - require.NoError(t, txn.Commit()) - first, err := db.SaveNextVersion() - require.NoError(t, err) - txn = db.Writer() - for i := 0; i < 50; i++ { // overwrite some values + txn := db.Writer() + for i := 0; i < 5; i++ { // overwrite some values require.NoError(t, txn.Set(ikey(i), ival(i*10))) } require.NoError(t, txn.Commit()) @@ -386,45 +389,51 @@ func DoTestReloadDB(t *testing.T, load Loader) { require.NoError(t, err) txn = db.Writer() - for i := 100; i < 150; i++ { - require.NoError(t, txn.Set(ikey(i), ival(i))) - } + require.NoError(t, txn.Set(ikey(100), ival(100))) require.NoError(t, txn.Commit()) - db.Close() // Reload and check each saved version + db.Close() db = load(t, dirname) - view, err := db.ReaderAt(first) + // require.True(t, db.Versions().Equal(versions)) + vset, err := db.Versions() require.NoError(t, err) - for i := 0; i < 100; i++ { - v, err := view.Get(ikey(i)) - require.NoError(t, err) - require.Equal(t, ival(i), v) + require.Equal(t, last, vset.Last()) + + txn = db.Writer() + for i := 10; i < 15; i++ { + require.NoError(t, txn.Set(ikey(i), ival(i+10))) } - view.Discard() + require.NoError(t, txn.Commit()) - view, err = db.ReaderAt(last) - require.NoError(t, err) - for i := 0; i < 50; i++ { - v, err := view.Get(ikey(i)) + for i := 0; i < 10; i++ { + view, err := db.ReaderAt(firstVersions[i]) require.NoError(t, err) - require.Equal(t, ival(i*10), v) + val, err := view.Get(ikey(i)) + require.NoError(t, err) + require.Equal(t, ival(i), val) + view.Discard() } - for i := 50; i < 100; i++ { + + view, err := db.ReaderAt(last) + require.NoError(t, err) + for i := 0; i < 10; i++ { v, err := view.Get(ikey(i)) require.NoError(t, err) - require.Equal(t, ival(i), v) + if i < 5 { + require.Equal(t, ival(i*10), v) + } else { + require.Equal(t, ival(i), v) + } } view.Discard() // Load working version view = db.Reader() - for i := 100; i < 150; i++ { - v, err := view.Get(ikey(i)) - require.NoError(t, err) - require.Equal(t, ival(i), v) - } + val, err := view.Get(ikey(100)) + require.NoError(t, err) + require.Equal(t, ival(100), val) view.Discard() require.NoError(t, db.Close()) diff --git a/db/go.mod b/db/go.mod index 0d75f20d239b..c06b81730f12 100644 --- a/db/go.mod +++ b/db/go.mod @@ -3,6 +3,7 @@ go 1.15 module github.com/cosmos/cosmos-sdk/db require ( + github.com/dgraph-io/badger/v3 v3.2103.1 github.com/google/btree v1.0.0 github.com/stretchr/testify v1.7.0 ) diff --git a/db/go.sum b/db/go.sum index c7801a435648..5033ed4839c9 100644 --- a/db/go.sum +++ b/db/go.sum @@ -1,13 +1,144 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v3 v3.2103.1 h1:zaX53IRg7ycxVlkd5pYdCeFp1FynD6qBGQoQql3R3Hk= +github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.12.0 h1:/PtAHvnBY4Kqnx/xCQ3OIV9uYcSFGScBsWI3Oogeh6w= +github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/db/version_manager.go b/db/version_manager.go index 55d88e97a2f1..1c04d2297ee8 100644 --- a/db/version_manager.go +++ b/db/version_manager.go @@ -2,6 +2,7 @@ package db import ( "fmt" + "math" ) // VersionManager encapsulates the current valid versions of a DB and computes @@ -13,16 +14,19 @@ type VersionManager struct { var _ VersionSet = (*VersionManager)(nil) -// NewVersionManager creates a VersionManager from a sorted slice of ascending version ids. +// NewVersionManager creates a VersionManager from a slice of version ids. func NewVersionManager(versions []uint64) *VersionManager { vmap := make(map[uint64]struct{}) var init, last uint64 + init = math.MaxUint64 for _, ver := range versions { vmap[ver] = struct{}{} - } - if len(versions) > 0 { - init = versions[0] - last = versions[len(versions)-1] + if ver < init { + init = ver + } + if ver > last { + last = ver + } } return &VersionManager{versions: vmap, initial: init, last: last} }