{"record":{"id":"907c8504a937c003","repo":"dgraph-io/badger","slug":"equality-can-happen-only-on-base-level-d","errorCode":null,"errorMessage":"Equality can happen only on base level: %d","messagePattern":"Equality can happen only on base level: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"skl/skl.go","lineNumber":329,"sourceCode":"\t\t\t\t// We haven't computed prev, next for this level because height exceeds old listHeight.\n\t\t\t\t// For these levels, we expect the lists to be sparse, so we can just search from head.\n\t\t\t\tprev[i], next[i] = s.findSpliceForLevel(key, s.head, i)\n\t\t\t\t// Someone adds the exact same key before we are able to do so. This can only happen on\n\t\t\t\t// the base level. But we know we are not on the base level.\n\t\t\t\ty.AssertTrue(prev[i] != next[i])\n\t\t\t}\n\t\t\tnextOffset := s.arena.getNodeOffset(next[i])\n\t\t\tx.tower[i].Store(nextOffset)\n\t\t\tif prev[i].casNextOffset(i, nextOffset, s.arena.getNodeOffset(x)) {\n\t\t\t\t// Managed to insert x between prev[i] and next[i]. Go to the next level.\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t// CAS failed. We need to recompute prev and next.\n\t\t\t// It is unlikely to be helpful to try to use a different level as we redo the search,\n\t\t\t// because it is unlikely that lots of nodes are inserted between prev[i] and next[i].\n\t\t\tprev[i], next[i] = s.findSpliceForLevel(key, prev[i], i)\n\t\t\tif prev[i] == next[i] {\n\t\t\t\ty.AssertTruef(i == 0, \"Equality can happen only on base level: %d\", i)\n\t\t\t\tprev[i].setValue(s.arena, v)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Empty returns if the Skiplist is empty.\nfunc (s *Skiplist) Empty() bool {\n\treturn s.findLast() == nil\n}\n\n// findLast returns the last element. If head (empty list), we return nil. All the find functions\n// will NEVER return the head nodes.\nfunc (s *Skiplist) findLast() *node {\n\tn := s.head\n\tlevel := int(s.getHeight()) - 1\n\tfor {","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/dgraph-io/badger/blob/2a001d466f6b71a917319a1db41f99860e16e269/skl/skl.go#L311-L347","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\ngo list.Put(k, v1) // raw shared skiplist, no DB-level coordination\ngo list.Put(k2, v2) // can panic: Equality can happen only on base level\n// after\ndb.Update(func(txn *Txn) error {\n    txn.Set(k, v1); return nil\n}) // let badger manage memtable/skiplist concurrency","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"defer func() {\n\tif r := recover(); r != nil {\n\t\tif strings.Contains(fmt.Sprint(r), \"Equality can happen only on base level\") {\n\t\t\tlog.Printf(\"skiplist corruption suspected: %v\", r)\n\t\t\t// reopen DB / rebuild memtable\n\t\t}\n\t\tpanic(r)\n\t}\n}()","preventionTips":["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"],"tags":["go","badger","skiplist","concurrency","panic"],"backgroundTag":"skiplist-level-invariant-violation","analyzedSha":"2a001d466f6b71a917319a1db41f99860e16e269","analyzedAt":"2026-09-05T13:00:02.264Z","contentChangedAt":"2026-09-05T13:00:02.264Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}