dgraph-io/badger · error

ErrReadOnlyTxn

ErrReadOnlyTxn

Error message

No sets or deletes are allowed in a read-only transaction

What it means

ErrReadOnlyTxn is returned when a mutation (Set/SetEntry/Delete/modify) is attempted on a read-only transaction (txn.go:356). Read-only transactions come from db.View() or db.NewTransactionAt(ts, false) and cannot write.

Solutions

  1. Use db.Update (or NewTransactionAt(ts, true)) when the code writes
  2. Split read-only and mutating logic into separate View/Update blocks
  3. Check txn.update before mutating in generic helpers

Example fix

// before
err := db.View(func(txn *Txn) error {
    return txn.Set([]byte("k"), []byte("v")) // ErrReadOnlyTxn
})
// after
err := db.Update(func(txn *Txn) error {
    return txn.Set([]byte("k"), []byte("v"))
})
Defensive patterns

Strategy: validation

Validate before calling

func mustBeWritable(txn *badger.Txn) error {
    if !txn.update { // unexported; instead track writability in your own flag
        return errors.New("read-only transaction")
    }
    return nil
}
// practical caller-side check: only pass txns from db.Update/NewTransaction(true) to mutators

Type guard

func isWritableTxn(txn *badger.Txn, writable bool) bool { return writable }

Try / catch

if err := txn.Set(k, v); err != nil {
    if errors.Is(err, badger.ErrReadOnlyTxn) {
        return fmt.Errorf("mutation attempted in View: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling txn.Set/SetEntry/Delete inside db.View(); passing a read-only txn into a mutating helper; creating a txn with NewTransactionAt(ts, false) then writing.

Common situations: Refactoring Update() to View() for read paths while leaving a Set inside; shared helpers that both read and write receiving a read-only txn.

Related errors


AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05). Data as JSON: /api/errors/769468265c9a7742. Report an issue: GitHub.

Appendix: source

Thrown at errors.go:34

)

var (
	// ErrValueLogSize is returned when opt.ValueLogFileSize option is not within the valid
	// range.
	ErrValueLogSize = stderrors.New("Invalid ValueLogFileSize, must be in range [1MB, 2GB)")

	// ErrKeyNotFound is returned when key isn't found on a txn.Get.
	ErrKeyNotFound = stderrors.New("Key not found")

	// ErrTxnTooBig is returned if too many writes are fit into a single transaction.
	ErrTxnTooBig = stderrors.New("Txn is too big to fit into one request")

	// ErrConflict is returned when a transaction conflicts with another transaction. This can
	// happen if the read rows had been updated concurrently by another transaction.
	ErrConflict = stderrors.New("Transaction Conflict. Please retry")

	// ErrReadOnlyTxn is returned if an update function is called on a read-only transaction.
	ErrReadOnlyTxn = stderrors.New("No sets or deletes are allowed in a read-only transaction")

	// ErrDiscardedTxn is returned if a previously discarded transaction is reused.
	ErrDiscardedTxn = stderrors.New("This transaction has been discarded. Create a new one")

	// ErrEmptyKey is returned if an empty key is passed on an update function.
	ErrEmptyKey = stderrors.New("Key cannot be empty")

	// ErrInvalidKey is returned if the key has a special !badger! prefix,
	// reserved for internal usage.
	ErrInvalidKey = stderrors.New("Key is using a reserved !badger! prefix")

	// ErrBannedKey is returned if the read/write key belongs to any banned namespace.
	ErrBannedKey = stderrors.New("Key is using the banned prefix")

	// ErrThresholdZero is returned if threshold is set to zero, and value log GC is called.
	// In such a case, GC can't be run.
	ErrThresholdZero = stderrors.New(
		"Value log GC can't run because threshold is set to zero")

View on GitHub (pinned to 2a001d466f)