Files
progcode/editorial/agent-rewrites/260.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": 260,
"slug": "editorial-2020-10-mechanism-backup-recovery",
"title": "Резервная копия PostgreSQL: как доказать, что её можно восстановить",
"excerpt": "Файл с совпавшим SHA-256 ещё не является рабочей резервной копией. Разбираем scope, manifest и безопасный restore на учебном примере PostgreSQL 12.",
"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>--schema</code> <code>pg_dump</code> не пытается включить объекты из других схем, от которых выбранные объекты зависят. В такой копии по умолчанию не окажутся и large objects. Поэтому <code>scope.includes</code> должен описывать минимальный набор, нужный целевой базе, а <code>scope.excludes</code> — всё, что проверка сознательно оставляет за пределами.</p>\n<pre><code>pg_dump --format=custom --file=training-catalog.dump --schema=catalog --schema=reference --strict-names training_catalog\npg_restore --list training-catalog.dump\nshasum -a 256 training-catalog.dump</code></pre>\n<p>Команды выше — учебный пример для PostgreSQL 12. Имена базы и файла вымышлены. Ключи <code>--schema</code> делают команду согласованной с примером scope, а <code>--strict-names</code> останавливает dump, если указанная схема не найдена. Это всё ещё не доказывает наличие зависимого типа или функции из другой схемы: их нужно добавить в scope либо доказать отдельной проверкой.</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 \"toolVersion\": {\"client\": \"&lt;pg_dump version&gt;\", \"server\": \"&lt;PostgreSQL version&gt;\"},\n \"scope\": {\"includes\": [\"schema:catalog\", \"schema:reference\"], \"excludes\": [\"cluster roles\", \"tablespaces\", \"large objects\", \"external files\"]},\n \"restoreChecks\": {\"relations\": [\"catalog.items\", \"reference.codes\"], \"rows\": {\"catalog.items\": 3, \"reference.codes\": 2}},\n \"safety\": \"только изолированный учебный кандидат\"\n}</code></pre>\n<p>Числа строк и значение <code>&lt;hash&gt;</code> здесь учебные: их нельзя переносить в рабочий manifest. После dump оператор записывает фактический digest, размер и версии клиента и сервера. Manifest не должен содержать пароль или рабочую строку подключения; достаточно идентификатора разрешённой цели, если он позволяет независимо проверить endpoint и владельца.</p>\n<p>Поле <code>artifact.sha256</code> относится к файлу до restore. Поля <code>restoreChecks</code> относятся к базе после restore. Их нельзя заменить одним флагом <code>complete: true</code>. Если manifest и файл можно заменить вместе, локальное совпадение не доказывает происхождение артефакта: эталон digest нужно получать из доверенного журнала или другого независимого канала.</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> завершает отправку SQL-команд при первой ошибке, но не превращает restore в доказательство успеха. После команды нужно отдельно проверить ожидаемые relations, версию схемы и безопасный набор синтетических строк. Проверка не должна менять источник, отправлять письмо, списывать деньги или обращаться к внешней системе.</p>\n<p>Не запускайте restore непроверенного архива в рабочей базе даже при совпавшем digest. Сначала просмотрите список архива, сверьте scope и подтвердите цель. Если архив или endpoint нельзя связать с доверенным источником, правильный результат — «restore не проверен», а не попытка на удачу.</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>Выборочный dump схем не гарантирует восстановление зависимостей из других схем, а dump одной базы не содержит cluster-wide роли и tablespaces. Если приложению нужны large objects, расширения, секреты или внешние файлы, их надо включить в отдельный контракт и пройти отдельным drill. Версия клиента и версия сервера должны соответствовать поддерживаемой конфигурации; ссылка на документацию PostgreSQL 12 ниже фиксирует контекст примера, а не заменяет проверку вашей среды.</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/12/backup.html\" target=\"_blank\" rel=\"noopener noreferrer\">PostgreSQL 12 Documentation: Backup and Restore</a> — различает логический dump, файловое резервирование и continuous archiving.</li><li><a href=\"https://www.postgresql.org/docs/12/app-pgdump.html\" target=\"_blank\" rel=\"noopener noreferrer\">PostgreSQL 12 Documentation: pg_dump</a> — описывает dump одной базы, custom format, <code>--schema</code>, <code>--strict-names</code> и границы зависимостей и large objects.</li><li><a href=\"https://www.postgresql.org/docs/12/app-pgrestore.html\" target=\"_blank\" rel=\"noopener noreferrer\">PostgreSQL 12 Documentation: pg_restore</a> — описывает non-plain archive, <code>--list</code> и <code>--exit-on-error</code>.</li><li><a href=\"https://csrc.nist.gov/pubs/fips/180-4/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST FIPS 180-4: Secure Hash Standard</a> — фиксирует назначение digest для обнаружения изменений сообщения; стандарт не доказывает полноту scope или пригодность restore.</li></ul>"
}