From 9775e0ffa26d9de7228531aa46690bd96387b81c Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Thu, 3 Sep 2026 21:46:44 +0300 Subject: [PATCH] editorial: polish PostgreSQL backup recovery article --- editorial/agent-rewrites/261.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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

Сначала фиксируем вопрос восстановления

\n

Резервная копия отвечает на вопрос «что удалось сохранить». Восстановление отвечает на другой вопрос: «можно ли из этих байтов собрать нужный набор объектов в разрешённой цели». Между вопросами стоят формат архива, версия инструмента, зависимости, права, роли, tablespaces и внешние файлы.

\n

В этом примере рассматриваем только логический dump одной учебной базы PostgreSQL. Нужный набор состоит из схем catalog и reference. Роли кластера, tablespaces и файлы вне базы в scope не входят. Если scope не записан заранее, после сбоя легко принять неполную копию за полную.

\n
Схема проверки резервной копии: scope, archive, manifest, checksum, изолированная цель и проверки после restore
Проверка начинается с описанного scope. Checksum проверяет байты архива, а запросы после restore проверяют состав результата.
\n

Scope отделяет нужную копию от похожего файла

\n

У каждой копии должна быть граница. Запишите базу, формат, включённые схемы и исключения до запуска dump. Поле complete без расшифровки почти бесполезно: оно сообщает мнение оператора, но не показывает, какие объекты вошли в архив.

\n
Диагностика backup и restore
СимптомПричинаПроверкаДействие
Файл есть, но ожидаемой таблицы нет в спискеВыбран не тот архив или слишком узкий scopepg_restore --list и manifestОстановить restore и исправить scope
Checksum не совпалФайл изменился, обрезан или перепутанПовторно вычислить SHA-256 тех же байтовНе восстанавливать; получить архив заново
Restore завершился, но схема пустаяDump не включил зависимость или данныеПроверить scope, список объектов и row countРасширить scope или изменить контракт
Команда требует роль или tablespaceОбъект относится к кластеру, а не только к базеСверить область dump и требования targetПодготовить отдельную проверку
Цель не удаётся отличить от источникаВ runbook нет защитной границыПроверить имя и подключениеНе запускать restore до создания кандидата
\n

Таблица задаёт порядок мышления. Каждый симптом получает собственную проверку. Размер файла не заменяет ни одну из них: большой архив может быть неполным, а маленький — правильным для узкого scope.

\n

Manifest связывает архив с ожидаемым результатом

\n

Положите рядом с архивом manifest. В нём достаточно хранить идентификатор копии, имя и формат файла, размер, SHA-256, includes, excludes и проверки после восстановления. Не записывайте туда пароль, секрет или рабочую строку подключения. Manifest описывает артефакт и ожидаемое доказательство, а не выдаёт доступ.

\n
manifestVersion: 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, версию схемы и безопасные счётчики строк. Такой разрыв показывает, на каком шаге сломался маршрут.

\n

Учебная последовательность создания архива

\n

Команды ниже демонстрируют форму проверки. Они не запускались против рабочей базы. Имена training_catalog, training-catalog-2020-10.dump и все данные в примере учебные. Перед применением нужно выбрать разрешённую аутентификацию, проверить версию клиента и убедиться, что процесс не получает больше прав, чем требуется.

\n
# Учебные команды, без адреса и секретов окружения\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: совместимость клиента, сервера и целевой базы нужно проверять в конкретной среде.

\n

Список из pg_restore --list — ранняя остановка. Он помогает заметить архив другой базы, неожиданное имя схемы или отсутствующую relation до подключения к кандидату. Но список не доказывает, что данные восстановятся и что приложение сможет работать. После него нужен отдельный restore и проверки результата.

\n

Restore выполняем только в изолированную цель

\n

Безопасная цель должна иметь отдельное имя, отдельное подключение и понятного владельца. В учебном маршруте она называется training_restore_candidate. Перед командой оператор проверяет, что подключение не ведёт к источнику. Если это нельзя доказать, восстановление не начинают.

\n
# Только учебный изолированный кандидат\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.

\n

Порядок первого restore drill

\n
  1. Назовите цель восстановления: какая база, схемы и данные должны появиться после restore. Отдельно запишите роли, tablespaces и внешние файлы, которые не проверяются.
  2. Создайте logical archive выбранным инструментом. Сохраните его формат, имя, размер, версию клиента и SHA-256 в manifest.
  3. Сверьте checksum. При расхождении остановитесь до pg_restore; изменённый файл нельзя проверять восстановлением.
  4. Прочитайте оглавление архива. Сопоставьте его с includes и ожидаемыми relations. Несовпадение означает проблему scope или источника.
  5. Подготовьте изолированного кандидата. Проверьте имя цели, права и отсутствие маршрута записи в исходную базу.
  6. Выполните restore в кандидате. Сохраните код выхода и диагностический вывод, но не считайте код 0 единственным доказательством.
  7. Запустите безопасные structural и semantic checks. Сравните результат с manifest и запишите, что именно не входило в проверку.
  8. Повторите drill после изменения команды, scope, версии инструмента или структуры базы. Старое доказательство не подтверждает новый маршрут.
