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

  1. Nothing to fix: if you observe errNoMerge from MergeOperator.Get results, it is consumed internally and means the value is the single stored version
  2. If implementing custom merge logic, treat single-version keys as 'no merge required' and return the value as-is
  3. 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

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.