diff --git a/editorial/agent-rewrites/260.json b/editorial/agent-rewrites/260.json index afe962f..a922e22 100644 --- a/editorial/agent-rewrites/260.json +++ b/editorial/agent-rewrites/260.json @@ -2,6 +2,6 @@ "index": 260, "slug": "editorial-2020-10-mechanism-backup-recovery", "title": "Резервная копия PostgreSQL: как доказать, что её можно восстановить", - "excerpt": "Файл с совпавшим SHA-256 ещё не является рабочей резервной копией. Разбираем scope, manifest и безопасный restore на учебном примере PostgreSQL.", - "contentHtml": "
В хранилище лежит свежий файл catalog.dump. SHA-256 совпадает с записью в журнале. Во время сбоя оператор запускает восстановление, а нужной роли нет, таблица пуста или архив относится к другой схеме. Цена ошибки — потерянное время в самом дорогом окне и риск направить restore в источник, который ещё содержит единственную рабочую копию.
Проблема не в одной команде. Слово «backup» смешивает четыре разных факта: данные прочитали, файл записали, файл можно разобрать, после restore получился нужный набор объектов. Checksum подтверждает только неизменность байтов. Код выхода 0 подтверждает только завершение конкретного процесса. Ни один из них не отвечает на вопрос, сможет ли приложение использовать восстановленную базу.
Тезис простой: резервная копия должна иметь контракт. В контракте записывают границы данных, формат архива, контрольную сумму, ожидаемые объекты, исключения, безопасную цель и проверки после восстановления. Restore считается успешным не после запуска команды, а после прохождения этих проверок в изолированной цели.
\npg_dump создаёт логический dump одной базы. Это не снимок всего кластера. Роли и другие cluster-wide объекты требуют отдельного решения и могут входить в область pg_dumpall. Tablespaces, файлы на диске, секреты и данные внешних систем также не появляются в обычном dump автоматически. Если они нужны приложению, их надо назвать отдельными артефактами или явно исключить из критерия.
Выборочная копия схемы уменьшает размер, но увеличивает число предположений. Таблица может ссылаться на тип, функцию или другую схему, которую фильтр не включил. Поэтому строка scope.includes должна описывать не удобный путь команды, а минимальный набор, который нужен целевой базе. Если зависимость неизвестна, это пробел контракта, а не повод считать архив «почти полным».
pg_dump --format=custom --file=training-catalog.dump training_catalog\npg_restore --list training-catalog.dump\nshasum -a 256 training-catalog.dump\nКоманды выше — учебный пример. Имена базы и файла вымышлены. Они показывают форму последовательности, но не доказывают, что команда выполнялась и что архив пригоден для production-восстановления. Сначала зафиксируйте область, затем создавайте артефакт.
\nБез manifest оператор выбирает файл по имени и дате. Такой выбор не описывает содержимое. Минимальная запись должна позволять другому человеку ответить на пять вопросов: какой это артефакт, в каком он формате, какие байты проверять, что входит в scope и каким наблюдением подтвердить результат.
\n{\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}\nПоле artifact.sha256 относится к файлу до restore. Поля restoreChecks относятся к базе после restore. Их нельзя заменить одним флагом complete: true. Если байты изменились, маршрут останавливается до чтения архива. Если байты целы, но список объектов не совпадает, нужно разбирать scope или выбрать правильный артефакт.
SHA-256 полезен на границе хранения и передачи. Он обнаруживает изменённый, повреждённый или перепутанный файл. При несовпадении digest действие одно: остановить restore и получить доверенный артефакт заново. Нельзя «проверить дальше», потому что следующие результаты уже относятся к байтам, которым нельзя доверять.
\nСовпавший digest не видит неверный scope. Два файла могут быть целыми и одинаково непригодными: оба могли содержать только одну из двух требуемых схем. Поэтому checksum — gate целостности, а не verdict восстановления. Оглавление архива и checks после restore отвечают на другие вопросы.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| SHA-256 не совпал | Файл изменился, повреждён или выбран не тот артефакт | Повторить digest и сверить путь с manifest | Остановить restore, получить архив из доверенного источника |
| Архив читается, нужной relation нет | Scope слишком узкий или выбран другой dump | Сравнить pg_restore --list с scope.includes | Исправить путь создания или выпустить новую версию manifest |
| Роль отсутствует после restore | Cluster-wide объект не входил в dump базы | Проверить scope.excludes и список требуемых ролей | Восстановить роли отдельным согласованным шагом или изменить контракт |
| Restore завершился, таблица пуста | Dump неполный, выбран не тот объект или check не соответствует данным | Выполнить безопасный row count и сверить его с manifest | Остановить ввод данных, найти расхождение до переключения |
| Команда направлена в рабочую базу | Не доказана изоляция цели | Проверить адрес, имя и отдельные credentials кандидата | Не запускать restore; создать и явно подтвердить безопасную цель |
Таблица задаёт отрицательный путь. Любое расхождение переводит операцию в остановку. Нельзя продолжать до следующего шага только потому, что архив «свежий» или команда раньше уже работала. Причина должна попасть в запись проверки вместе с действием, которое вернёт маршрут к доверенному состоянию.
\nЦель должна быть отдельной базой или отдельным кластером с понятным именем, доступом и запретом на перезапись источника. Перед командой проверьте подключение и сохраните фактический endpoint. Учебный пример ниже не запускает PostgreSQL. Он показывает порядок и границу безопасности.
\n# Учебная последовательность. Источник не перезаписывается.\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;\"\nФлаг --exit-on-error не превращает restore в доказательство успеха. Он делает ранний отказ заметнее. После команды нужно отдельно проверить ожидаемые relations, версию схемы и безопасный набор синтетических строк. Проверка не должна менять источник, отправлять письмо, списывать деньги или обращаться к внешней системе.
Этот учебный маршрут не измеряет RPO, RTO, скорость выгрузки, стоимость хранения или длительность restore. Он не проверяет шифрование, права доступа, репликацию и автоматическое расписание. Logical dump не заменяет физическое резервирование, continuous archiving или план восстановления всего кластера. Подход также не отвечает за данные, которые живут вне PostgreSQL.
\nНе называйте пример production-результатом. Учебные имена, числа строк, checksum и результаты здесь не являются отчётом о реальной базе. В рабочей системе значения должен получить сам запуск на разрешённом стенде. Версию PostgreSQL и версию клиента нужно записать рядом с результатом: формат и поведение инструментов должны соответствовать поддерживаемой конфигурации.
\nКритерий готовности проверяемый: для конкретного manifest другой оператор может найти нужный архив, подтвердить его digest, увидеть заявленные объекты, восстановить его только в изолированной цели и получить ожидаемые checks. При изменённом файле, неполном scope, неизвестной цели или расхождении результата маршрут останавливается с понятной причиной. Пока это не доказано повторяемым drill, в наличии есть файл, но нет подтверждённой резервной копии.
\nВ хранилище лежит свежий файл catalog.dump. SHA-256 совпадает с записью в журнале. Во время сбоя оператор запускает восстановление, а нужной роли нет, таблица пуста или архив относится к другой схеме. Цена ошибки — потерянное время в самом дорогом окне и риск направить restore в источник, который ещё содержит единственную рабочую копию.
Проблема не в одной команде. Слово «backup» смешивает четыре разных факта: данные прочитали, файл записали, файл можно разобрать, после restore получился нужный набор объектов. Checksum подтверждает только неизменность байтов. Код выхода 0 подтверждает только завершение конкретного процесса. Ни один из них не отвечает на вопрос, сможет ли приложение использовать восстановленную базу.
Тезис простой: резервная копия должна иметь контракт. В контракте записывают границы данных, формат архива, контрольную сумму, ожидаемые объекты, исключения, безопасную цель и проверки после восстановления. Restore считается успешным не после запуска команды, а после прохождения этих проверок в изолированной цели.
\npg_dump создаёт логический dump одной базы. Это не снимок всего кластера. Роли и другие cluster-wide объекты требуют отдельного решения и могут входить в область pg_dumpall. Tablespaces, файлы на диске, секреты и данные внешних систем также не появляются в обычном dump автоматически. Если они нужны приложению, их надо назвать отдельными артефактами или явно исключить из критерия.
Выборочная копия схемы уменьшает размер, но увеличивает число предположений. При --schema pg_dump не пытается включить объекты из других схем, от которых выбранные объекты зависят. В такой копии по умолчанию не окажутся и large objects. Поэтому scope.includes должен описывать минимальный набор, нужный целевой базе, а scope.excludes — всё, что проверка сознательно оставляет за пределами.
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\nКоманды выше — учебный пример для PostgreSQL 12. Имена базы и файла вымышлены. Ключи --schema делают команду согласованной с примером scope, а --strict-names останавливает dump, если указанная схема не найдена. Это всё ещё не доказывает наличие зависимого типа или функции из другой схемы: их нужно добавить в scope либо доказать отдельной проверкой.
Без manifest оператор выбирает файл по имени и дате. Такой выбор не описывает содержимое. Минимальная запись должна позволять другому человеку ответить на пять вопросов: какой это артефакт, в каком он формате, какие байты проверять, что входит в scope и каким наблюдением подтвердить результат.
\n{\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}\nЧисла строк и значение <hash> здесь учебные: их нельзя переносить в рабочий manifest. После dump оператор записывает фактический digest, размер и версии клиента и сервера. Manifest не должен содержать пароль или рабочую строку подключения; достаточно идентификатора разрешённой цели, если он позволяет независимо проверить endpoint и владельца.
Поле artifact.sha256 относится к файлу до restore. Поля restoreChecks относятся к базе после restore. Их нельзя заменить одним флагом complete: true. Если manifest и файл можно заменить вместе, локальное совпадение не доказывает происхождение артефакта: эталон digest нужно получать из доверенного журнала или другого независимого канала.
SHA-256 полезен на границе хранения и передачи. Он обнаруживает изменённый, повреждённый или перепутанный файл, если digest получен до изменения и хранится в доверенном месте. При несовпадении действие одно: остановить restore и получить артефакт заново. Нельзя «проверить дальше», потому что следующие результаты уже относятся к байтам, которым нельзя доверять.
\nСовпавший digest не видит неверный scope. Два файла могут быть целыми и одинаково непригодными: оба могли содержать только одну из двух требуемых схем. Поэтому checksum — gate целостности, а не verdict восстановления. Оглавление архива и checks после restore отвечают на другие вопросы.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| SHA-256 не совпал | Файл изменился, повреждён или выбран не тот артефакт | Повторить digest и сверить путь с manifest | Остановить restore, получить архив из доверенного источника |
| Архив читается, нужной relation нет | Scope слишком узкий или выбран другой dump | Сравнить pg_restore --list с scope.includes | Исправить путь создания или выпустить новую версию manifest |
| Роль отсутствует после restore | Cluster-wide объект не входил в dump базы | Проверить scope.excludes и список требуемых ролей | Восстановить роли отдельным согласованным шагом или изменить контракт |
| Restore завершился, таблица пуста | Dump неполный, выбран не тот объект или check не соответствует данным | Выполнить безопасный row count и сверить его с manifest | Остановить ввод данных, найти расхождение до переключения |
| Команда направлена в рабочую базу | Не доказана изоляция цели | Проверить адрес, имя и отдельные credentials кандидата | Не запускать restore; создать и явно подтвердить безопасную цель |
Таблица задаёт отрицательный путь. Любое расхождение переводит операцию в остановку. Нельзя продолжать до следующего шага только потому, что архив «свежий» или команда раньше уже работала. Причина должна попасть в запись проверки вместе с действием, которое вернёт маршрут к доверенному состоянию.
\nЦель должна быть отдельной базой или отдельным кластером с понятным именем, доступом и запретом на перезапись источника. Перед командой проверьте подключение и сохраните фактический endpoint. Учебный пример ниже не запускает PostgreSQL. Он показывает порядок и границу безопасности.
\n# Учебная последовательность. Источник не перезаписывается.\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;\"\nФлаг --exit-on-error завершает отправку SQL-команд при первой ошибке, но не превращает restore в доказательство успеха. После команды нужно отдельно проверить ожидаемые relations, версию схемы и безопасный набор синтетических строк. Проверка не должна менять источник, отправлять письмо, списывать деньги или обращаться к внешней системе.
Не запускайте restore непроверенного архива в рабочей базе даже при совпавшем digest. Сначала просмотрите список архива, сверьте scope и подтвердите цель. Если архив или endpoint нельзя связать с доверенным источником, правильный результат — «restore не проверен», а не попытка на удачу.
\nЭтот учебный маршрут не измеряет RPO, RTO, скорость выгрузки, стоимость хранения или длительность restore. Он не проверяет шифрование, права доступа, репликацию и автоматическое расписание. Logical dump не заменяет физическое резервирование, continuous archiving или план восстановления всего кластера. Подход также не отвечает за данные, которые живут вне PostgreSQL.
\nВыборочный dump схем не гарантирует восстановление зависимостей из других схем, а dump одной базы не содержит cluster-wide роли и tablespaces. Если приложению нужны large objects, расширения, секреты или внешние файлы, их надо включить в отдельный контракт и пройти отдельным drill. Версия клиента и версия сервера должны соответствовать поддерживаемой конфигурации; ссылка на документацию PostgreSQL 12 ниже фиксирует контекст примера, а не заменяет проверку вашей среды.
\nНе называйте пример production-результатом. Учебные имена, числа строк, checksum и результаты здесь не являются отчётом о реальной базе. В рабочей системе значения должен получить сам запуск на разрешённом стенде. Версию PostgreSQL и версию клиента нужно записать рядом с результатом: формат и поведение инструментов должны соответствовать поддерживаемой конфигурации.
\nКритерий готовности проверяемый: для конкретного manifest другой оператор может найти нужный архив, подтвердить его digest, увидеть заявленные объекты, восстановить его только в изолированной цели и получить ожидаемые checks. При изменённом файле, неполном scope, неизвестной цели или расхождении результата маршрут останавливается с понятной причиной. Пока это не доказано повторяемым drill, в наличии есть файл, но нет подтверждённой резервной копии.
\n--schema, --strict-names и границы зависимостей и large objects.--list и --exit-on-error.