Transactions

Read-only transactions

To start a read-only transaction, you can use the DB.View() method:

  1. err := db.View(func(txn *badger.Txn) error {
  2. // Your code here…
  3. return nil
  4. })

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.

Read-write transactions

To start a read-write transaction, you can use the DB.Update() method:

  1. err := db.Update(func(txn *badger.Txn) error {
  2. // Your code here…
  3. return nil
  4. })

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):

  1. updates := make(map[string]string)
  2. txn := db.NewTransaction(true)
  3. for k,v := range updates {
  4. if err := txn.Set([]byte(k),[]byte(v)); err == badger.ErrTxnTooBig {
  5. _ = txn.Commit()
  6. txn = db.NewTransaction(true)
  7. _ = txn.Set([]byte(k),[]byte(v))
  8. }
  9. }
  10. _ = txn.Commit()

Managing transactions manually

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.

  1. // Start a writable transaction.
  2. txn := db.NewTransaction(true)
  3. defer txn.Discard()
  4. // Use the transaction...
  5. err := txn.Set([]byte("answer"), []byte("42"))
  6. if err != nil {
  7. return err
  8. }
  9. // Commit the transaction and check for error.
  10. if err := txn.Commit(); err != nil {
  11. return err
  12. }

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.