dgraph-io/badger · warning

Unable to find fid: %d

Error message

Unable to find fid: %d

What it means

Thrown by valueLog.rewrite (value.go:364) during GC (doRunGC): after moving surviving entries to new log files, the code removes the old file from filesMap but a sanity check finds f.fid is no longer (or never was) present, so the GC run returns this error instead of proceeding.

Source

Thrown at value.go:364

				// Decrease the batch size to half.
				batchSize = batchSize / 2
				continue
			}
			return err
		}
		i += batchSize
	}
	vlog.opt.Infof("Processed %d entries in %d loops", len(wb), loops)
	vlog.opt.Infof("Total entries: %d. Moved: %d", count, moved)
	vlog.opt.Infof("Removing fid: %d", f.fid)
	var deleteFileNow bool
	// Entries written to LSM. Remove the older file now.
	{
		vlog.filesLock.Lock()
		// Just a sanity-check.
		if _, ok := vlog.filesMap[f.fid]; !ok {
			vlog.filesLock.Unlock()
			return fmt.Errorf("Unable to find fid: %d", f.fid)
		}
		if vlog.iteratorCount() == 0 {
			delete(vlog.filesMap, f.fid)
			deleteFileNow = true
		} else {
			vlog.filesToBeDeleted = append(vlog.filesToBeDeleted, f.fid)
		}
		vlog.filesLock.Unlock()
	}

	if deleteFileNow {
		if err := vlog.deleteLogFile(f); err != nil {
			return err
		}
	}
	return nil
}

View on GitHub (pinned to 2a001d466f)

Solutions

  1. Treat it as a benign end-state in GC loops: catch it and skip that fid, re-listing vlog files for the next GC candidate.
  2. Ensure only one goroutine runs value-log GC at a time (serialize with a mutex or worker).
  3. Re-run GC later on a still-present file id obtained from vlog.filesMap/lf, rather than a stale cached fid.
  4. Do not delete .vlog files externally while the DB is open; let badger's GC delete them.
  5. If persistent, restart the DB to rebuild filesMap and retry GC.

Example fix

// before
err := db.RunValueLogGC(0.5)
if err != nil { log.Fatal(err) }
// after
err := db.RunValueLogGC(0.5)
if err != nil && !strings.Contains(err.Error(), "Unable to find fid") && !errors.Is(err, badger.ErrRejected) {
    log.Fatal(err)
} // otherwise: file already rewritten/removed; retry GC on next cycle
Defensive patterns

Strategy: try-catch

Validate before calling

// before GC, check the file still exists in the live set
info, err := db.Opts() // or track fids via value log registry
// simplest guard: only run GC sequentially from a single worker goroutine
var gcMu sync.Mutex
func safeRunGC(db *badger.DB, ratio float64) error {
    gcMu.Lock(); defer gcMu.Unlock()
    return db.RunValueLogGC(ratio)
}

Type guard

func isFidNotFoundErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "Unable to find fid")
}

Try / catch

err := db.RunValueLogGC(0.5)
switch {
case err == nil:
    // GC done
case errors.Is(err, badger.ErrNoRewrite), isFidNotFoundErr(err):
    // file already rewritten/removed: benign, skip and pick another fid later
default:
    return err
}

Prevention

When it happens

Trigger: Running vlog.RunGC/DB.RunValueLogGC when the target log file was already deleted or replaced by a concurrent GC/iterator cleanup, or after external manipulation/removal of .vlog files while the DB is open.

Common situations: Two GC loops racing on the same value-log file, calling RunValueLogGC on an already-collected file id, operator deleting a .vlog file mid-run, crash-recovery removing files between selection and rewrite.

Related errors


AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05). Data as JSON: /api/errors/e04a4c2df0817c3f. Report an issue: GitHub.