Last active
January 22, 2023 21:03
-
-
Save alexanderbez/d99fd0383ad57e991e9af9adcbb70b9d to your computer and use it in GitHub Desktop.
An example wrapper around BadgerDB providing a simple embedded key/value store interface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ( | |
"context" | |
"fmt" | |
"os" | |
"time" | |
"github.com/dgraph-io/badger" | |
) | |
const ( | |
// Default BadgerDB discardRatio. It represents the discard ratio for the | |
// BadgerDB GC. | |
// | |
// Ref: https://godoc.org/github.com/dgraph-io/badger#DB.RunValueLogGC | |
badgerDiscardRatio = 0.5 | |
// Default BadgerDB GC interval | |
badgerGCInterval = 10 * time.Minute | |
) | |
var ( | |
// BadgerAlertNamespace defines the alerts BadgerDB namespace. | |
BadgerAlertNamespace = []byte("alerts") | |
) | |
type ( | |
// DB defines an embedded key/value store database interface. | |
DB interface { | |
Get(namespace, key []byte) (value []byte, err error) | |
Set(namespace, key, value []byte) error | |
Has(namespace, key []byte) (bool, error) | |
Close() error | |
} | |
// BadgerDB is a wrapper around a BadgerDB backend database that implements | |
// the DB interface. | |
BadgerDB struct { | |
db *badger.DB | |
ctx context.Context | |
cancelFunc context.CancelFunc | |
logger Logger | |
} | |
) | |
// NewBadgerDB returns a new initialized BadgerDB database implementing the DB | |
// interface. If the database cannot be initialized, an error will be returned. | |
func NewBadgerDB(dataDir string, logger Logger) (DB, error) { | |
if err := os.MkdirAll(dataDir, 0774); err != nil { | |
return nil, err | |
} | |
opts := badger.DefaultOptions | |
opts.SyncWrites = true | |
opts.Dir, opts.ValueDir = dataDir, dataDir | |
badgerDB, err := badger.Open(opts) | |
if err != nil { | |
return nil, err | |
} | |
bdb := &BadgerDB{ | |
db: badgerDB, | |
logger: logger.With("module", "db"), | |
} | |
bdb.ctx, bdb.cancelFunc = context.WithCancel(context.Background()) | |
go bdb.runGC() | |
return bdb, nil | |
} | |
// Get implements the DB interface. It attempts to get a value for a given key | |
// and namespace. If the key does not exist in the provided namespace, an error | |
// is returned, otherwise the retrieved value. | |
func (bdb *BadgerDB) Get(namespace, key []byte) (value []byte, err error) { | |
err = bdb.db.View(func(txn *badger.Txn) error { | |
item, err := txn.Get(badgerNamespaceKey(namespace, key)) | |
if err != nil { | |
return err | |
} | |
tmpValue, err := item.Value() | |
if err != nil { | |
return err | |
} | |
// Copy the value as the value provided Badger is only valid while the | |
// transaction is open. | |
value = make([]byte, len(tmpValue)) | |
copy(value, tmpValue) | |
return nil | |
}) | |
if err != nil { | |
return nil, err | |
} | |
return value, nil | |
} | |
// Set implements the DB interface. It attempts to store a value for a given key | |
// and namespace. If the key/value pair cannot be saved, an error is returned. | |
func (bdb *BadgerDB) Set(namespace, key, value []byte) error { | |
err := bdb.db.Update(func(txn *badger.Txn) error { | |
return txn.Set(badgerNamespaceKey(namespace, key), value) | |
}) | |
if err != nil { | |
bdb.logger.Debugf("failed to set key %s for namespace %s: %v", key, namespace, err) | |
return err | |
} | |
return nil | |
} | |
// Has implements the DB interface. It returns a boolean reflecting if the | |
// datbase has a given key for a namespace or not. An error is only returned if | |
// an error to Get would be returned that is not of type badger.ErrKeyNotFound. | |
func (bdb *BadgerDB) Has(namespace, key []byte) (ok bool, err error) { | |
_, err = bdb.Get(namespace, key) | |
switch err { | |
case badger.ErrKeyNotFound: | |
ok, err = false, nil | |
case nil: | |
ok, err = true, nil | |
} | |
return | |
} | |
// Close implements the DB interface. It closes the connection to the underlying | |
// BadgerDB database as well as invoking the context's cancel function. | |
func (bdb *BadgerDB) Close() error { | |
bdb.cancelFunc() | |
return bdb.db.Close() | |
} | |
// runGC triggers the garbage collection for the BadgerDB backend database. It | |
// should be run in a goroutine. | |
func (bdb *BadgerDB) runGC() { | |
ticker := time.NewTicker(badgerGCInterval) | |
for { | |
select { | |
case <-ticker.C: | |
err := bdb.db.RunValueLogGC(badgerDiscardRatio) | |
if err != nil { | |
// don't report error when GC didn't result in any cleanup | |
if err == badger.ErrNoRewrite { | |
bdb.logger.Debugf("no BadgerDB GC occurred: %v", err) | |
} else { | |
bdb.logger.Errorf("failed to GC BadgerDB: %v", err) | |
} | |
} | |
case <-bdb.ctx.Done(): | |
return | |
} | |
} | |
} | |
// badgerNamespaceKey returns a composite key used for lookup and storage for a | |
// given namespace and key. | |
func badgerNamespaceKey(namespace, key []byte) []byte { | |
prefix := []byte(fmt.Sprintf("%s/", namespace)) | |
return append(prefix, key...) | |
} |
How do i install the version 1.5.x
This is for version 1.5.x of Badger_DB. There are breaking changes to the opts and Item.value functionality from 1.6.0 onwards.
Enough that I can't copy this and update it?
tmpValue, err := item.Value() returns just err.
instead following works i guess.
tmpValue, err = item.ValueCopy(nil)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is for version 1.5.x of Badger_DB. There are breaking changes to the opts and Item.value functionality from 1.6.0 onwards.