BadgerDB is an embedded, persistent key-value store written in pure Go by the Dgraph team. Designed in 2017 to address the problem that LevelDB- and RocksDB-cgo bindings were the only practical KV options for Go programs at the time, BadgerDB is a native Go LSM implementation with a key innovation: it stores values separately from keys (WiscKey design) when values are above a threshold, dramatically reducing write amplification.
Backup / Load APIs for hot-incremental backup.BadgerDB is an embeddable key-value store written in Go by Dgraph Labs, and it is the storage engine underneath the Dgraph graph database. It follows the log-structured merge (LSM) tree design used by LevelDB and RocksDB, with one important change taken from the WiscKey paper: values above a configurable threshold are written to a separate append-only value log, and the LSM tree stores only the key and a small pointer into that log. Small values are still stored inline in the tree.
Because the LSM tree holds keys and pointers rather than full values, it is much smaller, so more of it fits in memory and compaction rewrites far less data. This reduces write amplification, which matters on SSDs. The cost is that a read of a large value needs one extra random read into the value log, and deleted or overwritten values must be reclaimed by a separate value-log garbage collection.
A read checks the in-memory tables first, then each level of SSTables from newest to oldest, using bloom filters and block indexes to skip tables that cannot contain the key. When the entry found is a value pointer, Badger performs a single read from the value log. Iteration over key ranges can be configured to fetch only keys (PrefetchValues = false), which is very fast because it touches only the compact LSM tree.
package main
import (
"fmt"
"log"
badger "github.com/dgraph-io/badger/v4"
)
func main() {
db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
if err != nil {
log.Fatal(err)
}
defer db.Close()
// read-write transaction
err = db.Update(func(txn *badger.Txn) error {
e := badger.NewEntry([]byte("user:42"), []byte(`{"name":"Ana"}`))
return txn.SetEntry(e)
})
// read-only transaction
err = db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("user:42"))
if err != nil {
return err
}
return item.Value(func(v []byte) error {
fmt.Println(string(v))
return nil
})
})
// prefix scan over keys only
db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false
it := txn.NewIterator(opts)
defer it.Close()
for it.Seek([]byte("user:")); it.ValidForPrefix([]byte("user:")); it.Next() {
fmt.Println(string(it.Item().Key()))
}
return nil
})
// reclaim space in the value log (call periodically)
_ = db.RunValueLogGC(0.5)
}
Transactions are serializable snapshot isolation (SSI): Update retries are up to the caller when a conflict returns badger.ErrConflict. Entries can carry a TTL with WithTTL, which is handy for caches and sessions.
Choose BadgerDB when you are writing a Go service that needs a fast, persistent, embedded store with transactions and you want to avoid cgo (RocksDB bindings require it). Prefer bbolt or LMDB when the workload is read-heavy with modest writes and you value a single-file B+ tree with simple operational behaviour. Prefer RocksDB when you need its very large tuning surface, column families, or bindings for many languages. Prefer Redis or Memcached when data must be shared over the network by many processes rather than embedded in one.
RunValueLogGC, typically on a ticker; forgetting it makes disk usage grow indefinitely.ErrTxnTooBig if too large; batch with WriteBatch for bulk loads.