База состояния и восстановление FTS

Пошаговое руководство для пользователя (что остановить, что за файлы лежат рядом со state.db, почему служебные команды отказываются работать, пока жив процесс-писатель) — на странице Восстановление хранилища сессий.

state.db хранит два разных класса данных:

  • sessions и messages — канонический транскрипт.
  • Таблицы messages_fts* и их триггеры синхронизации — производные поисковые индексы.

Производные индексы можно временно отсоединить. Они не должны превращать живую запись сообщения или поиск в неограниченную перестройку всего транскрипта.

Поведение вживую, когда повреждён FTS

Если запись в FTS или поиск сообщает об ошибке класса «повреждение», SessionDB:

  1. записывает долговременный маркер fts_stale;
  2. в той же транзакции удаляет триггеры синхронизации FTS;
  3. повторяет канонические записи уже без приёмников производного индекса; и
  4. отдаёт результаты поиска из канонических строк через запасной путь LIKE.

Упавшая живая операция никогда не запускает FTS5('rebuild'). Существующее владение восстановлением не меняется: более позднее открытие SessionDB может выполнить перестройку под межпроцессной блокировкой допуска и защитой от чужого держателя. Если такая защищённая перестройка выполниться не может, FTS остаётся отсоединённым, канонические записи продолжают работать, а hermes doctor сообщает явную команду починки.

Поведение вживую, когда повреждён сам файл

Если живая запись сообщает голый SQLITE_CORRUPT / SQLITE_NOTADB (database disk image is malformed, file is not a database) без следов FTS, повреждение сидит в каноническом B-дереве, схеме или списке свободных страниц. Тогда SessionDB помещает этот дескриптор в карантин (StateDbCorruptError):

  1. упавшая запись пробрасывает типизированную ошибку, и ничего не повторяется;
  2. последующие записи через этот дескриптор падают сразу, не трогая файл;
  3. дескриптор никогда не переоткрывает своё соединение после close(); и
  4. close() пропускает свой явный чекпоинт WAL.

Защита — в том, чтобы остановить записи. В реальной эксплуатации дескриптор, который продолжал писать ~50 минут после первой структурной ошибки, при завершении работы записал чекпоинтом 15 страниц под неверными номерами (страница 1 получила лист messages_fts_trigram_data) и превратил повреждённый, но читаемый файл в файл, который больше не открывался вовсе. Пропуск явного чекпоинта — вторая линия обороны; на Python 3.12+ карантин также отключает собственный чекпоинт SQLite при закрытии последнего соединения (SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE), так что файл-спутник -wal переживает close() и остаётся для разбора. На Python 3.11 этого переключателя нет, и SQLite всё же может выполнить один чекпоинт при закрытии, поэтому скопируйте state.db, state.db-wal и state.db-shm вместе, прежде чем что-либо перезапускать.

Шлюз и путь сброса агента обращаются с карантином как с подменённым файлом: ожидающие транскрипты уходят в sessions/<id>.jsonl и в спул шлюза pending_messages/ вместо очереди повторов, а разовая перестройка FTS на повреждённом файле не запускается никогда. Карантин действует в пределах процесса — общий дескриптор остаётся отравленным для всех держателей, пока процесс не перезапустится на починенном или восстановленном файле. Не запускайте hermes doctor --fix, пока шлюз ещё работает. Дальнейшие шаги:

hermes gateway stop
HERMES_HOME="$HOME/.hermes" hermes sessions recover --source "$HOME/.hermes/state.db" --inspect-only
# если восстановление возможно:
HERMES_HOME="$HOME/.hermes" hermes sessions recover --source "$HOME/.hermes/state.db" --output "$HOME/recovered-state.db"

либо восстановите самый свежий снимок из state-snapshots/.

Явная починка

Остановите каждый процесс, который может открыть базу профиля, прежде чем её чинить. Держите их остановленными на всё время починки и проверки.

hermes gateway stop
HERMES_HOME="$HOME/.hermes" hermes sessions repair --check-only
HERMES_HOME="$HOME/.hermes" hermes sessions repair

sessions repair по умолчанию создаёт резервную копию SQLite и выполняет структурную работу через защищённый путь репозитория «снимок и продвижение». Не копируйте state.db, state.db-wal и state.db-shm по отдельности через cp: эти файлы — один живой образ SQLite.

После починки и до перезапуска шлюза проверьте пробу здоровья, маркер устаревания, набор триггеров и число канонических строк:

HERMES_HOME="$HOME/.hermes" hermes sessions repair --check-only
sqlite3 "$HOME/.hermes/state.db" \
  "SELECT key, value FROM state_meta WHERE key = 'fts_stale';"
sqlite3 "$HOME/.hermes/state.db" \
  "SELECT type, name FROM sqlite_master WHERE name IN
   ('messages_fts_insert','messages_fts_update','messages_fts_delete')
   ORDER BY name;"
sqlite3 "$HOME/.hermes/state.db" \
  "SELECT 'sessions', COUNT(*) FROM sessions
   UNION ALL SELECT 'messages', COUNT(*) FROM messages;"

Запрос маркера не должен вернуть ни одной строки, ожидаемые триггеры FTS должны присутствовать, а число канонических строк не должно уменьшиться. Если починка не удалась, сохраните и живую базу, и указанную резервную копию; никогда не удаляйте канонические строки ради того, чтобы исчезла ошибка производного индекса.

перевод и редактура:

обновлено

⌕ ESC