diff --git a/editorial/agent-rewrites/259.json b/editorial/agent-rewrites/259.json index 124ce20..a04c1d5 100644 --- a/editorial/agent-rewrites/259.json +++ b/editorial/agent-rewrites/259.json @@ -3,5 +3,5 @@ "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Симптом знакомый: в хранилище лежит свежий архив, команда видит его размер и дату, но никто не может уверенно сказать, что произойдёт после 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.