
BadgerDB is an embeddable, persistent and fast key-value (KV) database written in pure Go. It is the underlying database for Dgraph, a fast, distributed graph database. It's meant to be a performant alternative to non-Go-based key-value stores like RocksDB.
Badger is stable and is being used to serve data sets worth hundreds of
terabytes. Badger supports concurrent ACID transactions with serializable
snapshot isolation (SSI) guarantees. A Jepsen-style bank test runs nightly for
8h, with --race flag and ensures the maintenance of transactional guarantees.
Badger has also been tested to work with filesystem level anomalies, to ensure
persistence and consistency. Badger is being used by a number of projects which
includes Dgraph, Jaeger Tracing, UsenetExpress, and many more.
The list of projects using Badger can be found here.
Badger v1.0 was released in Nov 2017, and the latest version that is data-compatible with v1.0 is v1.6.0.
Badger v2.0 was released in Nov 2019 with a new storage format which won't be compatible with all of the v1.x. Badger v2.0 supports compression, encryption and uses a cache to speed up lookup.
The Changelog is kept fairly up-to-date.
For more details on our version naming schema please read Choosing a version.
To start using Badger, install Go 1.12 or above and run go get:
$ go get github.com/dgraph-io/badger/v2
This will retrieve the library and install the badger command line
utility into your $GOBIN path.
CGO_ENABLED=0 go get github.com/dgraph-io/badger/... which will download badger without the support for ZSTD compression algorithm.BadgerDB is a pretty special package from the point of view that the most important change we can make to it is not on its API but rather on how data is stored on disk.
This is why we follow a version naming schema that differs from Semantic Versioning.
Following these rules:
For a longer explanation on the reasons behind using a new versioning naming schema, you can read VERSIONING.md.
The top-level object in Badger is a DB. It represents multiple files on disk
in specific directories, which contain the data for a single database.
To open your database, use the badger.Open() function, with the appropriate
options. The Dir and ValueDir options are mandatory and must be
specified by the client. They can be set to the same value to simplify things.
package main
import (
"log"
badger "github.com/dgraph-io/badger/v2"
)
func main() {
// Open the Badger database located in the /tmp/badger directory.
// It will be created if it doesn't exist.
db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Your code here…
}
Please note that Badger obtains a lock on the directories so multiple processes cannot open the same database at the same time.
By default, Badger ensures all the data is persisted to the disk. It also supports a pure
in-memory mode. When Badger is running in in-memory mode, all the data is stored in the memory.
Reads and writes are much faster in in-memory mode, but all the data stored in Badger will be lost
in case of a crash or close. To open badger in in-memory mode, set the InMemory option.
opt := badger.DefaultOptions("").WithInMemory(true)
To start a read-only transaction, you can use the DB.View() method:
err := db.View(func(txn *badger.Txn) error {
// Your code here…
return nil
})
You cannot perform any writes or deletes within this transaction. Badger ensures that you get a consistent view of the database within this closure. Any writes that happen elsewhere after the transaction has started, will not be seen by calls made within the closure.
To start a read-write transaction, you can use the DB.Update() method:
err := db.Update(func(txn *badger.Txn) error {
// Your code here…
return nil
})
All database operations are allowed inside a read-write transaction.
Always check the returned error value. If you return an error within your closure it will be passed through.
An ErrConflict error will be reported in case of a conflict. Depending on the state
of your application, you have the option to retry the operation if you receive
this error.
An ErrTxnTooBig will be reported in case the number of pending writes/deletes in
the transaction exceeds a certain limit. In that case, it is best to commit the
transaction and start a new transaction immediately. Here is an example (we are
not checking for errors in some places for simplicity):
updates := make(map[string]string)
txn := db.NewTransaction(true)
for k,v := range updates {
if err := txn.Set([]byte(k),[]byte(v)); err == badger.ErrTxnTooBig {
_ = txn.Commit()
txn = db.NewTransaction(true)
_ = txn.Set([]byte(k),[]byte(v))
}
}
_ = txn.Commit()
The DB.View() and DB.Update() methods are wrappers around the
DB.NewTransaction() and Txn.Commit() methods (or Txn.Discard() in case of
read-only transactions). These helper methods will start the transaction,
execute a function, and then safely discard your transaction if an error is
returned. This is the recommended way to use Badger transactions.
However, sometimes you may want to manually create and commit your
transactions. You can use the DB.NewTransaction() function directly, which
takes in a boolean argument to specify whether a read-write transaction is
required. For read-write transactions, it is necessary to call Txn.Commit()
to ensure the transaction is committed. For read-only transactions, calling
Txn.Discard() is sufficient. Txn.Commit() also calls Txn.Discard()
internally to cleanup the transaction, so just calling Txn.Commit() is
sufficient for read-write transaction. However, if your code doesn’t call
Txn.Commit() for some reason (for e.g it returns prematurely with an error),
then please make sure you call Txn.Discard() in a defer block. Refer to the
code below.
// Start a writable transaction.
txn := db.NewTransaction(true)
defer txn.Discard()
// Use the transaction...
err := txn.Set([]byte("answer"), []byte("42"))
if err != nil {
return err
}
// Commit the transaction and check for error.
if err := txn.Commit(); err != nil {
return err
}
The first argument to DB.NewTransaction() is a boolean stating if the transaction
should be writable.
Badger allows an optional callback to the Txn.Commit() method. Normally, the
callback can be set to nil, and the method will return after all the writes
have succeeded. However, if this callback is provided, the Txn.Commit()
method returns as soon as it has checked for any conflicts. The actual writing
to the disk happens asynchronously, and the callback is invoked once the
writing has finished, or an error has occurred. This can improve the throughput
of the application in some cases. But it also means that a transaction is not
durable until the callback has been invoked with a nil error value.
To save a key/value pair, use the Txn.Set() method:
err := db.Update(func(txn *badger.Txn) error {
err := txn.Set([]byte("answer"), []byte("42"))
return err
})
Key/Value pair can also be saved by first creating Entry, then setting this
Entry using Txn.SetEntry(). Entry also exposes methods to set properties
on it.
err := db.Update(func(txn *badger.Txn) error {
e := badger.NewEntry([]byte("answer"), []byte("42"))
err := txn.SetEntry(e)
return err
})
This will set the value of the "answer" key to "42". To retrieve this
value, we can use the Txn.Get() method:
err := db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("answer"))
handle(err)
var valNot, valCopy []byte
err := item.Value(func(val []byte) error {
// This func with val would only be called if item.Value encounters no error.
// Accessing val here is valid.
fmt.Printf("The answer is: %s\n", val)
// Copying or parsing val is valid.
valCopy = append([]byte{}, val...)
// Assigning val slice to another variable is NOT OK.
valNot = val // Do not do this.
return nil
})
handle(err)
// DO NOT access val here. It is the most common cause of bugs.
fmt.Printf("NEVER do this. %s\n", valNot)
// You must copy it to use it outside item.Value(...).
fmt.Printf("The answer is: %s\n", valCopy)
// Alternatively, you could also use item.ValueCopy().
valCopy, err = item.ValueCopy(nil)
handle(err)
fmt.Printf("The answer is: %s\n", valCopy)
return nil
})
Txn.Get() returns ErrKeyNotFound if the value is not found.
Please note that values returned from Get() are only valid while the
transaction is open. If you need to use a value outside of the transaction
then you must use copy() to copy it to another byte slice.
Use the Txn.Delete() method to delete a key.
To get unique monotonically increasing integers with strong durability, you can
use the DB.GetSequence method. This method returns a Sequence object, which
is thread-safe and can be used concurrently via various goroutines.
Badger would lease a range of integers to hand out from memory, with the
bandwidth provided to DB.GetSequence. The frequency at which disk writes are
done is determined by this lease bandwidth and the frequency of Next
invocations. Setting a bandwidth too low would do more disk writes, setting it
too high would result in wasted integers if Badger is closed or crashes.
To avoid wasted integers, call Release before closing Badger.
seq, err := db.GetSequence(key, 1000)
defer seq.Release()
for {
num, err := seq.Next()
}
Badger provides support for ordered merge operations. You can define a func
of type MergeFunc which takes in an existing value, and a value to be
merged with it. It returns a new value which is the result of the merge
operation. All values are specified in byte arrays. For e.g., here is a merge
function (add) which appends a []byte value to an existing []byte value.
// Merge function to append one byte slice to another
func add(originalValue, newValue []byte) []byte {
return append(originalValue, newValue...)
}
This function can then be passed to the DB.GetMergeOperator() method, along
with a
$ claude mcp add badger \
-- python -m otcore.mcp_server <graph>