\n

Отрицательный путь важнее зелёной отметки

\n

Если hash не совпал, проблема находится до восстановления. Не меняйте параметры target и не запускайте архив «на удачу». Получите файл заново, проверьте источник и обновите manifest только после подтверждения.

\n

Если hash совпал, но в оглавлении нет reference.codes, байты целы, а scope неверен для заявленной цели. Checksum сработал правильно: он не обязан обнаруживать отсутствие объекта. Исправьте команду dump или измените контракт восстановления. Не добавляйте недостающую таблицу вручную и не называйте такой результат полным restore.

\n

Если restore завершился, но row count или версия схемы не совпали, target получил результат, который не отвечает контракту. Сохраните симптом, проверьте миграции и повторите тест с исправленным архивом. Если цель оказалась исходной базой, остановите процедуру и разберите права и runbook до следующей попытки.

\n

Ограничения и критерий готовности

\n

Эта практика не проверяет point-in-time recovery, непрерывное архивирование WAL, retention, шифрование, резервирование самого хранилища, кластерные роли, tablespaces или файлы вне PostgreSQL. Логический dump одной базы не становится копией всего кластера. Учебный кандидат не даёт production-метрик и не заменяет аварийное упражнение с согласованным окном.

\n

Первый drill можно считать завершённым, если сохранены четыре независимых факта: scope совпал с целью; checksum совпал с manifest; архив содержит ожидаемые объекты; restore в изолированный кандидат прошёл заранее заданные безопасные checks. В журнале также перечислены исключения и версия инструмента. Если хотя бы один факт отсутствует, статус должен быть «проверка не завершена», а не «backup готов».

\n

Проверяемые источники

\n" + "contentHtml": "

Ночью задача резервного копирования завершилась без ошибки. Утром в хранилище появился файл с сегодняшней датой. Это хороший признак, но не доказательство восстановления. Пока никто не прочитал архив, не сверил его состав и не поднял копию в отдельной базе, команда знает только одно: программа записала какой-то файл.

\n

Цена ошибки проявится во время сбоя. Архив может содержать одну схему вместо двух, не включать роль или зависимость, а команда восстановления может указывать на исходную базу. Тогда оператор теряет время, рискует перезаписать рабочие данные и узнаёт границы копии в худший момент. Backup считается пригодным не по имени файла и не по коду выхода команды, а по маршруту от scope до безопасного restore.

\n

Сначала фиксируем вопрос восстановления

\n

Резервная копия отвечает на вопрос «что удалось сохранить». Восстановление отвечает на другой вопрос: «можно ли из этих байтов собрать нужный набор объектов в разрешённой цели». Между вопросами стоят формат архива, версия инструмента, зависимости, права, роли, tablespaces и внешние файлы.

\n

В этом примере рассматриваем выборочный логический dump учебной базы PostgreSQL 13. Нужный набор состоит из схем catalog и reference. Роли кластера, tablespaces и файлы вне базы в scope не входят. Параметр --schema выбирает объекты схемы, но pg_dump не добавляет зависимости за пределами выбранных схем. Поэтому такой dump допустим только после проверки замыкания зависимостей (dependency closure): если таблицам нужны типы, функции или расширения из другой схемы, их добавляют в scope либо делают dump всей базы.

\n
Схема проверки резервной копии: scope, archive, manifest, checksum, изолированная цель и проверки после restore
Проверка начинается с описанного scope. Checksum проверяет байты архива, а запросы после restore проверяют состав результата.
\n

Scope отделяет нужную копию от похожего файла

\n

У каждой копии должна быть граница. Запишите базу, формат, включённые схемы, зависимости и исключения до запуска dump. Поле complete без расшифровки почти бесполезно: оно сообщает мнение оператора, но не показывает, какие объекты вошли в архив.

\n
Диагностика backup и restore
СимптомПричинаПроверкаДействие
Файл есть, но ожидаемой таблицы нет в спискеВыбран не тот архив или слишком узкий scopepg_restore --list и manifestОстановить restore и исправить scope
Checksum не совпалФайл изменился, обрезан или перепутанПовторно вычислить SHA-256 тех же байтовНе восстанавливать; получить архив заново
Restore завершился, но ожидаемая таблица пустаDump не включил данные, зависимость или нужную версию схемыПроверить scope, список объектов и контрольный эталонный наборРасширить scope или изменить контракт
Команда требует роль или tablespaceОбъект относится к кластеру, а не только к базеСверить область dump и требования targetПодготовить отдельную проверку
Цель не удаётся отличить от источникаВ runbook нет защитной границыПроверить имя и подключениеНе запускать restore до создания кандидата
\n

Таблица задаёт порядок мышления. Каждый симптом получает собственную проверку. Размер файла не заменяет ни одну из них: большой архив может быть неполным, а маленький — правильным для узкого scope.

\n

Manifest связывает архив с ожидаемым результатом

\n

