{ "index": 259, "slug": "editorial-2020-10-field-backup-recovery", "title": "Резервная копия не равна восстановлению: как проверить backup без риска для источника", "excerpt": "Архив можно увидеть и скачать, но это ещё не доказательство восстановления. Разбираем manifest, checksum, область данных, изолированную цель и проверку результата на учебном PostgreSQL-сценарии.", "contentHtml": "
Симптом знакомый: в хранилище лежит свежий архив, команда видит его размер и дату, но никто не может уверенно сказать, что произойдёт после restore. Неясно, какие схемы попали в файл, совпадают ли его байты с теми, что указаны в журнале, куда пойдёт восстановление и чем считать результат успешным.
Цена такой ошибки появляется в аварии. Оператор выбирает архив по имени, запускает привычную команду и обнаруживает неполный набор таблиц. Или восстанавливает данные поверх источника. В этот момент резервная копия не помогает: она превращается в ещё один объект, которому нужно доверять без проверки.
\nТезис статьи простой: backup готов к применению только тогда, когда команда может проверить его scope, целостность, безопасность цели и смысл результата. Наличие файла доказывает наличие файла. Оно не доказывает, что приложение сможет продолжить работу.
\nВосстановление состоит из нескольких независимых вопросов. Первый — что архив выбран правильно. Второй — что его содержимое не изменилось после создания. Третий — что в него попала нужная область базы. Четвёртый — что команда не затронет источник. Пятый — что после восстановления появились ожидаемые схемы, таблицы и данные.
\nЭти вопросы связывает короткий manifest. В нём хранят идентификатор архива, формат, область данных, исключения, SHA-256 и проверки результата. Manifest не заменяет сам архив и не делает восстановление автоматическим. Он задаёт договор, с которым можно сравнить фактический файл и фактический кандидат.
\n{\n "backupId": "training-catalog-2020-10-a",\n "format": "pg_dump custom",\n "artifact": {\n "file": "training-catalog.dump",\n "sha256": "..."\n },\n "scope": {\n "includes": ["schema:catalog", "schema:reference"],\n "excludes": ["cluster roles", "tablespaces", "external files"]\n },\n "checks": {\n "relations": ["catalog.items", "reference.codes"],\n "rows": { "catalog.items": 3, "reference.codes": 2 }\n },\n "target": "isolated training candidate only"\n}\nПример учебный. Имена, числа и значение хеша вымышлены. Поле excludes здесь не декоративно: логический dump одной базы не следует выдавать за копию всего кластера. Роли, tablespaces и внешние файлы требуют отдельного решения и отдельной проверки.
SHA-256 помогает ответить на узкий вопрос: совпадают ли байты фактического файла с ожидаемым digest. Если значение изменилось, файл нельзя считать тем же артефактом. Причиной может быть неполная передача, повреждение, подмена или выбор другого файла под похожим именем.
\nНо одинаковый digest не доказывает правильный scope. Можно безошибочно сохранить неполный dump и получить идеальное совпадение checksum. Поэтому проверка идёт в два слоя: сначала bytes, потом содержимое архива и результат восстановления.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Архив есть, но scope не описан | Файл создавали без явного договора | Сверить список ожидаемых схем и исключений с командой создания | Остановить drill и уточнить scope |
| SHA-256 не совпадает | Выбран другой или повреждённый файл | Заново вычислить digest фактического файла | Не читать и не восстанавливать файл; получить доверенный артефакт |
| В списке нет нужной relation | Она не попала в dump или фильтр сузил область | Сравнить pg_restore --list с manifest | Исправить создание архива или оформить зависимость отдельно |
| Цель совпадает с источником | Runbook не закрепил изоляцию | Проверить имя базы и запрет перезаписи до команды | Не запускать; создать отдельного кандидата |
| Restore завершился, но таблица пуста | Неполный scope, неверная версия или слабый check | Выполнить read-only запросы к кандидату и сравнить expected rows | Зафиксировать отрицательный verdict и исправить договор |
Для учебного custom archive можно сначала получить список объектов, не меняя базу. Команда pg_restore --list показывает, что инструмент видит внутри non-plain archive. Этот список нужно сравнить с manifest, а не с памятью оператора. Если ожидалась reference.codes, а её нет, повторный запуск restore не добавит объект.
# Учебные имена. Команды не выполнялись этой статьёй.\nsha256sum training-catalog.dump\npg_restore --list training-catalog.dump\n\n# Только после проверок — отдельный кандидат.\ncreatedb training_restore_candidate\npg_restore --dbname=training_restore_candidate training-catalog.dump\nПеред последней командой нужно подтвердить, что training_restore_candidate не является источником и не содержит нужных рабочих данных. Если безопасной цели нет, правильный результат — «restore не проверен». Нельзя превращать отсутствие стенда в разрешение на риск.
После восстановления проверяют не только код возврата процесса. Нужны конкретные read-only проверки: существуют ли ожидаемые схемы, совпадает ли набор relations, имеют ли таблицы ожидаемый тип и не пусты ли обязательные справочники. Количество строк — учебный критерий, а не обещание для настоящей базы. В production оно меняется, поэтому ожидаемое значение должно зависеть от снимка и бизнес-условия.
\npg_restore.pg_restore --list и сравнить его с ожидаемыми relations.Хорошая проверка должна остановиться на плохом входе. Изменённый файл должен отклоняться по checksum. Суженный scope должен отклоняться по сравнению manifest и списка объектов. Источник должен быть недопустимой целью. Пустая relation должна приводить к отрицательному verdict, если приложение требует данные.
\nНельзя исправлять отрицательный результат подменой evidence. Не следует менять хеш, чтобы пройти gate, добавлять строки вручную после восстановления или переписывать expected count после обнаружения расхождения. Эти действия скрывают причину и делают следующий запуск менее надёжным.
\nЛогический dump PostgreSQL покрывает не все способы восстановления. Копирование файлов кластера, continuous archiving и point-in-time recovery имеют другие предпосылки. Команда, которая проверила custom archive одной базы, не доказала восстановление всего кластера и не измерила готовность приложения.
\nУчебный сценарий не проверяет сеть, права, секреты, размер production-данных, скорость передачи, репликацию, WAL, внешние object storage и работу зависимых сервисов. Он также не даёт RPO или RTO. Время учебной команды становится RTO только после измерения на разрешённой цели с описанными условиями. Дата архива сама по себе не является RPO.
\nЕсли drill не может затронуть реальное окружение, это не недостаток статьи. Нужно отделить проверенный учебный контракт от непроверенной инфраструктуры и назначить следующий безопасный эксперимент. Такой итог точнее, чем заявление о полной готовности к аварии.
\nМинимальный проверяемый критерий выглядит так: команда предъявляет manifest и фактический archive; SHA-256 совпадает; список объектов покрывает заявленный scope; restore выполнен только в изолированной цели; источник не изменился; структурные и смысловые checks прошли; время и версия инструмента записаны; непокрытые области явно перечислены.
\nЕсли хотя бы один пункт неизвестен, verdict должен быть ограниченным: «архив найден», «байты совпали» или «учебная база восстановилась». Нельзя сокращать его до «резервное копирование работает». Копия становится рабочим механизмом только там, где команда может повторить путь, увидеть отрицательный результат и безопасно остановиться.
\n