Files
progcode/editorial/agent-rewrites/261.json
T

8 lines
20 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 261,
"slug": "editorial-2020-10-practice-backup-recovery",
"title": "Резервная копия не равна восстановлению: как проверить PostgreSQL-архив",
"excerpt": "Файл backup может существовать, читаться и всё равно не содержать нужный объём данных. Разбираем scope, checksum и безопасный restore в изолированную базу.",
"contentHtml": "<p>Ночью задача резервного копирования завершилась без ошибки. Утром в хранилище появился файл с сегодняшней датой. Это хороший признак, но не доказательство восстановления. Пока никто не прочитал архив, не сверил его состав и не поднял копию в отдельной базе, команда знает только одно: программа записала какой-то файл.</p>\n<p>Цена ошибки проявится во время сбоя. Архив может содержать одну схему вместо двух, не включать роль или зависимость, а команда восстановления может указывать на исходную базу. Тогда оператор теряет время, рискует перезаписать рабочие данные и узнаёт границы копии в худший момент. Backup считается пригодным не по имени файла и не по коду выхода команды, а по маршруту от scope до безопасного restore.</p>\n<h2>Сначала фиксируем вопрос восстановления</h2>\n<p>Резервная копия отвечает на вопрос «что удалось сохранить». Восстановление отвечает на другой вопрос: «можно ли из этих байтов собрать нужный набор объектов в разрешённой цели». Между вопросами стоят формат архива, версия инструмента, зависимости, права, роли, tablespaces и внешние файлы.</p>\n<p>В этом примере рассматриваем выборочный логический dump учебной базы PostgreSQL 13. Нужный набор состоит из схем <code>catalog</code> и <code>reference</code>. Роли кластера, tablespaces и файлы вне базы в scope не входят. Параметр <code>--schema</code> выбирает объекты схемы, но <code>pg_dump</code> не добавляет зависимости за пределами выбранных схем. Поэтому такой dump допустим только после проверки замыкания зависимостей (dependency closure): если таблицам нужны типы, функции или расширения из другой схемы, их добавляют в scope либо делают dump всей базы.</p>\n<figure><img src='/assets/editorial/2020/backup-recovery-contract-2020.svg' alt='Схема проверки резервной копии: scope, archive, manifest, checksum, изолированная цель и проверки после restore' loading='lazy' /><figcaption>Проверка начинается с описанного scope. Checksum проверяет байты архива, а запросы после restore проверяют состав результата.</figcaption></figure>\n<h2>Scope отделяет нужную копию от похожего файла</h2>\n<p>У каждой копии должна быть граница. Запишите базу, формат, включённые схемы, зависимости и исключения до запуска dump. Поле <code>complete</code> без расшифровки почти бесполезно: оно сообщает мнение оператора, но не показывает, какие объекты вошли в архив.</p>\n<div class='table-scroll'><table><caption>Диагностика backup и restore</caption><thead><tr><th scope='col'>Симптом</th><th scope='col'>Причина</th><th scope='col'>Проверка</th><th scope='col'>Действие</th></tr></thead><tbody><tr><td>Файл есть, но ожидаемой таблицы нет в списке</td><td>Выбран не тот архив или слишком узкий scope</td><td><code>pg_restore --list</code> и manifest</td><td>Остановить restore и исправить scope</td></tr><tr><td>Checksum не совпал</td><td>Файл изменился, обрезан или перепутан</td><td>Повторно вычислить SHA-256 тех же байтов</td><td>Не восстанавливать; получить архив заново</td></tr><tr><td>Restore завершился, но ожидаемая таблица пуста</td><td>Dump не включил данные, зависимость или нужную версию схемы</td><td>Проверить scope, список объектов и контрольный эталонный набор</td><td>Расширить scope или изменить контракт</td></tr><tr><td>Команда требует роль или tablespace</td><td>Объект относится к кластеру, а не только к базе</td><td>Сверить область dump и требования target</td><td>Подготовить отдельную проверку</td></tr><tr><td>Цель не удаётся отличить от источника</td><td>В runbook нет защитной границы</td><td>Проверить имя и подключение</td><td>Не запускать restore до создания кандидата</td></tr></tbody></table></div>\n<p>Таблица задаёт порядок мышления. Каждый симптом получает собственную проверку. Размер файла не заменяет ни одну из них: большой архив может быть неполным, а маленький — правильным для узкого scope.</p>\n<h2>Manifest связывает архив с ожидаемым результатом</h2>\n<p>Положите рядом с архивом manifest. В нём достаточно хранить идентификатор копии, имя и формат файла, размер, SHA-256, major-версии клиента и сервера, includes, excludes и проверки после восстановления. Не записывайте туда пароль, секрет или рабочую строку подключения. Manifest описывает артефакт и ожидаемое доказательство, а не выдаёт доступ.</p>\n<pre><code>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: только изолированная учебная база</code></pre>\n<p>Поле <code>sha256</code> отвечает только за целостность байтов. Совпадение hash не говорит, что архив содержит нужные данные. Для этого manifest хранит отдельные структурные проверки (structural checks): ожидаемые relations, версию схемы и безопасные счётчики строк. Счётчики сравнивают с заранее согласованным эталонным учебным набором, а не с непроверенным числом из рабочей базы. Такой разрыв показывает, на каком шаге сломался маршрут.</p>\n<h2>Учебная последовательность создания архива</h2>\n<p>Команды ниже демонстрируют выборочный dump схем <code>catalog</code> и <code>reference</code>. Они не запускались против рабочей базы. Имена <code>training_catalog</code>, <code>training-catalog-2020-10.dump</code> и все данные в примере учебные. Перед применением нужно выбрать разрешённую аутентификацию, проверить замыкание зависимостей (dependency closure) и убедиться, что процесс не получает больше прав, чем требуется.</p>\n<pre><code># Учебные команды, без адреса и секретов окружения\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</code></pre>\n<p>В примере <code>shasum</code> показан для macOS; в Linux используйте эквивалент <code>sha256sum</code>. Сохраняйте вывод команды, а затем переносите именно его контрольную сумму в manifest. Для PostgreSQL 13 клиент <code>pg_dump</code> не может снять dump с сервера более новой major-версии; dump может загружаться на более новую версию, но загрузка в более старую не гарантируется. Поэтому в manifest нужны обе версии и отдельная проверка целевой базы.</p>\n<p>Формат custom предназначен для чтения через <code>pg_restore</code>. Если используется plain SQL, маршрут будет другим. Список из <code>pg_restore --list</code> — ранняя остановка: он помогает заметить архив другой базы, неожиданное имя схемы или отсутствующую relation до подключения к кандидату. Но список не доказывает, что данные восстановятся и что приложение сможет работать. После него нужен отдельный restore и проверки результата.</p>\n<h2>Restore выполняем только в изолированную цель</h2>\n<p>Безопасная цель должна иметь отдельное имя, отдельное подключение и понятного владельца. В учебном маршруте она называется <code>training_restore_candidate</code>. Перед командой оператор проверяет, что подключение не ведёт к источнику. Если это нельзя доказать, восстановление не начинают. Dump от недоверенного источника сначала нужно вывести в SQL через <code>pg_restore --file=training-catalog-2020-10.sql training-catalog-2020-10.dump</code> и проверить: документация PostgreSQL предупреждает, что restore выполняет на цели код, сформированный суперпользователями источника.</p>\n<pre><code># Только учебный изолированный кандидат\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;'</code></pre>\n<p>Эти запросы дают только учебный пример: их значения должны совпадать с ожидаемыми значениями эталонного учебного набора, а не с произвольным ожиданием оператора. Они не сообщают реальное время восстановления, полноту кластера или готовность приложения. В рабочем проекте критерии подбирают по контракту данных: проверяют существование критичных relations, версию схемы, небольшой синтетический набор или чтение безопасного reference-объекта. Запрос не должен менять источник, отправлять побочный эффект во внешнюю систему или требовать ресурс, которого нет в заявленном scope.</p>\n<h2>Порядок первого restore drill</h2>\n<ol><li>Назовите цель восстановления: какая база, схемы и данные должны появиться после restore. Отдельно запишите роли, tablespaces и внешние файлы, которые не проверяются.</li><li>Создайте logical archive выбранным инструментом. Сохраните его формат, имя, размер, major-версии клиента и сервера и SHA-256 в manifest.</li><li>Сверьте checksum. При расхождении остановитесь до <code>pg_restore</code>; изменённый файл нельзя проверять восстановлением.</li><li>Прочитайте оглавление архива. Сопоставьте его с includes и ожидаемыми relations. Несовпадение означает проблему scope или источника.</li><li>Подготовьте изолированного кандидата на основе пустого <code>template0</code>. Проверьте имя цели, права и отсутствие маршрута записи в исходную базу.</li><li>Выполните restore в кандидате. Сохраните код выхода и диагностический вывод, но не считайте код 0 единственным доказательством.</li><li>Запустите безопасные structural и semantic checks. Сравните результат с manifest и эталонным учебным набором, а также запишите, что именно не входило в проверку.</li><li>Повторите drill после изменения команды, scope, версии инструмента или структуры базы. Старое доказательство не подтверждает новый маршрут.</li></ol>\n<h2>Отрицательный путь важнее зелёной отметки</h2>\n<p>Если hash не совпал, проблема находится до восстановления. Не меняйте параметры target и не запускайте архив «на удачу». Получите файл заново, проверьте источник и обновите manifest только после подтверждения.</p>\n<p>Если hash совпал, но в оглавлении нет <code>reference.codes</code>, байты целы, а scope неверен для заявленной цели. Checksum сработал правильно: он не обязан обнаруживать отсутствие объекта. Исправьте команду dump или измените контракт восстановления. Не добавляйте недостающую таблицу вручную и не называйте такой результат полным restore.</p>\n<p>Если restore завершился, но row count или версия схемы не совпали с эталонным учебным набором, target получил результат, который не отвечает контракту. Сохраните симптом, проверьте миграции и повторите тест с исправленным архивом. Если цель оказалась исходной базой, остановите процедуру и разберите права и runbook до следующей попытки.</p>\n<h2>Ограничения и критерий готовности</h2>\n<p>Эта практика не проверяет point-in-time recovery, непрерывное архивирование WAL, retention, шифрование, резервирование самого хранилища, кластерные роли, tablespaces или файлы вне PostgreSQL. Логический dump одной базы не становится копией всего кластера. Выборочный dump схем не переносит зависимости автоматически; при неполном замыкании зависимостей нужен полный dump или явно расширенный scope. Учебный кандидат не даёт production-метрик и не заменяет аварийное упражнение с согласованным окном.</p>\n<p>Первый drill можно считать завершённым, если сохранены четыре независимых факта: scope совпал с целью и замыкание зависимостей проверено; checksum совпал с manifest; архив содержит ожидаемые объекты; restore в изолированный кандидат прошёл заранее заданные безопасные checks по эталонному учебному набору. В журнале также перечислены исключения и major-версии клиента и сервера. Если хотя бы один факт отсутствует, статус должен быть «проверка не завершена», а не «backup готов».</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://www.postgresql.org/docs/13/backup.html' target='_blank' rel='noopener noreferrer'>PostgreSQL 13: Backup and Restore</a> — фиксирует различия между SQL dump, файловой копией и continuous archiving.</li><li><a href='https://www.postgresql.org/docs/13/app-pgdump.html' target='_blank' rel='noopener noreferrer'>PostgreSQL 13: pg_dump</a> — описывает custom format, выбор схем, границы зависимостей и совместимость major-версий.</li><li><a href='https://www.postgresql.org/docs/13/app-pgrestore.html' target='_blank' rel='noopener noreferrer'>PostgreSQL 13: pg_restore</a> — описывает восстановление non-plain archive, просмотр оглавления, <code>--exit-on-error</code> и предупреждение о недоверенном содержимом.</li></ul>"
}