Skip to content

Instantly share code, notes, and snippets.

@alexanderbez
Last active January 22, 2023 21:03
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save alexanderbez/d99fd0383ad57e991e9af9adcbb70b9d to your computer and use it in GitHub Desktop.
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.
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...)
}
@jonknight73
Copy link

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.

@KINTEP
Copy link

KINTEP commented Oct 26, 2020

How do i install the version 1.5.x

@Nuxij
Copy link

Nuxij commented Jan 5, 2021

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?

@Yanik39
Copy link

Yanik39 commented Jul 30, 2021

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