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
- Synchronize shutdown: stop all readers/iterators (close iterators, drain workers) before db.Close()
- Check db.IsClosed() before creating transactions/iterators in long-lived workers
- 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
- Order shutdown: stop workers, close iterators, then db.Close()
- Guard worker loops with db.IsClosed() checks per iteration
- Use context cancellation tied to DB shutdown
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
- Unclosed iterator at time of Txn.Discard.
- This transaction has been discarded. Create a new one
- opt.Prefix should be nil for NewKeyIterator.
- BlockCacheSize should be set since compression/encryption ar
- Cannot use GetSequence with managedDB=true.
AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05).
Data as JSON: /api/errors/e380d92b4e3f86fe.
Report an issue: GitHub.