Положите рядом с архивом manifest. В нём достаточно хранить идентификатор копии, имя и формат файла, размер, SHA-256, major-версии клиента и сервера, includes, excludes и проверки после восстановления. Не записывайте туда пароль, секрет или рабочую строку подключения. Manifest описывает артефакт и ожидаемое доказательство, а не выдаёт доступ.

\n
manifestVersion: 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, версию схемы и безопасные счётчики строк. Счётчики сравнивают с заранее согласованным эталонным учебным набором, а не с непроверенным числом из рабочей базы. Такой разрыв показывает, на каком шаге сломался маршрут.

\n

Учебная последовательность создания архива

\n

Команды ниже демонстрируют выборочный dump схем catalog и reference. Они не запускались против рабочей базы. Имена training_catalog, training-catalog-2020-10.dump и все данные в примере учебные. Перед применением нужно выбрать разрешённую аутентификацию, проверить замыкание зависимостей (dependency closure) и убедиться, что процесс не получает больше прав, чем требуется.

\n
# Учебные команды, без адреса и секретов окружения\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 нужны обе версии и отдельная проверка целевой базы.

\n

Формат custom предназначен для чтения через pg_restore. Если используется plain SQL, маршрут будет другим. Список из pg_restore --list — ранняя остановка: он помогает заметить архив другой базы, неожиданное имя схемы или отсутствующую relation до подключения к кандидату. Но список не доказывает, что данные восстановятся и что приложение сможет работать. После него нужен отдельный restore и проверки результата.

\n

Restore выполняем только в изолированную цель

\n

Безопасная цель должна иметь отдельное имя, отдельное подключение и понятного владельца. В учебном маршруте она называется training_restore_candidate. Перед командой оператор проверяет, что подключение не ведёт к источнику. Если это нельзя доказать, восстановление не начинают. Dump от недоверенного источника сначала нужно вывести в SQL через pg_restore --file=training-catalog-2020-10.sql training-catalog-2020-10.dump и проверить: документация PostgreSQL предупреждает, что restore выполняет на цели код, сформированный суперпользователями источника.

\n
# Только учебный изолированный кандидат\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.

\n

Порядок первого restore drill

\n
  1. Назовите цель восстановления: какая база, схемы и данные должны появиться после restore. Отдельно запишите роли, tablespaces и внешние файлы, которые не проверяются.
  2. Создайте logical archive выбранным инструментом. Сохраните его формат, имя, размер, major-версии клиента и сервера и SHA-256 в manifest.
  3. Сверьте checksum. При расхождении остановитесь до pg_restore; изменённый файл нельзя проверять восстановлением.
  4. Прочитайте оглавление архива. Сопоставьте его с includes и ожидаемыми relations. Несовпадение означает проблему scope или источника.
  5. Подготовьте изолированного кандидата на основе пустого template0. Проверьте имя цели, права и отсутствие маршрута записи в исходную базу.
  6. Выполните restore в кандидате. Сохраните код выхода и диагностический вывод, но не считайте код 0 единственным доказательством.
  7. Запустите безопасные structural и semantic checks. Сравните результат с manifest и эталонным учебным набором, а также запишите, что именно не входило в проверку.
  8. Повторите drill после изменения команды, scope, версии инструмента или структуры базы. Старое доказательство не подтверждает новый маршрут.
\n

Отрицательный путь важнее зелёной отметки

\n

Если hash не совпал, проблема находится до восстановления. Не меняйте параметры target и не запускайте архив «на удачу». Получите файл заново, проверьте источник и обновите manifest только после подтверждения.

\n

Если hash совпал, но в оглавлении нет reference.codes, байты целы, а scope неверен для заявленной цели. Checksum сработал правильно: он не обязан обнаруживать отсутствие объекта. Исправьте команду dump или измените контракт восстановления. Не добавляйте недостающую таблицу вручную и не называйте такой результат полным restore.

\n

Если restore завершился, но row count или версия схемы не совпали с эталонным учебным набором, target получил результат, который не отвечает контракту. Сохраните симптом, проверьте миграции и повторите тест с исправленным архивом. Если цель оказалась исходной базой, остановите процедуру и разберите права и runbook до следующей попытки.

\n

Ограничения и критерий готовности

\n

Эта практика не проверяет point-in-time recovery, непрерывное архивирование WAL, retention, шифрование, резервирование самого хранилища, кластерные роли, tablespaces или файлы вне PostgreSQL. Логический dump одной базы не становится копией всего кластера. Выборочный dump схем не переносит зависимости автоматически; при неполном замыкании зависимостей нужен полный dump или явно расширенный scope. Учебный кандидат не даёт production-метрик и не заменяет аварийное упражнение с согласованным окном.

\n

Первый drill можно считать завершённым, если сохранены четыре независимых факта: scope совпал с целью и замыкание зависимостей проверено; checksum совпал с manifest; архив содержит ожидаемые объекты; restore в изолированный кандидат прошёл заранее заданные безопасные checks по эталонному учебному набору. В журнале также перечислены исключения и major-версии клиента и сервера. Если хотя бы один факт отсутствует, статус должен быть «проверка не завершена», а не «backup готов».

\n

Проверяемые источники

\n" }