diff --git a/editorial/agent-rewrites/261.json b/editorial/agent-rewrites/261.json index 1fe77be..98c5112 100644 --- a/editorial/agent-rewrites/261.json +++ b/editorial/agent-rewrites/261.json @@ -3,5 +3,5 @@ "slug": "editorial-2020-10-practice-backup-recovery", "title": "Резервная копия не равна восстановлению: как проверить PostgreSQL-архив", "excerpt": "Файл backup может существовать, читаться и всё равно не содержать нужный объём данных. Разбираем scope, checksum и безопасный restore в изолированную базу.", - "contentHtml": "
Ночью задача резервного копирования завершилась без ошибки. Утром в хранилище появился файл с сегодняшней датой. Это хороший признак, но не доказательство восстановления. Пока никто не прочитал архив, не сверил его состав и не поднял копию в отдельной базе, команда знает только одно: программа записала какой-то файл.
\nЦена ошибки проявится во время сбоя. Архив может содержать одну схему вместо двух, не включать роль или зависимость, а команда восстановления может указывать на исходную базу. Тогда оператор теряет время, рискует перезаписать рабочие данные и узнаёт границы копии в худший момент. Тезис статьи простой: backup считается пригодным не по имени файла и не по коду выхода команды, а по проверяемому маршруту от scope до безопасного restore.
\nРезервная копия отвечает на вопрос «что удалось сохранить». Восстановление отвечает на другой вопрос: «можно ли из этих байтов собрать нужный набор объектов в разрешённой цели». Между вопросами стоят формат архива, версия инструмента, зависимости, права, роли, tablespaces и внешние файлы.
\nВ этом примере рассматриваем только логический dump одной учебной базы PostgreSQL. Нужный набор состоит из схем catalog и reference. Роли кластера, tablespaces и файлы вне базы в scope не входят. Если scope не записан заранее, после сбоя легко принять неполную копию за полную.
У каждой копии должна быть граница. Запишите базу, формат, включённые схемы и исключения до запуска dump. Поле complete без расшифровки почти бесполезно: оно сообщает мнение оператора, но не показывает, какие объекты вошли в архив.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Файл есть, но ожидаемой таблицы нет в списке | Выбран не тот архив или слишком узкий scope | pg_restore --list и manifest | Остановить restore и исправить scope |
| Checksum не совпал | Файл изменился, обрезан или перепутан | Повторно вычислить SHA-256 тех же байтов | Не восстанавливать; получить архив заново |
| Restore завершился, но схема пустая | Dump не включил зависимость или данные | Проверить scope, список объектов и row count | Расширить scope или изменить контракт |
| Команда требует роль или tablespace | Объект относится к кластеру, а не только к базе | Сверить область dump и требования target | Подготовить отдельную проверку |
| Цель не удаётся отличить от источника | В runbook нет защитной границы | Проверить имя и подключение | Не запускать restore до создания кандидата |
Таблица задаёт порядок мышления. Каждый симптом получает собственную проверку. Размер файла не заменяет ни одну из них: большой архив может быть неполным, а маленький — правильным для узкого scope.
\nПоложите рядом с архивом manifest. В нём достаточно хранить идентификатор копии, имя и формат файла, размер, SHA-256, includes, excludes и проверки после восстановления. Не записывайте туда пароль, секрет или рабочую строку подключения. Manifest описывает артефакт и ожидаемое доказательство, а не выдаёт доступ.
\nmanifestVersion: 1\nbackupId: training-catalog-2020-10-a\nartifact.file: training-catalog-2020-10.dump\nartifact.format: pg_dump custom (-Fc)\nartifact.sha256: учебное значение после создания\nscope.includes: schema:catalog, schema:reference\nscope.excludes: cluster roles, tablespaces, external files\nrestoreChecks: catalog.items, reference.codes\nsafety: только изолированная учебная база\nПоле sha256 отвечает только за целостность байтов. Совпадение hash не говорит, что архив содержит нужные данные. Для этого manifest хранит отдельные structural checks: ожидаемые relations, версию схемы и безопасные счётчики строк. Такой разрыв показывает, на каком шаге сломался маршрут.
Команды ниже демонстрируют форму проверки. Они не запускались против рабочей базы. Имена training_catalog, training-catalog-2020-10.dump и все данные в примере учебные. Перед применением нужно выбрать разрешённую аутентификацию, проверить версию клиента и убедиться, что процесс не получает больше прав, чем требуется.
# Учебные команды, без адреса и секретов окружения\npg_dump --format=custom --file=training-catalog-2020-10.dump training_catalog\nshasum -a 256 training-catalog-2020-10.dump\npg_restore --list training-catalog-2020-10.dump\nФормат custom предназначен для чтения через pg_restore. Если используется plain SQL, маршрут будет другим. Нельзя взять команду для одного формата и считать её универсальной. Версию pg_dump фиксируйте рядом с manifest: совместимость клиента, сервера и целевой базы нужно проверять в конкретной среде.
Список из pg_restore --list — ранняя остановка. Он помогает заметить архив другой базы, неожиданное имя схемы или отсутствующую relation до подключения к кандидату. Но список не доказывает, что данные восстановятся и что приложение сможет работать. После него нужен отдельный restore и проверки результата.
Безопасная цель должна иметь отдельное имя, отдельное подключение и понятного владельца. В учебном маршруте она называется training_restore_candidate. Перед командой оператор проверяет, что подключение не ведёт к источнику. Если это нельзя доказать, восстановление не начинают.
# Только учебный изолированный кандидат\ncreatedb training_restore_candidate\npg_restore --dbname=training_restore_candidate training-catalog-2020-10.dump\npsql --dbname=training_restore_candidate --command='SELECT count(*) FROM catalog.items;'\npsql --dbname=training_restore_candidate --command='SELECT count(*) FROM reference.codes;'\nЭти запросы дают только учебный пример. Они не сообщают реальное время восстановления, полноту кластера или готовность приложения. В рабочем проекте критерии подбирают по контракту данных: проверяют существование критичных relations, версию схемы, небольшой синтетический набор или чтение безопасного reference-объекта. Запрос не должен менять источник, отправлять побочный эффект во внешнюю систему или требовать ресурс, которого нет в заявленном scope.
\npg_restore; изменённый файл нельзя проверять восстановлением.Если hash не совпал, проблема находится до восстановления. Не меняйте параметры target и не запускайте архив «на удачу». Получите файл заново, проверьте источник и обновите manifest только после подтверждения.
\nЕсли hash совпал, но в оглавлении нет reference.codes, байты целы, а scope неверен для заявленной цели. Checksum сработал правильно: он не обязан обнаруживать отсутствие объекта. Исправьте команду dump или измените контракт восстановления. Не добавляйте недостающую таблицу вручную и не называйте такой результат полным restore.
Если restore завершился, но row count или версия схемы не совпали, target получил результат, который не отвечает контракту. Сохраните симптом, проверьте миграции и повторите тест с исправленным архивом. Если цель оказалась исходной базой, остановите процедуру и разберите права и runbook до следующей попытки.
\nЭта практика не проверяет point-in-time recovery, непрерывное архивирование WAL, retention, шифрование, резервирование самого хранилища, кластерные роли, tablespaces или файлы вне PostgreSQL. Логический dump одной базы не становится копией всего кластера. Учебный кандидат не даёт production-метрик и не заменяет аварийное упражнение с согласованным окном.
\nПервый drill можно считать завершённым, если сохранены четыре независимых факта: scope совпал с целью; checksum совпал с manifest; архив содержит ожидаемые объекты; restore в изолированный кандидат прошёл заранее заданные безопасные checks. В журнале также перечислены исключения и версия инструмента. Если хотя бы один факт отсутствует, статус должен быть «проверка не завершена», а не «backup готов».
\nНочью задача резервного копирования завершилась без ошибки. Утром в хранилище появился файл с сегодняшней датой. Это хороший признак, но не доказательство восстановления. Пока никто не прочитал архив, не сверил его состав и не поднял копию в отдельной базе, команда знает только одно: программа записала какой-то файл.
\nЦена ошибки проявится во время сбоя. Архив может содержать одну схему вместо двух, не включать роль или зависимость, а команда восстановления может указывать на исходную базу. Тогда оператор теряет время, рискует перезаписать рабочие данные и узнаёт границы копии в худший момент. Backup считается пригодным не по имени файла и не по коду выхода команды, а по маршруту от scope до безопасного restore.
\nРезервная копия отвечает на вопрос «что удалось сохранить». Восстановление отвечает на другой вопрос: «можно ли из этих байтов собрать нужный набор объектов в разрешённой цели». Между вопросами стоят формат архива, версия инструмента, зависимости, права, роли, tablespaces и внешние файлы.
\nВ этом примере рассматриваем выборочный логический dump учебной базы PostgreSQL 13. Нужный набор состоит из схем catalog и reference. Роли кластера, tablespaces и файлы вне базы в scope не входят. Параметр --schema выбирает объекты схемы, но pg_dump не добавляет зависимости за пределами выбранных схем. Поэтому такой dump допустим только после проверки замыкания зависимостей (dependency closure): если таблицам нужны типы, функции или расширения из другой схемы, их добавляют в scope либо делают dump всей базы.
У каждой копии должна быть граница. Запишите базу, формат, включённые схемы, зависимости и исключения до запуска dump. Поле complete без расшифровки почти бесполезно: оно сообщает мнение оператора, но не показывает, какие объекты вошли в архив.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Файл есть, но ожидаемой таблицы нет в списке | Выбран не тот архив или слишком узкий scope | pg_restore --list и manifest | Остановить restore и исправить scope |
| Checksum не совпал | Файл изменился, обрезан или перепутан | Повторно вычислить SHA-256 тех же байтов | Не восстанавливать; получить архив заново |
| Restore завершился, но ожидаемая таблица пуста | Dump не включил данные, зависимость или нужную версию схемы | Проверить scope, список объектов и контрольный эталонный набор | Расширить scope или изменить контракт |
| Команда требует роль или tablespace | Объект относится к кластеру, а не только к базе | Сверить область dump и требования target | Подготовить отдельную проверку |
| Цель не удаётся отличить от источника | В runbook нет защитной границы | Проверить имя и подключение | Не запускать restore до создания кандидата |
Таблица задаёт порядок мышления. Каждый симптом получает собственную проверку. Размер файла не заменяет ни одну из них: большой архив может быть неполным, а маленький — правильным для узкого scope.
\nПоложите рядом с архивом manifest. В нём достаточно хранить идентификатор копии, имя и формат файла, размер, SHA-256, major-версии клиента и сервера, includes, excludes и проверки после восстановления. Не записывайте туда пароль, секрет или рабочую строку подключения. Manifest описывает артефакт и ожидаемое доказательство, а не выдаёт доступ.
\nmanifestVersion: 1\nbackupId: training-catalog-2020-10-a\nsource.server.major: 13\nclient.pg_dump.major: 13\ntarget.server.major: 13\nartifact.file: training-catalog-2020-10.dump\nartifact.format: pg_dump custom (-Fc)\nartifact.sha256: значение из shasum -a 256\nscope.includes: schema:catalog, schema:reference\nscope.excludes: cluster roles, tablespaces, external files\nrestoreChecks: catalog.items, reference.codes, counts match training baseline\nsafety: только изолированная учебная база\nПоле sha256 отвечает только за целостность байтов. Совпадение hash не говорит, что архив содержит нужные данные. Для этого manifest хранит отдельные структурные проверки (structural checks): ожидаемые relations, версию схемы и безопасные счётчики строк. Счётчики сравнивают с заранее согласованным эталонным учебным набором, а не с непроверенным числом из рабочей базы. Такой разрыв показывает, на каком шаге сломался маршрут.
Команды ниже демонстрируют выборочный dump схем catalog и reference. Они не запускались против рабочей базы. Имена training_catalog, training-catalog-2020-10.dump и все данные в примере учебные. Перед применением нужно выбрать разрешённую аутентификацию, проверить замыкание зависимостей (dependency closure) и убедиться, что процесс не получает больше прав, чем требуется.
# Учебные команды, без адреса и секретов окружения\npg_dump --version\npsql --dbname=training_catalog --command='SHOW server_version;'\npg_dump --format=custom --schema=catalog --schema=reference --file=training-catalog-2020-10.dump training_catalog\nshasum -a 256 training-catalog-2020-10.dump\npg_restore --list training-catalog-2020-10.dump\nВ примере shasum показан для macOS; в Linux используйте эквивалент sha256sum. Сохраняйте вывод команды, а затем переносите именно его контрольную сумму в manifest. Для PostgreSQL 13 клиент pg_dump не может снять dump с сервера более новой major-версии; dump может загружаться на более новую версию, но загрузка в более старую не гарантируется. Поэтому в manifest нужны обе версии и отдельная проверка целевой базы.
Формат custom предназначен для чтения через pg_restore. Если используется plain SQL, маршрут будет другим. Список из pg_restore --list — ранняя остановка: он помогает заметить архив другой базы, неожиданное имя схемы или отсутствующую relation до подключения к кандидату. Но список не доказывает, что данные восстановятся и что приложение сможет работать. После него нужен отдельный restore и проверки результата.
Безопасная цель должна иметь отдельное имя, отдельное подключение и понятного владельца. В учебном маршруте она называется training_restore_candidate. Перед командой оператор проверяет, что подключение не ведёт к источнику. Если это нельзя доказать, восстановление не начинают. Dump от недоверенного источника сначала нужно вывести в SQL через pg_restore --file=training-catalog-2020-10.sql training-catalog-2020-10.dump и проверить: документация PostgreSQL предупреждает, что restore выполняет на цели код, сформированный суперпользователями источника.
# Только учебный изолированный кандидат\ncreatedb --template=template0 training_restore_candidate\npg_restore --exit-on-error --dbname=training_restore_candidate training-catalog-2020-10.dump\npsql --dbname=training_restore_candidate --command='SELECT count(*) FROM catalog.items;'\npsql --dbname=training_restore_candidate --command='SELECT count(*) FROM reference.codes;'\nЭти запросы дают только учебный пример: их значения должны совпадать с ожидаемыми значениями эталонного учебного набора, а не с произвольным ожиданием оператора. Они не сообщают реальное время восстановления, полноту кластера или готовность приложения. В рабочем проекте критерии подбирают по контракту данных: проверяют существование критичных relations, версию схемы, небольшой синтетический набор или чтение безопасного reference-объекта. Запрос не должен менять источник, отправлять побочный эффект во внешнюю систему или требовать ресурс, которого нет в заявленном scope.
\npg_restore; изменённый файл нельзя проверять восстановлением.template0. Проверьте имя цели, права и отсутствие маршрута записи в исходную базу.Если hash не совпал, проблема находится до восстановления. Не меняйте параметры target и не запускайте архив «на удачу». Получите файл заново, проверьте источник и обновите manifest только после подтверждения.
\nЕсли hash совпал, но в оглавлении нет reference.codes, байты целы, а scope неверен для заявленной цели. Checksum сработал правильно: он не обязан обнаруживать отсутствие объекта. Исправьте команду dump или измените контракт восстановления. Не добавляйте недостающую таблицу вручную и не называйте такой результат полным restore.
Если restore завершился, но row count или версия схемы не совпали с эталонным учебным набором, target получил результат, который не отвечает контракту. Сохраните симптом, проверьте миграции и повторите тест с исправленным архивом. Если цель оказалась исходной базой, остановите процедуру и разберите права и runbook до следующей попытки.
\nЭта практика не проверяет point-in-time recovery, непрерывное архивирование WAL, retention, шифрование, резервирование самого хранилища, кластерные роли, tablespaces или файлы вне PostgreSQL. Логический dump одной базы не становится копией всего кластера. Выборочный dump схем не переносит зависимости автоматически; при неполном замыкании зависимостей нужен полный dump или явно расширенный scope. Учебный кандидат не даёт production-метрик и не заменяет аварийное упражнение с согласованным окном.
\nПервый drill можно считать завершённым, если сохранены четыре независимых факта: scope совпал с целью и замыкание зависимостей проверено; checksum совпал с manifest; архив содержит ожидаемые объекты; restore в изолированный кандидат прошёл заранее заданные безопасные checks по эталонному учебному набору. В журнале также перечислены исключения и major-версии клиента и сервера. Если хотя бы один факт отсутствует, статус должен быть «проверка не завершена», а не «backup готов».
\n--exit-on-error и предупреждение о недоверенном содержимом.