dgraph-io/badger · critical
Equality can happen only on base level: %d
Error message
Equality can happen only on base level: %d
What it means
During skl.Skiplist.Put, the CAS-based insertion retries at each level; if prev[i] == next[i] at level i, the key already exists at that level. Since duplicates can only occur at the base level (0), finding equality at a higher level means the list is corrupt, so Badger panics with this assertion.
Source
Thrown at skl/skl.go:329
// We haven't computed prev, next for this level because height exceeds old listHeight.
// For these levels, we expect the lists to be sparse, so we can just search from head.
prev[i], next[i] = s.findSpliceForLevel(key, s.head, i)
// Someone adds the exact same key before we are able to do so. This can only happen on
// the base level. But we know we are not on the base level.
y.AssertTrue(prev[i] != next[i])
}
nextOffset := s.arena.getNodeOffset(next[i])
x.tower[i].Store(nextOffset)
if prev[i].casNextOffset(i, nextOffset, s.arena.getNodeOffset(x)) {
// Managed to insert x between prev[i] and next[i]. Go to the next level.
break
}
// CAS failed. We need to recompute prev and next.
// It is unlikely to be helpful to try to use a different level as we redo the search,
// because it is unlikely that lots of nodes are inserted between prev[i] and next[i].
prev[i], next[i] = s.findSpliceForLevel(key, prev[i], i)
if prev[i] == next[i] {
y.AssertTruef(i == 0, "Equality can happen only on base level: %d", i)
prev[i].setValue(s.arena, v)
return
}
}
}
}
// Empty returns if the Skiplist is empty.
func (s *Skiplist) Empty() bool {
return s.findLast() == nil
}
// findLast returns the last element. If head (empty list), we return nil. All the find functions
// will NEVER return the head nodes.
func (s *Skiplist) findLast() *node {
n := s.head
level := int(s.getHeight()) - 1
for {View on GitHub (pinned to 2a001d466f)
Solutions
- Use the DB-level API with badger's own concurrency controls instead of sharing a raw skl.Skiplist across goroutines beyond its contract.
- Ensure keys have a strict total order (no key compares equal to another distinct key via a custom encoding).
- If this arises in stock badger, capture the repro and report it — it indicates corruption; reopen the DB from a clean state/WAL replay.
- Audit any local modifications to skl.go findSpliceForLevel/tower handling against upstream.
Example fix
// before
go list.Put(k, v1) // raw shared skiplist, no DB-level coordination
go list.Put(k2, v2) // can panic: Equality can happen only on base level
// after
db.Update(func(txn *Txn) error {
txn.Set(k, v1); return nil
}) // let badger manage memtable/skiplist concurrency Defensive patterns
Strategy: try-catch
Try / catch
defer func() {
if r := recover(); r != nil {
if strings.Contains(fmt.Sprint(r), "Equality can happen only on base level") {
log.Printf("skiplist corruption suspected: %v", r)
// reopen DB / rebuild memtable
}
panic(r)
}
}() Prevention
- Use DB-level transactions rather than sharing raw skl.Skiplist across goroutines
- Ensure key encoding guarantees a strict total order
- Avoid forks/modifications to skl insertion logic; keep upstream parity
When it happens
Trigger: Concurrent Put operations racing such that findSpliceForLevel returns prev==next at a level above 0 — indicating either internal corruption or an out-of-contract use of the skiplist (e.g. key comparisons that violate total ordering, or direct concurrent misuse outside badger's guarantees).
Common situations: Highly concurrent writers to a directly-shared skl.Skiplist, custom comparators/key encodings where equal prefixes compare equal at higher levels, forks with modified node/tower logic, or memory corruption from buffer reuse.
Related errors
- Arena too small, toWrite:%d newTotal:%d limit:%d
- Cannot use NewTransactionAt with managedDB=false. Use NewTra
- cannot use NewWriteBatchAt with managedDB=false. Use NewWrit
- cannot use NewManagedWriteBatch with managedDB=false. Use Ne
- Cannot use CommitAt with managedDB=false. Use Commit instead
AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05).
Data as JSON: /api/errors/907c8504a937c003.
Report an issue: GitHub.