Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a BadgerDB backend #115

Merged
merged 7 commits into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ linters:
- gosimple
- govet
- ineffassign
- interfacer
# - interfacer
- lll
- misspell
- maligned
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- [\#115](https://github.com/tendermint/tm-db/pull/115) Add a `BadgerDB` backend (@mvdan)

## 0.6.0

**2020-06-24**
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Go 1.13+

- **[RocksDB](https://github.com/tecbot/gorocksdb) [experimental]:** A [Go wrapper](https://github.com/tecbot/gorocksdb) around [RocksDB](https://rocksdb.org). Similarly to LevelDB (above) it uses LSM-trees for on-disk storage, but is optimized for fast storage media such as SSDs and memory. Supports atomic transactions, but not full ACID transactions.

- **[BadgerDB](https://github.com/dgraph-io/badger) [experimental]:** A key-value database written as a pure-Go alternative to others like LevelDB and RocksDB. Makes use of multiple goroutines for performance, and includes advanced features such as transactions, write batches, compression, and more.

## Meta-databases

- **PrefixDB [stable]:** A database which wraps another database and uses a static prefix for all keys. This allows multiple logical databases to be stored in a common underlying databases by using different namespaces. Used by the Cosmos SDK to give different modules their own namespaced database in a single application database.
Expand Down
16 changes: 16 additions & 0 deletions backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,22 @@ func testDBIterator(t *testing.T, backend BackendType) {
require.NoError(t, err)
verifyIterator(t, ritr,
[]int64(nil), "reverse iterator from 2 (ex) to 4")

// Ensure that the iterators don't panic with an empty database.
dir2, err := ioutil.TempDir("", "tm-db-test")
require.NoError(t, err)
db2, err := NewDB(name, backend, dir2)
require.NoError(t, err)
defer cleanupDBDir(dir2, name)

itr, err = db2.Iterator(nil, nil)
require.NoError(t, err)
verifyIterator(t, itr, nil, "forward iterator with empty db")

ritr, err = db2.ReverseIterator(nil, nil)
require.NoError(t, err)
verifyIterator(t, ritr, nil, "reverse iterator with empty db")

}

func verifyIterator(t *testing.T, itr Iterator, expected []int64, msg string) {
Expand Down
292 changes: 292 additions & 0 deletions badger_db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
package db

import (
"bytes"
"fmt"
"os"
"path/filepath"

"github.com/dgraph-io/badger/v2"
)

func init() { registerDBCreator(BadgerDBBackend, badgerDBCreator, true) }

func badgerDBCreator(dbName, dir string) (DB, error) {
return NewBadgerDB(dbName, dir)
}

// NewBadgerDB creates a Badger key-value store backed to the
// directory dir supplied. If dir does not exist, it will be created.
func NewBadgerDB(dbName, dir string) (*BadgerDB, error) {
// Since Badger doesn't support database names, we join both to obtain
// the final directory to use for the database.
path := filepath.Join(dir, dbName)

if err := os.MkdirAll(path, 0755); err != nil {
return nil, err
}
opts := badger.DefaultOptions(path)
opts.SyncWrites = false // note that we have Sync methods
opts.Logger = nil // badger is too chatty by default
return NewBadgerDBWithOptions(opts)
}

// NewBadgerDBWithOptions creates a BadgerDB key value store
// gives the flexibility of initializing a database with the
// respective options.
func NewBadgerDBWithOptions(opts badger.Options) (*BadgerDB, error) {
db, err := badger.Open(opts)
if err != nil {
return nil, err
}
return &BadgerDB{db: db}, nil
}

type BadgerDB struct {
db *badger.DB
}

var _ DB = (*BadgerDB)(nil)

func (b *BadgerDB) Get(key []byte) ([]byte, error) {
if len(key) == 0 {
return nil, errKeyEmpty
}
var val []byte
err := b.db.View(func(txn *badger.Txn) error {
item, err := txn.Get(key)
if err == badger.ErrKeyNotFound {
return nil
} else if err != nil {
return err
}
val, err = item.ValueCopy(nil)
if err == nil && val == nil {
val = []byte{}
}
return err
})
return val, err
}

func (b *BadgerDB) Has(key []byte) (bool, error) {
if len(key) == 0 {
return false, errKeyEmpty
}
var found bool
err := b.db.View(func(txn *badger.Txn) error {
_, err := txn.Get(key)
if err != nil && err != badger.ErrKeyNotFound {
return err
}
found = (err != badger.ErrKeyNotFound)
return nil
})
return found, err
}

func (b *BadgerDB) Set(key, value []byte) error {
if len(key) == 0 {
return errKeyEmpty
}
if value == nil {
return errValueNil
}
return b.db.Update(func(txn *badger.Txn) error {
return txn.Set(key, value)
})
}

func withSync(db *badger.DB, err error) error {
if err != nil {
return err
}
return db.Sync()
}

func (b *BadgerDB) SetSync(key, value []byte) error {
return withSync(b.db, b.Set(key, value))
}

func (b *BadgerDB) Delete(key []byte) error {
if len(key) == 0 {
return errKeyEmpty
}
return b.db.Update(func(txn *badger.Txn) error {
return txn.Delete(key)
})
}

func (b *BadgerDB) DeleteSync(key []byte) error {
return withSync(b.db, b.Delete(key))
}

func (b *BadgerDB) Close() error {
return b.db.Close()
}

func (b *BadgerDB) Print() error {
return nil
}

func (b *BadgerDB) iteratorOpts(start, end []byte, opts badger.IteratorOptions) (*badgerDBIterator, error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, errKeyEmpty
}
txn := b.db.NewTransaction(false)
iter := 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 &badgerDBIterator{
reverse: opts.Reverse,
start: start,
end: end,

txn: txn,
iter: iter,
}, nil
}

func (b *BadgerDB) Iterator(start, end []byte) (Iterator, error) {
opts := badger.DefaultIteratorOptions
return b.iteratorOpts(start, end, opts)
}

func (b *BadgerDB) ReverseIterator(start, end []byte) (Iterator, error) {
opts := badger.DefaultIteratorOptions
opts.Reverse = true
return b.iteratorOpts(end, start, opts)
}

func (b *BadgerDB) Stats() map[string]string {
return nil
}

func (b *BadgerDB) NewBatch() Batch {
wb := &badgerDBBatch{
db: b.db,
wb: b.db.NewWriteBatch(),
firstFlush: make(chan struct{}, 1),
erikgrinaker marked this conversation as resolved.
Show resolved Hide resolved
}
wb.firstFlush <- struct{}{}
return wb
}

var _ Batch = (*badgerDBBatch)(nil)

type badgerDBBatch struct {
db *badger.DB
wb *badger.WriteBatch

// Calling db.Flush twice panics, so we must keep track of whether we've
// flushed already on our own. If Write can receive from the firstFlush
// channel, then it's the first and only Flush call we should do.
//
// Upstream bug report:
// https://github.com/dgraph-io/badger/issues/1394
firstFlush chan struct{}
}

func (b *badgerDBBatch) Set(key, value []byte) error {
if len(key) == 0 {
return errKeyEmpty
}
if value == nil {
return errValueNil
}
return b.wb.Set(key, value)
}

func (b *badgerDBBatch) Delete(key []byte) error {
if len(key) == 0 {
return errKeyEmpty
}
return b.wb.Delete(key)
}

func (b *badgerDBBatch) Write() error {
select {
case <-b.firstFlush:
return b.wb.Flush()
default:
return fmt.Errorf("batch already flushed")
}
}

func (b *badgerDBBatch) WriteSync() error {
return withSync(b.db, b.Write())
}

func (b *badgerDBBatch) Close() error {
select {
case <-b.firstFlush: // a Flush after Cancel panics too
default:
}
b.wb.Cancel()
return nil
}

type badgerDBIterator struct {
reverse bool
start, end []byte

txn *badger.Txn
iter *badger.Iterator

lastErr error
}

func (i *badgerDBIterator) Close() error {
i.iter.Close()
i.txn.Discard()
return nil
}

func (i *badgerDBIterator) Domain() (start, end []byte) { return i.start, i.end }
func (i *badgerDBIterator) Error() error { return i.lastErr }

func (i *badgerDBIterator) Next() {
if !i.Valid() {
panic("iterator is invalid")
}
i.iter.Next()
}

func (i *badgerDBIterator) 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 *badgerDBIterator) Key() []byte {
if !i.Valid() {
panic("iterator is invalid")
}
// Note that we don't use KeyCopy, so this is only valid until the next
// call to Next.
return i.iter.Item().KeyCopy(nil)
}

func (i *badgerDBIterator) Value() []byte {
if !i.Valid() {
panic("iterator is invalid")
}
val, err := i.iter.Item().ValueCopy(nil)
if err != nil {
i.lastErr = err
}
return val
}
2 changes: 2 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const (
// - requires gcc
// - use rocksdb build tag (go build -tags rocksdb)
RocksDBBackend BackendType = "rocksdb"

BadgerDBBackend BackendType = "badgerdb"
)

type dbCreator func(name string, dir string) (DB, error)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/tendermint/tm-db
go 1.12

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger/v2 v2.0.3
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
Expand Down
Loading