8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"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\": \"<hash>\"},\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>"
|
||
}
|