Files
progcode/editorial/agent-rewrites/260.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": 260,
"slug": "editorial-2020-10-mechanism-backup-recovery",
"title": "Резервная копия PostgreSQL: как доказать, что её можно восстановить",
"excerpt": "Файл с совпавшим SHA-256 ещё не является рабочей резервной копией. Разбираем scope, manifest и безопасный restore на учебном примере PostgreSQL.",
"contentHtml": "<p>В хранилище лежит свежий файл <code>catalog.dump</code>. SHA-256 совпадает с записью в журнале. Во время сбоя оператор запускает восстановление, а нужной роли нет, таблица пуста или архив относится к другой схеме. Цена ошибки — потерянное время в самом дорогом окне и риск направить restore в источник, который ещё содержит единственную рабочую копию.</p>\n<p>Проблема не в одной команде. Слово «backup» смешивает четыре разных факта: данные прочитали, файл записали, файл можно разобрать, после restore получился нужный набор объектов. Checksum подтверждает только неизменность байтов. Код выхода <code>0</code> подтверждает только завершение конкретного процесса. Ни один из них не отвечает на вопрос, сможет ли приложение использовать восстановленную базу.</p>\n<p>Тезис простой: резервная копия должна иметь контракт. В контракте записывают границы данных, формат архива, контрольную сумму, ожидаемые объекты, исключения, безопасную цель и проверки после восстановления. Restore считается успешным не после запуска команды, а после прохождения этих проверок в изолированной цели.</p>\n<h2>Сначала определите, что именно копируется</h2>\n<p><code>pg_dump</code> создаёт логический dump одной базы. Это не снимок всего кластера. Роли и другие cluster-wide объекты требуют отдельного решения и могут входить в область <code>pg_dumpall</code>. Tablespaces, файлы на диске, секреты и данные внешних систем также не появляются в обычном dump автоматически. Если они нужны приложению, их надо назвать отдельными артефактами или явно исключить из критерия.</p>\n<p>Выборочная копия схемы уменьшает размер, но увеличивает число предположений. Таблица может ссылаться на тип, функцию или другую схему, которую фильтр не включил. Поэтому строка <code>scope.includes</code> должна описывать не удобный путь команды, а минимальный набор, который нужен целевой базе. Если зависимость неизвестна, это пробел контракта, а не повод считать архив «почти полным».</p>\n<pre><code>pg_dump --format=custom --file=training-catalog.dump training_catalog\npg_restore --list training-catalog.dump\nshasum -a 256 training-catalog.dump</code></pre>\n<p>Команды выше — учебный пример. Имена базы и файла вымышлены. Они показывают форму последовательности, но не доказывают, что команда выполнялась и что архив пригоден для production-восстановления. Сначала зафиксируйте область, затем создавайте артефакт.</p>\n<h2>Manifest связывает создание и восстановление</h2>\n<p>Без manifest оператор выбирает файл по имени и дате. Такой выбор не описывает содержимое. Минимальная запись должна позволять другому человеку ответить на пять вопросов: какой это артефакт, в каком он формате, какие байты проверять, что входит в scope и каким наблюдением подтвердить результат.</p>\n<pre><code>{\n \"manifestVersion\": 1,\n \"backupId\": \"training-catalog-2020-10-a\",\n \"artifact\": {\"file\": \"training-catalog.dump\", \"format\": \"pg_dump custom (-Fc)\", \"sha256\": \"&lt;hash&gt;\"},\n \"scope\": {\"includes\": [\"schema:catalog\", \"schema:reference\"], \"excludes\": [\"cluster roles\", \"tablespaces\", \"external files\"]},\n \"restoreChecks\": {\"relations\": [\"catalog.items\", \"reference.codes\"], \"rows\": {\"catalog.items\": 3, \"reference.codes\": 2}},\n \"safety\": \"только изолированный учебный кандидат\"\n}</code></pre>\n<p>Поле <code>artifact.sha256</code> относится к файлу до restore. Поля <code>restoreChecks</code> относятся к базе после restore. Их нельзя заменить одним флагом <code>complete: true</code>. Если байты изменились, маршрут останавливается до чтения архива. Если байты целы, но список объектов не совпадает, нужно разбирать scope или выбрать правильный артефакт.</p>\n<figure><img src=\"/assets/editorial/2020/backup-recovery-restore-path-2020.svg\" alt=\"Путь восстановления: scope и manifest приводят к архиву, checksum проверяет байты, список архива сверяется с ожиданиями, затем изолированная цель проходит проверки\" loading=\"lazy\" /><figcaption>Restore проходит несколько границ. Каждая граница может остановить маршрут и сохранить конкретную причину отказа.</figcaption></figure>\n<h2>Checksum проверяет файл, а не смысл</h2>\n<p>SHA-256 полезен на границе хранения и передачи. Он обнаруживает изменённый, повреждённый или перепутанный файл. При несовпадении digest действие одно: остановить restore и получить доверенный артефакт заново. Нельзя «проверить дальше», потому что следующие результаты уже относятся к байтам, которым нельзя доверять.</p>\n<p>Совпавший digest не видит неверный scope. Два файла могут быть целыми и одинаково непригодными: оба могли содержать только одну из двух требуемых схем. Поэтому checksum — gate целостности, а не verdict восстановления. Оглавление архива и checks после restore отвечают на другие вопросы.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><caption>Диагностика резервной копии и 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>SHA-256 не совпал</td><td>Файл изменился, повреждён или выбран не тот артефакт</td><td>Повторить digest и сверить путь с manifest</td><td>Остановить restore, получить архив из доверенного источника</td></tr><tr><td>Архив читается, нужной relation нет</td><td>Scope слишком узкий или выбран другой dump</td><td>Сравнить <code>pg_restore --list</code> с <code>scope.includes</code></td><td>Исправить путь создания или выпустить новую версию manifest</td></tr><tr><td>Роль отсутствует после restore</td><td>Cluster-wide объект не входил в dump базы</td><td>Проверить <code>scope.excludes</code> и список требуемых ролей</td><td>Восстановить роли отдельным согласованным шагом или изменить контракт</td></tr><tr><td>Restore завершился, таблица пуста</td><td>Dump неполный, выбран не тот объект или check не соответствует данным</td><td>Выполнить безопасный row count и сверить его с manifest</td><td>Остановить ввод данных, найти расхождение до переключения</td></tr><tr><td>Команда направлена в рабочую базу</td><td>Не доказана изоляция цели</td><td>Проверить адрес, имя и отдельные credentials кандидата</td><td>Не запускать restore; создать и явно подтвердить безопасную цель</td></tr></tbody></table></div>\n<p>Таблица задаёт отрицательный путь. Любое расхождение переводит операцию в остановку. Нельзя продолжать до следующего шага только потому, что архив «свежий» или команда раньше уже работала. Причина должна попасть в запись проверки вместе с действием, которое вернёт маршрут к доверенному состоянию.</p>\n<h2>Restore выполняйте в изолированном кандидате</h2>\n<p>Цель должна быть отдельной базой или отдельным кластером с понятным именем, доступом и запретом на перезапись источника. Перед командой проверьте подключение и сохраните фактический endpoint. Учебный пример ниже не запускает PostgreSQL. Он показывает порядок и границу безопасности.</p>\n<pre><code># Учебная последовательность. Источник не перезаписывается.\ncreatedb training_restore_candidate\npg_restore --dbname=training_restore_candidate --exit-on-error training-catalog.dump\npsql training_restore_candidate --command=\"SELECT to_regclass('catalog.items');\"\npsql training_restore_candidate --command=\"SELECT count(*) FROM catalog.items;\"</code></pre>\n<p>Флаг <code>--exit-on-error</code> не превращает restore в доказательство успеха. Он делает ранний отказ заметнее. После команды нужно отдельно проверить ожидаемые relations, версию схемы и безопасный набор синтетических строк. Проверка не должна менять источник, отправлять письмо, списывать деньги или обращаться к внешней системе.</p>\n<h2>Порядок действий</h2>\n<ol><li>Сформулируйте scope одним предложением: какая база, схемы и зависимые объекты должны появиться после restore.</li><li>Перечислите исключения: роли, tablespaces, внешние файлы, секреты и другие ресурсы, которые не входят в этот артефакт.</li><li>Создайте архив выбранного формата и запишите имя, размер, версию инструмента и SHA-256 в manifest.</li><li>Прочитайте список архива и сравните его с ожидаемыми объектами. Несовпадение останавливает проверку.</li><li>Создайте изолированный кандидат. Проверьте адрес, credentials и отсутствие маршрута записи в источник.</li><li>Выполните restore с сохранением stderr и кода выхода. Не трактуйте пустой stderr как полную проверку.</li><li>Запустите structural checks: relations, владельцы и версия схемы. Затем запустите безопасные semantic checks из manifest.</li><li>Запишите verdict и список непроверенных областей. Только после этого решайте, нужна ли отдельная проверка ролей, внешних файлов, retention или переключения.</li></ol>\n<h2>Ограничения и критерий готовности</h2>\n<p>Этот учебный маршрут не измеряет RPO, RTO, скорость выгрузки, стоимость хранения или длительность restore. Он не проверяет шифрование, права доступа, репликацию и автоматическое расписание. Logical dump не заменяет физическое резервирование, continuous archiving или план восстановления всего кластера. Подход также не отвечает за данные, которые живут вне PostgreSQL.</p>\n<p>Не называйте пример production-результатом. Учебные имена, числа строк, checksum и результаты здесь не являются отчётом о реальной базе. В рабочей системе значения должен получить сам запуск на разрешённом стенде. Версию PostgreSQL и версию клиента нужно записать рядом с результатом: формат и поведение инструментов должны соответствовать поддерживаемой конфигурации.</p>\n<p>Критерий готовности проверяемый: для конкретного manifest другой оператор может найти нужный архив, подтвердить его digest, увидеть заявленные объекты, восстановить его только в изолированной цели и получить ожидаемые checks. При изменённом файле, неполном scope, неизвестной цели или расхождении результата маршрут останавливается с понятной причиной. Пока это не доказано повторяемым drill, в наличии есть файл, но нет подтверждённой резервной копии.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.postgresql.org/docs/current/backup.html\" target=\"_blank\" rel=\"noopener noreferrer\">PostgreSQL Documentation: Backup and Restore</a> — границы логического dump, физического резервирования и continuous archiving.</li><li><a href=\"https://www.postgresql.org/docs/current/app-pgdump.html\" target=\"_blank\" rel=\"noopener noreferrer\">PostgreSQL Documentation: pg_dump</a> — форматы, область одной базы и ограничения выборочного dump.</li><li><a href=\"https://www.postgresql.org/docs/current/app-pgrestore.html\" target=\"_blank\" rel=\"noopener noreferrer\">PostgreSQL Documentation: pg_restore</a> — чтение non-plain archive, список объектов и восстановление.</li></ul>"
}