syncthing/syncthing · critical

loading small index:

Error message

loading small index: 

What it means

The legacy-database small index (a string↔uint32 ID map persisted in the DB under a prefix) panics when NewPrefixIterator cannot be created for its key prefix. This runs at construction time (newSmallIndex → load), so a DB-level failure here is treated as unrecoverable corruption/misconfiguration of the underlying key-value store rather than a handled error.

Solutions

  1. Ensure only one syncthing process uses the database directory (check for lock files and running instances)
  2. Restore the database from a backup, or move the old DB aside and let syncthing re-index the folder if history is expendable
  3. Fix permissions/ownership on the database directory and check ulimit -n
  4. If it occurs during an upgrade, roll back to the previous syncthing version, complete a clean shutdown, then upgrade again
Defensive patterns

Strategy: fallback

Validate before calling

// pre-flight: refuse to start if the db dir is locked or unwritable
if _, err := os.OpenFile(filepath.Join(dbDir, "lock"), os.O_CREATE|os.O_EXCL, 0o600); err == nil {
    os.Remove(filepath.Join(dbDir, "lock"))
} else if os.IsExist(err) {
    return errors.New("database appears in use by another process")
}

Prevention

When it happens

Trigger: Opening an old/corrupt database where the prefix iterator cannot be created — open file descriptor exhaustion, DB reopened concurrently by another process holding a lock, corrupted/mismatched backend files during a legacy (LevelDB→PEBBLE/sqlite) migration, or a read-only database file.

Common situations: Two syncthing instances pointed at the same data dir; crash mid-migration leaving a mixed old/new DB; permissions changed on index.db; FD limits hit after many folder restarts.

Related errors


AI-assisted analysis of syncthing/syncthing@058bcd7334 (2026-08-15). Data as JSON: /api/errors/327127996d1380f3. Report an issue: GitHub.

Appendix: source

Thrown at internal/db/olddb/smallindex.go:45

}

func newSmallIndex(db backend.Backend, prefix []byte) *smallIndex {
	idx := &smallIndex{
		db:     db,
		prefix: prefix,
		id2val: make(map[uint32]string),
		val2id: make(map[string]uint32),
	}
	idx.load()
	return idx
}

// load iterates over the prefix space in the database and populates the in
// memory maps.
func (i *smallIndex) load() {
	it, err := i.db.NewPrefixIterator(i.prefix)
	if err != nil {
		panic("loading small index: " + err.Error())
	}
	defer it.Release()
	for it.Next() {
		val := string(it.Value())
		id := binary.BigEndian.Uint32(it.Key()[len(i.prefix):])
		if val != "" {
			// Empty value means the entry has been deleted.
			i.id2val[id] = val
			i.val2id[val] = id
		}
		if id >= i.nextID {
			i.nextID = id + 1
		}
	}
}

// ID returns the index number for the given byte slice, allocating a new one
// and persisting this to the database if necessary.

View on GitHub (pinned to 058bcd7334)