{"record":{"id":"327127996d1380f3","repo":"syncthing/syncthing","slug":"loading-small-index","errorCode":null,"errorMessage":"loading small index: ","messagePattern":"loading small index: ","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/db/olddb/smallindex.go","lineNumber":45,"sourceCode":"}\n\nfunc newSmallIndex(db backend.Backend, prefix []byte) *smallIndex {\n\tidx := &smallIndex{\n\t\tdb:     db,\n\t\tprefix: prefix,\n\t\tid2val: make(map[uint32]string),\n\t\tval2id: make(map[string]uint32),\n\t}\n\tidx.load()\n\treturn idx\n}\n\n// load iterates over the prefix space in the database and populates the in\n// memory maps.\nfunc (i *smallIndex) load() {\n\tit, err := i.db.NewPrefixIterator(i.prefix)\n\tif err != nil {\n\t\tpanic(\"loading small index: \" + err.Error())\n\t}\n\tdefer it.Release()\n\tfor it.Next() {\n\t\tval := string(it.Value())\n\t\tid := binary.BigEndian.Uint32(it.Key()[len(i.prefix):])\n\t\tif val != \"\" {\n\t\t\t// Empty value means the entry has been deleted.\n\t\t\ti.id2val[id] = val\n\t\t\ti.val2id[val] = id\n\t\t}\n\t\tif id >= i.nextID {\n\t\t\ti.nextID = id + 1\n\t\t}\n\t}\n}\n\n// ID returns the index number for the given byte slice, allocating a new one\n// and persisting this to the database if necessary.","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/syncthing/syncthing/blob/058bcd7334839663cf569501d3ac539034d45cb5/internal/db/olddb/smallindex.go#L27-L63","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure only one syncthing process uses the database directory (check for lock files and running instances)","Restore the database from a backup, or move the old DB aside and let syncthing re-index the folder if history is expendable","Fix permissions/ownership on the database directory and check ulimit -n","If it occurs during an upgrade, roll back to the previous syncthing version, complete a clean shutdown, then upgrade again"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// pre-flight: refuse to start if the db dir is locked or unwritable\nif _, err := os.OpenFile(filepath.Join(dbDir, \"lock\"), os.O_CREATE|os.O_EXCL, 0o600); err == nil {\n    os.Remove(filepath.Join(dbDir, \"lock\"))\n} else if os.IsExist(err) {\n    return errors.New(\"database appears in use by another process\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Enforce single-instance operation per data directory (systemd, container orchestration)","Back up the index database before upgrades","Never mix database files produced by different syncthing major versions"],"tags":["database","panic","migration","corruption","go"],"backgroundTag":null,"analyzedSha":"058bcd7334839663cf569501d3ac539034d45cb5","analyzedAt":"2026-08-15T07:53:43.174Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}