dgraph-io/badger · error

DB Closed

Error message

DB Closed

What it means

ErrDBClosed is panicked by NewIterator when the underlying DB has been closed. You cannot create iterators (or transactions) against a closed database; all reads require an open DB. This can also surface when a background worker with an open iterator keeps running after db.Close().

Source

Thrown at iterator.go:464

	Alloc *z.Allocator
}

// NewIterator returns a new iterator. Depending upon the options, either only keys, or both
// key-value pairs would be fetched. The keys are returned in lexicographically sorted order.
// Using prefetch is recommended if you're doing a long running iteration, for performance.
//
// Multiple Iterators:
// For a read-only txn, multiple iterators can be running simultaneously. However, for a read-write
// txn, iterators have the nuance of being a snapshot of the writes for the transaction at the time
// iterator was created. If writes are performed after an iterator is created, then that iterator
// will not be able to see those writes. Only writes performed before an iterator was created can be
// viewed.
func (txn *Txn) NewIterator(opt IteratorOptions) *Iterator {
	if txn.discarded {
		panic(ErrDiscardedTxn)
	}
	if txn.db.IsClosed() {
		panic(ErrDBClosed)
	}

	y.NumIteratorsCreatedAdd(txn.db.opt.MetricsEnabled, 1)

	// Keep track of the number of active iterators.
	txn.numIterators.Add(1)

	// TODO: If Prefix is set, only pick those memtables which have keys with the prefix.
	tables, decr := txn.db.getMemTables()
	defer decr()
	txn.db.vlog.incrIteratorCount()
	var iters []y.Iterator
	if itr := txn.newPendingWritesIterator(opt.Reverse); itr != nil {
		iters = append(iters, itr)
	}
	for i := 0; i < len(tables); i++ {
		iters = append(iters, tables[i].sl.NewUniIterator(opt.Reverse))
	}

View on GitHub (pinned to 2a001d466f)

Solutions

  1. Synchronize shutdown: stop all readers/iterators (close iterators, drain workers) before db.Close()
  2. Check db.IsClosed() before creating transactions/iterators in long-lived workers
  3. Keep a single owner of the DB lifecycle and expose explicit Open/Close coordination

Example fix

// before
go worker(db)
db.Close()
// after
stop := make(chan struct{})
go worker(db, stop)
close(stop)
wg.Wait() // worker finished and closed its iterators
db.Close()
Defensive patterns

Strategy: validation

Validate before calling

if db.IsClosed() { return errors.New("db is closed; cannot iterate") }

Try / catch

defer func(){ if r := recover(); r != nil { if errors.Is(badger.ErrDBClosed, nil); true { log.Warn("iterator used after close") } } }()
// simpler:
defer func(){ if r := recover(); r != nil { log.Warnf("badger panic: %v", r) } }()

Prevention

When it happens

Trigger: Calling txn.NewIterator after db.Close(); a worker goroutine creating iterators while main code closes the DB; Close being called on a DB that another component still reads from.

Common situations: Graceful-shutdown ordering bugs; tests closing a shared DB in cleanup while other tests still use it; singleton DB handles closed by one caller and reused by another.

Related errors


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