When ripe.db is corrupted and no valid backup exists, a new empty database is created with a db_recreated.json marker; /addresses and /addresses/diff answer 503 until the collector gathers data again, /health reports db_recreated. Tests now isolate all state files via conftest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.1 KiB
План: защита от пустой выдачи после потери базы (находка 1 ревью)
Источник: docs/review-2026-09-21.md, находка 1 (высокая); риск 3 анализа.
Проблема
Если ripe.db испорчена и исправной копии нет, первый запрос получает 503, а затем создаётся новая пустая база, и /addresses отвечает 200 []. Потребитель (роутер, файрвол), забирающий список по расписанию, может принять пустой список за истину и стереть свои правила. /health при живом демоне показывает ok при counts = 0.
Дизайн
- Признак потери. Когда база пересоздана из-за порчи без копий,
db._recoverпишет файл-меткуdb_recreated.jsonвDATA_DIR(время, путь карантина, текст ошибки). Метка ставится только в этом случае: чистая установка (базы ещё не было) и восстановление из копии её не создают. - Сразу рабочее соединение.
_recoverбольше не бросаетStorageErrorв этом случае, а создаёт новую базу и возвращает соединение (демон продолжает сбор,/healthи записи работают). Журнал изменений новой базы сдвигается так же, как при восстановлении из копии (RESTORE_JOURNAL_JUMP): курсоры старой базы дают410. Общий код сброса журнала выносится из_restore_backup. - Что блокируется. Пока данные не собраны заново,
GET /addressesиGET /addresses/diffотвечают503с заголовкомRetry-Afterи пояснением; остальные эндпоинты работают. Проверка идёт по запрошенным типам:- блокируется тип, для которого в
config.jsonесть источники, но в базе по нему нет ни одного значения; - тип без источников (например, нет ни одного FQDN) не блокируется и отдаёт законный пустой список.
- блокируется тип, для которого в
- Снятие метки. В конце каждого запуска сбора (
ASN,FQDN, в том числе через CLI) вызываетсяdb.settle_recreated: если ни один сконфигурированный тип не пуст, файл-метка удаляется. Снять блокировку вручную (принять пустую выдачу) можно, удаливdb_recreated.json. /health. Полеdb_recreated:nullили{"at": ..., "pending": true|false}(без путей: это же закрывает связанную находку 6 для нового поля). Пока блокировка активна, статусdegraded.- Устойчивость. Проверка метки читает только файл и базу; если
config.jsonнечитаем, блокировка сохраняется (fail closed).
Изменения
cidr_collector.py:RECREATED_FILE, вызовdb.settle_recreatedв обоихrun_collection.db.py: метка и создание новой базы в_recover,_reset_journal(общий код с_restore_backup),recreated_pending(conn, kinds),settle_recreated(conn).api_server.py: проверка в/addressesи/addresses/diff(503 +Retry-After), полеdb_recreatedи статус в/health.README.md: раздел «Automatic restore» (поведение без копий, ручное снятие метки),/health..gitignore/.dockerignore:db_recreated.json.- Тесты (2 новых, один существующий обновляется; всего 24): БД (порча без копий: соединение рабочее, метка записана, старый курсор
410; метка снимается при появлении данных и не мешает, если источников нет); API (/addressesи/addresses/diffдают 503,/healthdegradedсdb_recreated, после появления данных 200 и метка снята; тип без источников не блокируется). Существующая проверка «без копий -> StorageError» вtest_restore_from_backupзаменяется на новое поведение.
Не входит
- Чистая установка без базы по-прежнему отдаёт пустой список (терять нечего).
- Удаление файла базы вручную не распознаётся как потеря (порчи нет): пустая база создаётся без метки. Возможное продолжение: при отсутствии базы и наличии копий восстанавливаться из них.
- Порча внутри файла, обнаруженная при чтении, по-прежнему даёт 503 и восстанавливается вручную.
Проверка
Тесты в контейнере; вручную в отдельных процессах: порча ripe.db без копий при работающих API и демоне -> /addresses 503, /health degraded, после сбора 200 с данными и метка удалена; повтор в Docker Compose (общий том).
Откат
Убрать проверку в API и запись метки в _recover (возврат к предыдущему коммиту): схема базы не меняется, файл db_recreated.json можно удалить.