dgraph-io/badger · info
errNoMerge
errNoMerge
Error message
No need for merge
What it means
Sentinel error of the MergeOperator (db.GetMergeOperator). iterateAndMerge computes a new value by folding the operator's merge function over all versions of a key; errNoMerge signals that there was exactly one version and nothing to merge, so the operator loop treats it as success (no compaction run needed). It is internal control flow, not a real failure — returned by Get via iterateAndMerge/compact paths and filtered out by the operator.
Source
Thrown at merge.go:49
type MergeFunc func(existingVal, newVal []byte) []byte
// GetMergeOperator creates a new MergeOperator for a given key and returns a
// pointer to it. It also fires off a goroutine that performs a compaction using
// the merge function that runs periodically, as specified by dur.
func (db *DB) GetMergeOperator(key []byte,
f MergeFunc, dur time.Duration) *MergeOperator {
op := &MergeOperator{
f: f,
db: db,
key: key,
closer: z.NewCloser(1),
}
go op.runCompactions(dur)
return op
}
var errNoMerge = stderrors.New("No need for merge")
func (op *MergeOperator) iterateAndMerge() (newVal []byte, latest uint64, err error) {
txn := op.db.NewTransaction(false)
defer txn.Discard()
opt := DefaultIteratorOptions
opt.AllVersions = true
it := txn.NewKeyIterator(op.key, opt)
defer it.Close()
var numVersions int
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
if item.IsDeletedOrExpired() {
break
}
numVersions++
if numVersions == 1 {
// This should be the newVal, considering this is the latest version.View on GitHub (pinned to 2a001d466f)
Solutions
- Nothing to fix: if you observe errNoMerge from MergeOperator.Get results, it is consumed internally and means the value is the single stored version
- If implementing custom merge logic, treat single-version keys as 'no merge required' and return the value as-is
- Do not propagate errNoMerge (== errNoMerge) to callers; compare with errors.Is and map it to success
Example fix
// before
val, _, err := op.iterateAndMerge()
return err // leaks sentinel to callers
// after
val, _, err := op.iterateAndMerge()
if err == ErrKeyNotFound || err == errNoMerge {
return nil
} Defensive patterns
Strategy: type-guard
Validate before calling
val, err := op.Get()
if errors.Is(err, errNoMerge) { err = nil } Type guard
func isNoMerge(err error) bool { return err != nil && err.Error() == "No need for merge" } Try / catch
val, err := op.Get()
if err != nil && !isNoMerge(err) {
return err // treat errNoMerge as success
} Prevention
- Consume MergeOperator APIs (Get/Merge) instead of reimplementing iterateAndMerge
- When comparing errors, use errors.Is so internal sentinels are handled intentionally
- Understand that single-version keys legitimately produce this sentinel
When it happens
Trigger: Calling MergeOperator.Get (or the internal iterateAndMerge during periodic compaction) on a key that currently has exactly one version; nothing is wrong — the operator converts it to a nil error.
Common situations: Seen when wrapping operator internals or reading badger source/traces; users directly running iterateAndMerge-like logic or inspecting logs during low-contention periods where keys have single versions.
Related errors
AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05).
Data as JSON: /api/errors/c39014c5b78a927a.
Report an issue: GitHub.