Сохраняемые контрольные точки памяти для восстановления после сбоев
Узел ADAMANT теперь поддерживает сохраняемые, ротируемые контрольные точки производных состояний mem_*. После принудительного прерывания, оставляющего зеркала памяти в несогласованном состоянии, при запуске можно восстановить последнюю проверенную контрольную точку и воспроизвести только блоки после высоты этой контрольной точки, вместо перестройки всех таблиц памяти с высоты 1. Это реализация проекта из задачи #227, объединённой в pull request #261. Контрольные точки являются локальным кэшем восстановления; блоки и детерминированное воспроизведение по-прежнему остаются источником истины. Если проверка или воспроизведение завершатся неудачно, узел возвращается к существующему пути полной перестройки.
Производные таблицы, такие как mem_accounts, mem_round, таблицы делегатов и мультисиг-соединений, а также их неподтверждённые зеркала, могут стать несогласованными, если процесс будет завершён во время записи. Корректный путь эксплуатации по-прежнему требует завершения через SIGTERM, но контрольные точки сокращают время восстановления, если всё же произошло принудительное завершение.
Реализация вводит таблицу метаданных (mem_state_checkpoint_meta) и три набора ротируемых слотов таблиц (mem_ckpt_0..2_*) для подтверждённого состояния. Неподтверждённые таблицы соединений не сохраняются в контрольных точках; они перестраиваются из подтверждённого состояния при восстановлении. Основная логика разделена между logic/memCheckpoint.js для вычисления дайджеста и ротации слотов, modules/memCheckpoints.js как обёртка модуля, sql/memCheckpoints.js для вспомогательных SQL-функций, а также модификаций в modules/loader.js и modules/blocks/chain.js для запуска восстановления и создания контрольных точек.
Контрольные точки создаются только на границах завершённых раундов, после того как конвейер applyBlock полностью сохранил блок. На вершине цепочки это происходит каждый завершённый раунд. Во время синхронизации при догоняющем режиме это происходит каждый 100-й раунд, чтобы не снижать пропускную способность синхронизации. Создание контрольной точки использует транзакцию PostgreSQL с уровнем REPEATABLE READ, чтобы зафиксировать снимок MVCC. Критическая секция обработки блоков освобождается сразу после сохранения строки метаданных, в то время как копирование таблиц и вычисление дайджеста продолжаются в фоновом режиме по зафиксированному снимку. Это позволяет не удерживать критическую секцию на всём протяжении операции копирования.
Прежде чем контрольная точка будет принята для восстановления, проверяются несколько инвариантов: статус должен быть завершённым, схема и nethash должны совпадать, указанный блок должен существовать, а дайджест SHA-256 должен соответствовать. Восстановление проверяет все завершённые слоты в порядке от самого нового к самому старому, поэтому повреждённый самый новый слот не приводит к полной перестройке, если существует более старый, но корректный слот. При запуске, если checkMemTables() обнаруживает несогласованность, memCheckpoints.tryRecover() восстанавливает слот, сбрасывает неподтверждённое состояние, устанавливает последний блок и воспроизводит блоки от высоты контрольной точки до вершины. Если воспроизведение завершается неудачно, узел отбрасывает состояние контрольной точки и выполняет полную перестройку с genesis.
Функция включена по умолчанию в config.default.json:
"loading": {
"memCheckpoints": {
"enabled": true
}
}
Операторам следует учитывать, что это не вносит изменений в протокол; контрольные точки никогда не участвуют в консенсусе, и изменённые локальные данные не могут обойти проверку блоков. На mem_*-объёмах размером с основную сеть три слота требуют примерно 96–144 МБ плюс метаданные, поэтому рекомендуется резервировать около 1 ГБ свободного места. Операторам по-прежнему следует отдавать предпочтение корректному завершению работы, поскольку контрольные точки сокращают время восстановления, но не заменяют правильные процедуры завершения.