Files
progcode/editorial/agent-rewrites/261.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
17 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. Нужный набор состоит из схем <code>catalog</code> и <code>reference</code>. Роли кластера, tablespaces и файлы вне базы в scope не входят. Если scope не записан заранее, после сбоя легко принять неполную копию за полную.</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, список объектов и row count</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, includes, excludes и проверки после восстановления. Не записывайте туда пароль, секрет или рабочую строку подключения. Manifest описывает артефакт и ожидаемое доказательство, а не выдаёт доступ.</p>\n<pre><code>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: только изолированная учебная база</code></pre>\n<p>Поле <code>sha256</code> отвечает только за целостность байтов. Совпадение hash не говорит, что архив содержит нужные данные. Для этого manifest хранит отдельные structural checks: ожидаемые relations, версию схемы и безопасные счётчики строк. Такой разрыв показывает, на каком шаге сломался маршрут.</p>\n<h2>Учебная последовательность создания архива</h2>\n<p>Команды ниже демонстрируют форму проверки. Они не запускались против рабочей базы. Имена <code>training_catalog</code>, <code>training-catalog-2020-10.dump</code> и все данные в примере учебные. Перед применением нужно выбрать разрешённую аутентификацию, проверить версию клиента и убедиться, что процесс не получает больше прав, чем требуется.</p>\n<pre><code># Учебные команды, без адреса и секретов окружения\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</code></pre>\n<p>Формат custom предназначен для чтения через <code>pg_restore</code>. Если используется plain SQL, маршрут будет другим. Нельзя взять команду для одного формата и считать её универсальной. Версию <code>pg_dump</code> фиксируйте рядом с manifest: совместимость клиента, сервера и целевой базы нужно проверять в конкретной среде.</p>\n<p>Список из <code>pg_restore --list</code> — ранняя остановка. Он помогает заметить архив другой базы, неожиданное имя схемы или отсутствующую relation до подключения к кандидату. Но список не доказывает, что данные восстановятся и что приложение сможет работать. После него нужен отдельный restore и проверки результата.</p>\n<h2>Restore выполняем только в изолированную цель</h2>\n<p>Безопасная цель должна иметь отдельное имя, отдельное подключение и понятного владельца. В учебном маршруте она называется <code>training_restore_candidate</code>. Перед командой оператор проверяет, что подключение не ведёт к источнику. Если это нельзя доказать, восстановление не начинают.</p>\n<pre><code># Только учебный изолированный кандидат\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;'</code></pre>\n<p>Эти запросы дают только учебный пример. Они не сообщают реальное время восстановления, полноту кластера или готовность приложения. В рабочем проекте критерии подбирают по контракту данных: проверяют существование критичных relations, версию схемы, небольшой синтетический набор или чтение безопасного reference-объекта. Запрос не должен менять источник, отправлять побочный эффект во внешнюю систему или требовать ресурс, которого нет в заявленном scope.</p>\n<h2>Порядок первого restore drill</h2>\n<ol><li>Назовите цель восстановления: какая база, схемы и данные должны появиться после restore. Отдельно запишите роли, tablespaces и внешние файлы, которые не проверяются.</li><li>Создайте logical archive выбранным инструментом. Сохраните его формат, имя, размер, версию клиента и SHA-256 в manifest.</li><li>Сверьте checksum. При расхождении остановитесь до <code>pg_restore</code>; изменённый файл нельзя проверять восстановлением.</li><li>Прочитайте оглавление архива. Сопоставьте его с includes и ожидаемыми relations. Несовпадение означает проблему scope или источника.</li><li>Подготовьте изолированного кандидата. Проверьте имя цели, права и отсутствие маршрута записи в исходную базу.</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 одной базы не становится копией всего кластера. Учебный кандидат не даёт production-метрик и не заменяет аварийное упражнение с согласованным окном.</p>\n<p>Первый drill можно считать завершённым, если сохранены четыре независимых факта: scope совпал с целью; checksum совпал с manifest; архив содержит ожидаемые объекты; restore в изолированный кандидат прошёл заранее заданные безопасные checks. В журнале также перечислены исключения и версия инструмента. Если хотя бы один факт отсутствует, статус должен быть «проверка не завершена», а не «backup готов».</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://www.postgresql.org/docs/current/backup.html' target='_blank' rel='noopener noreferrer'>PostgreSQL: Backup and Restore</a> — описывает logical dump, файловые копии и continuous archiving как разные способы с разными границами.</li><li><a href='https://www.postgresql.org/docs/current/app-pgdump.html' target='_blank' rel='noopener noreferrer'>PostgreSQL: pg_dump</a> — фиксирует назначение утилиты, форматы архива и ограничения выборочного dump.</li><li><a href='https://www.postgresql.org/docs/current/app-pgrestore.html' target='_blank' rel='noopener noreferrer'>PostgreSQL: pg_restore</a> — описывает восстановление non-plain archive и просмотр его содержания.</li></ul>"
}