{ "index": 259, "slug": "editorial-2020-10-field-backup-recovery", "title": "Резервная копия не равна восстановлению: как проверить backup без риска для источника", "excerpt": "Архив можно увидеть и скачать, но это ещё не доказательство восстановления. Разбираем manifest, checksum, область данных, изолированную цель и проверку результата на учебном PostgreSQL-сценарии.", "contentHtml": "
Симптом знакомый: в хранилище лежит свежий архив, команда видит его размер и дату, но никто не может уверенно сказать, что произойдёт после restore. Неясно, какие схемы попали в файл, совпадают ли его байты с теми, что указаны в журнале, куда пойдёт восстановление и чем считать результат успешным.
Цена ошибки проявляется в аварии. Оператор выбирает архив по имени, запускает привычную команду и обнаруживает неполный набор таблиц. Или восстанавливает данные поверх источника. В этот момент резервная копия не помогает: она превращается в ещё один объект, которому нужно доверять без проверки.
\nТезис статьи простой: backup готов к применению только тогда, когда команда может проверить его scope, целостность, безопасность цели и смысл результата. Наличие файла доказывает наличие файла. Оно не доказывает, что приложение сможет продолжить работу. Учебный маршрут ниже показывает способ проверки, но не выдаёт учебный результат за production-drill.
\nВосстановление состоит из нескольких независимых вопросов. Первый — что архив выбран правильно. Второй — что его содержимое не изменилось после создания. Третий — что в него попала нужная область базы. Четвёртый — что команда не затронет источник. Пятый — что после восстановления появились ожидаемые схемы, таблицы и данные.
\nЭти вопросы связывает короткий manifest. В нём хранят идентификатор архива, формат, область данных, исключения, SHA-256 и проверки результата. Manifest не заменяет сам архив и не делает восстановление автоматическим. Он задаёт договор, с которым можно сравнить фактический файл и фактический кандидат.
\n{\n "manifestVersion": 1,\n "backupId": "training-catalog-2020-10-a",\n "artifact": {\n "file": "training-catalog.dump",\n "format": "pg_dump custom (-Fc)",\n "sha256": "<hash>"\n },\n "scope": {\n "includes": ["schema:catalog", "schema:reference"],\n "excludes": ["cluster roles", "tablespaces", "external files"]\n },\n "restoreChecks": {\n "relations": ["catalog.items", "reference.codes"],\n "rows": { "catalog.items": 3, "reference.codes": 2 }\n },\n "safety": "isolated training candidate only"\n}\nПример учебный. Имена, числа и значение хеша вымышлены. Поле excludes здесь не декоративно: логический dump одной базы не следует выдавать за копию всего кластера. Роли, tablespaces и внешние файлы требуют отдельного решения и отдельной проверки. artifact.sha256 относится к файлу до восстановления, а restoreChecks — к базе после него; один флаг complete: true их не заменяет.
SHA-256 помогает ответить на узкий вопрос: совпадают ли байты фактического файла с ожидаемым digest. Если значение изменилось, файл нельзя считать тем же артефактом. Причиной может быть неполная передача, повреждение, подмена или выбор другого файла под похожим именем.
\nНо одинаковый digest не доказывает правильный scope и не подтверждает происхождение файла. Можно безошибочно сохранить неполный dump и получить идеальное совпадение checksum. Поэтому проверка идёт в два слоя: сначала bytes, потом содержимое архива и результат восстановления. Алгоритм хеширования подтверждает вычисленное значение, а не полноту данных.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| SHA-256 не совпал | Файл изменился, повреждён или выбран не тот артефакт | Повторить digest и сверить путь с manifest | Остановить restore, получить архив из доверенного источника |
| Архив читается, нужной relation нет | Scope слишком узкий или выбран другой dump | Сравнить pg_restore --list с scope.includes | Исправить путь создания или выпустить новую версию manifest |
| Роль отсутствует после restore | Cluster-wide объект не входил в dump базы | Проверить scope.excludes и список требуемых ролей | Восстановить роли отдельным согласованным шагом или изменить контракт |
| Restore завершился, таблица пуста | Dump неполный, выбран не тот объект или check не соответствует данным | Выполнить безопасный row count и сверить его с manifest | Остановить ввод данных, найти расхождение до переключения |
| Команда направлена в рабочую базу | Не доказана изоляция цели | Проверить адрес, имя и отдельные credentials кандидата | Не запускать restore; создать и явно подтвердить безопасную цель |
Таблица задаёт отрицательный путь. Любое расхождение переводит операцию в остановку. Нельзя продолжать до следующего шага только потому, что архив «свежий» или команда раньше уже работала. Причина должна попасть в запись проверки вместе с действием, которое вернёт маршрут к доверенному состоянию.
\nДля custom archive сначала зафиксируйте исходную базу и scope, затем создайте артефакт и manifest. pg_restore --list читает оглавление non-plain archive и не подключается к базе, если не указан --dbname. Список нужно сравнить с manifest, а не с памятью оператора. Если ожидалась reference.codes, а её нет, повторный запуск restore не добавит объект.
# Учебные имена. Команды этой статьёй не выполнялись.\npg_dump --format=custom --file=training-catalog.dump training_catalog\n# Linux: sha256sum; macOS: shasum -a 256.\nsha256sum training-catalog.dump\npg_restore --version\npg_restore --list training-catalog.dump\nКоманда pg_dump здесь создаёт dump базы, но не доказывает, что в scope вошли все зависимости приложения. После её выполнения digest и список объектов нужно записать в manifest. На macOS вместо sha256sum используйте shasum -a 256; смешивать вывод двух утилит с разными файлами нельзя.
Цель должна быть отдельной базой или отдельным кластером с понятным именем, доступом и запретом на перезапись источника. Новый кандидат удобно создать из template0, чтобы локальные добавления в template1 не внесли неожиданные объекты. Перед последней командой проверьте подключение, адрес, credentials и отсутствие рабочих данных.
# Только после проверки адреса и прав кандидата.\ncreatedb --template=template0 training_restore_candidate\npg_restore --exit-on-error --no-owner \\\n --dbname=training_restore_candidate training-catalog.dump\npsql --dbname=training_restore_candidate --command="SELECT to_regclass('catalog.items'), to_regclass('reference.codes');"\npsql --dbname=training_restore_candidate --command='SELECT count(*) FROM catalog.items;'\nФлаг --exit-on-error делает ранний отказ заметнее, но не превращает код выхода в доказательство полноты. --no-owner полезен для учебного кандидата, где исходные роли не создавались; в рабочем сценарии владение объектами нужно согласовать отдельно. Сначала проверьте наличие relations, затем выполните row count и другие read-only checks. Если relation отсутствует, запрос к ней должен дать отрицательный результат, а не повод вручную добавить строки.
template0. Проверьте адрес, credentials и отсутствие маршрута записи в источник.Положительный сценарий отвечает только на вопрос «маршрут работает». Для baseline важнее сохранить отказ там, где контроль должен сработать. Проверяйте четыре отказа: изменённый файл отклоняется по 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. Версии PostgreSQL и клиентских утилит нужно записать рядом с результатом: доступные опции и поведение могут различаться между поддерживаемыми версиями.
\nКритерий готовности проверяемый: для конкретного manifest другой оператор может найти нужный архив, подтвердить его digest, увидеть заявленные объекты, восстановить его только в изолированной цели и получить ожидаемые checks. При изменённом файле, неполном scope, неизвестной цели или расхождении результата маршрут останавливается с понятной причиной. Пока это не доказано повторяемым drill, в наличии есть файл, но нет подтверждённой резервной копии.
\n--list, --exit-on-error, --no-owner, восстановление в новую базу и ограничения выборочного restore.