8 lines
20 KiB
JSON
8 lines
20 KiB
JSON
{
|
||
"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\": \"<hash>\"},\n \"toolVersion\": {\"client\": \"<pg_dump version>\", \"server\": \"<PostgreSQL version>\"},\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><hash></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>"
|
||
}
|