Daemon job "backup" (online copy, quick_check, rotation), restore of the newest valid copy when the database cannot be opened, change journal reset after restore, last_restore in /health, docs and tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.4 KiB
План: резервные копии базы и автовосстановление (доработка Б, п. 1.3-1.4)
Цель
База ripe.db копируется автоматически по расписанию, а при порче файла сервис сам поднимает последнюю исправную копию вместо пустой базы (риск 3 анализа). Откат к копии заметен в логе и в /health, а не происходит молча.
Дизайн
Резервное копирование
- Задание
backupв демоне (третье послеasnиfqdn, те же расписание, состояние вstatus.json, защита от наложения). Расписание:schedule.backupвconfig.json, по умолчанию30 4 * * *; менять можно черезPOST /schedule(типbackup). - Как копируем: онлайн-копия средствами SQLite (
Connection.backup), безопасная при работающих API и сборщике (WAL, чтение снимка). Файл сначала пишется как*.tmp, затем переименовывается: неполная копия не появляется. - Проверка: перед копированием
PRAGMA quick_checkживой базы, после копирования она же на копии. Если проверка не пройдена, копия не создаётся или удаляется, старые хорошие копии не трогаются, задание получаетlast_error,/healthпоказываетdegraded. - Хранение:
RIPE_BACKUP_DIR(по умолчанию<DATA_DIR>/backups), файлыripe-<UTC-время>.db. Ключbackup_keepвconfig.json(по умолчанию 7): лишние старые копии удаляются только после успешной новой. - Ограничение: по умолчанию копии лежат на том же томе
/data, что и база. Это защищает от порчи файла, но не от потери тома. Вынос на отдельный том или хост описывается в README (переменнаяRIPE_BACKUP_DIRи копирование каталога), не автоматизируется.
Автовосстановление
- Где: в
db.connect(), там, где сейчас порченая база убирается в*.corrupt-*. После этого берётся самая свежая копия, проходящаяquick_check; копия кладётся на место базы (через временный файл иos.replace), соединение открывается заново. Если исправных копий нет, поведение прежнее:StorageError, API отвечает 503. - Гонка API и демона: оба процесса могут обнаружить порчу одновременно. Восстановление идёт под файловой блокировкой (
file_lock), внутри блокировки база проверяется повторно: если другой процесс уже восстановил, повторных действий нет. - Курсоры diff после отката: журнал изменений откатывается вместе с базой, и номера записей после точки копии могли уже быть выданы клиентам. Чтобы старые курсоры не совпали с новой историей, после восстановления счётчик журнала сдвигается на 1 000 000 и «горизонт» ставится на новое значение (время горизонта = момент восстановления). Все прежние курсоры и времена дают
410, клиенты делают полную выгрузку. Это эвристика: она надёжна, пока между копиями накапливается меньше миллиона изменений (при суточной копии и редких изменениях RIPE запас большой). - Заметность: запись в лог уровня ERROR и файл
last_restore.json(время, использованная копия, путь карантина);/healthпоказывает полеlast_restore. Статусdegradedвосстановление не вызывает (сервис работает), но событие не теряется. - Ограничение: автоматически ловится порча, обнаруженная при открытии базы. Порча страниц внутри файла проявится при чтении (ответ 503) и будет замечена заданием
backup(quick_checkне пройдёт,/health=degraded); восстановление в этом случае ручное (порядок в README).
Что не входит
Копии вне сервера, шифрование, восстановление на момент времени, команда CLI restore, ручной запуск копии через POST /collect (можно добавить отдельно).
Изменения
cidr_collector.py:BACKUP_DIR,RESTORE_FILE,DEFAULT_BACKUP_KEEP.db.py:backup_database()(копия, проверка, ротация),_restore_latest_backup()под блокировкой, вызов изconnect(), сдвиг журнала.collector_daemon.py: заданиеbackup(реестрCOLLECTORSстановится реестром заданий с вызываемыми объектами,DEFAULT_CRONS["backup"]).api_server.py:ScheduleUpdate.typeдопускаетbackup;/healthпоказываетlast_restore.README.md: расписание и ключи (schedule.backup,backup_keep),RIPE_BACKUP_DIR, ручное восстановление, вынос копий с тома.- Тесты (2): копия проходит проверку, ротация оставляет
backup_keepштук, повреждённая живая база копию не создаёт; порченая база при открытии восстанавливается из копии (карантин сохранён, старые курсоры дают410), без копий остаётсяStorageError.
Проверка
Тесты в контейнере. Вручную на копии данных: задание backup создаёт файл, порча ripe.db (запись мусора) при работающих API и демоне приводит к восстановлению, /health показывает last_restore, запрос since со старым курсором даёт 410. Проверка в Docker Compose (общий том).
Откат
Удалить задание backup и вызов восстановления в connect(): схема базы не меняется (журнал и meta остаются), файлы копий можно удалить вручную.