Files
progcode/editorial/agent-rewrites/259.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": 259,
"slug": "editorial-2020-10-field-backup-recovery",
"title": "Резервная копия не равна восстановлению: как проверить backup без риска для источника",
"excerpt": "Архив можно увидеть и скачать, но это ещё не доказательство восстановления. Разбираем manifest, checksum, область данных, изолированную цель и проверку результата на учебном PostgreSQL-сценарии.",
"contentHtml": "<p>Симптом знакомый: в хранилище лежит свежий архив, команда видит его размер и дату, но никто не может уверенно сказать, что произойдёт после <code>restore</code>. Неясно, какие схемы попали в файл, совпадают ли его байты с теми, что указаны в журнале, куда пойдёт восстановление и чем считать результат успешным.</p>\n<p>Цена такой ошибки появляется в аварии. Оператор выбирает архив по имени, запускает привычную команду и обнаруживает неполный набор таблиц. Или восстанавливает данные поверх источника. В этот момент резервная копия не помогает: она превращается в ещё один объект, которому нужно доверять без проверки.</p>\n<p>Тезис статьи простой: backup готов к применению только тогда, когда команда может проверить его scope, целостность, безопасность цели и смысл результата. Наличие файла доказывает наличие файла. Оно не доказывает, что приложение сможет продолжить работу.</p>\n<h2>Что именно нужно доказать</h2>\n<p>Восстановление состоит из нескольких независимых вопросов. Первый — что архив выбран правильно. Второй — что его содержимое не изменилось после создания. Третий — что в него попала нужная область базы. Четвёртый — что команда не затронет источник. Пятый — что после восстановления появились ожидаемые схемы, таблицы и данные.</p>\n<p>Эти вопросы связывает короткий manifest. В нём хранят идентификатор архива, формат, область данных, исключения, SHA-256 и проверки результата. Manifest не заменяет сам архив и не делает восстановление автоматическим. Он задаёт договор, с которым можно сравнить фактический файл и фактический кандидат.</p>\n<pre><code>{\n &quot;backupId&quot;: &quot;training-catalog-2020-10-a&quot;,\n &quot;format&quot;: &quot;pg_dump custom&quot;,\n &quot;artifact&quot;: {\n &quot;file&quot;: &quot;training-catalog.dump&quot;,\n &quot;sha256&quot;: &quot;...&quot;\n },\n &quot;scope&quot;: {\n &quot;includes&quot;: [&quot;schema:catalog&quot;, &quot;schema:reference&quot;],\n &quot;excludes&quot;: [&quot;cluster roles&quot;, &quot;tablespaces&quot;, &quot;external files&quot;]\n },\n &quot;checks&quot;: {\n &quot;relations&quot;: [&quot;catalog.items&quot;, &quot;reference.codes&quot;],\n &quot;rows&quot;: { &quot;catalog.items&quot;: 3, &quot;reference.codes&quot;: 2 }\n },\n &quot;target&quot;: &quot;isolated training candidate only&quot;\n}</code></pre>\n<p>Пример учебный. Имена, числа и значение хеша вымышлены. Поле <code>excludes</code> здесь не декоративно: логический dump одной базы не следует выдавать за копию всего кластера. Роли, tablespaces и внешние файлы требуют отдельного решения и отдельной проверки.</p>\n<figure><img src='/assets/editorial/2020/backup-recovery-diagnosis-2020.svg' alt='Схема проверки резервного архива: scope, checksum, безопасная цель и проверки восстановленных данных' loading='lazy' /><figcaption>Сначала проверяется договор архива и его байты, затем цель восстановления, затем структура и данные кандидата.</figcaption></figure>\n<h2>Почему checksum не заменяет restore</h2>\n<p>SHA-256 помогает ответить на узкий вопрос: совпадают ли байты фактического файла с ожидаемым digest. Если значение изменилось, файл нельзя считать тем же артефактом. Причиной может быть неполная передача, повреждение, подмена или выбор другого файла под похожим именем.</p>\n<p>Но одинаковый digest не доказывает правильный scope. Можно безошибочно сохранить неполный dump и получить идеальное совпадение checksum. Поэтому проверка идёт в два слоя: сначала bytes, потом содержимое архива и результат восстановления.</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>Архив есть, но scope не описан</td><td>Файл создавали без явного договора</td><td>Сверить список ожидаемых схем и исключений с командой создания</td><td>Остановить drill и уточнить scope</td></tr><tr><td>SHA-256 не совпадает</td><td>Выбран другой или повреждённый файл</td><td>Заново вычислить digest фактического файла</td><td>Не читать и не восстанавливать файл; получить доверенный артефакт</td></tr><tr><td>В списке нет нужной relation</td><td>Она не попала в dump или фильтр сузил область</td><td>Сравнить <code>pg_restore --list</code> с manifest</td><td>Исправить создание архива или оформить зависимость отдельно</td></tr><tr><td>Цель совпадает с источником</td><td>Runbook не закрепил изоляцию</td><td>Проверить имя базы и запрет перезаписи до команды</td><td>Не запускать; создать отдельного кандидата</td></tr><tr><td>Restore завершился, но таблица пуста</td><td>Неполный scope, неверная версия или слабый check</td><td>Выполнить read-only запросы к кандидату и сравнить expected rows</td><td>Зафиксировать отрицательный verdict и исправить договор</td></tr></tbody></table></div>\n<h2>Конкретный маршрут для PostgreSQL</h2>\n<p>Для учебного custom archive можно сначала получить список объектов, не меняя базу. Команда <code>pg_restore --list</code> показывает, что инструмент видит внутри non-plain archive. Этот список нужно сравнить с manifest, а не с памятью оператора. Если ожидалась <code>reference.codes</code>, а её нет, повторный запуск restore не добавит объект.</p>\n<pre><code># Учебные имена. Команды не выполнялись этой статьёй.\nsha256sum training-catalog.dump\npg_restore --list training-catalog.dump\n\n# Только после проверок — отдельный кандидат.\ncreatedb training_restore_candidate\npg_restore --dbname=training_restore_candidate training-catalog.dump</code></pre>\n<p>Перед последней командой нужно подтвердить, что <code>training_restore_candidate</code> не является источником и не содержит нужных рабочих данных. Если безопасной цели нет, правильный результат — «restore не проверен». Нельзя превращать отсутствие стенда в разрешение на риск.</p>\n<p>После восстановления проверяют не только код возврата процесса. Нужны конкретные read-only проверки: существуют ли ожидаемые схемы, совпадает ли набор relations, имеют ли таблицы ожидаемый тип и не пусты ли обязательные справочники. Количество строк — учебный критерий, а не обещание для настоящей базы. В production оно меняется, поэтому ожидаемое значение должно зависеть от снимка и бизнес-условия.</p>\n<h2>Порядок действий</h2>\n<ol><li>Назвать backup ID и прочитать manifest целиком: формат, файл, scope, исключения, digest, цель и checks.</li><li>Сверить область восстановления с вопросом. Если нужная схема не включена, назвать это пробелом дизайна, а не ошибкой <code>pg_restore</code>.</li><li>Вычислить SHA-256 фактического файла. При несовпадении остановить маршрут до просмотра содержимого.</li><li>Получить список архива через <code>pg_restore --list</code> и сравнить его с ожидаемыми relations.</li><li>Подтвердить изолированную базу-кандидат и запретить перезапись источника. Проверку цели выполнить до команды restore.</li><li>Восстановить архив только в кандидата. Сохранить время начала, время окончания, версию инструмента и итог команды.</li><li>Выполнить структурные и смысловые read-only checks. Сравнить фактический результат с manifest, не меняя expected values после факта.</li><li>Записать непокрытые области: роли, tablespaces, внешние файлы, журналы изменений, требования к потере данных и допустимое время восстановления.</li></ol>\n<h2>Отрицательный путь важнее зелёного</h2>\n<p>Хорошая проверка должна остановиться на плохом входе. Изменённый файл должен отклоняться по checksum. Суженный scope должен отклоняться по сравнению manifest и списка объектов. Источник должен быть недопустимой целью. Пустая relation должна приводить к отрицательному verdict, если приложение требует данные.</p>\n<p>Нельзя исправлять отрицательный результат подменой evidence. Не следует менять хеш, чтобы пройти gate, добавлять строки вручную после восстановления или переписывать expected count после обнаружения расхождения. Эти действия скрывают причину и делают следующий запуск менее надёжным.</p>\n<h2>Ограничения метода</h2>\n<p>Логический dump PostgreSQL покрывает не все способы восстановления. Копирование файлов кластера, continuous archiving и point-in-time recovery имеют другие предпосылки. Команда, которая проверила custom archive одной базы, не доказала восстановление всего кластера и не измерила готовность приложения.</p>\n<p>Учебный сценарий не проверяет сеть, права, секреты, размер production-данных, скорость передачи, репликацию, WAL, внешние object storage и работу зависимых сервисов. Он также не даёт RPO или RTO. Время учебной команды становится RTO только после измерения на разрешённой цели с описанными условиями. Дата архива сама по себе не является RPO.</p>\n<p>Если drill не может затронуть реальное окружение, это не недостаток статьи. Нужно отделить проверенный учебный контракт от непроверенной инфраструктуры и назначить следующий безопасный эксперимент. Такой итог точнее, чем заявление о полной готовности к аварии.</p>\n<h2>Критерий готовности</h2>\n<p>Минимальный проверяемый критерий выглядит так: команда предъявляет manifest и фактический archive; SHA-256 совпадает; список объектов покрывает заявленный scope; restore выполнен только в изолированной цели; источник не изменился; структурные и смысловые checks прошли; время и версия инструмента записаны; непокрытые области явно перечислены.</p>\n<p>Если хотя бы один пункт неизвестен, verdict должен быть ограниченным: «архив найден», «байты совпали» или «учебная база восстановилась». Нельзя сокращать его до «резервное копирование работает». Копия становится рабочим механизмом только там, где команда может повторить путь, увидеть отрицательный результат и безопасно остановиться.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://www.postgresql.org/docs/current/backup.html' target='_blank' rel='noopener noreferrer'>PostgreSQL: Backup and Restore</a> — официально разделяет логические dump, файловое копирование и continuous archiving.</li><li><a href='https://www.postgresql.org/docs/current/app-pgrestore.html' target='_blank' rel='noopener noreferrer'>PostgreSQL: pg_restore</a> — описывает просмотр и восстановление архивов, созданных в форматах pg_dump.</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> — официальный стандарт семейства SHA-2; digest проверяет bytes, но не семантическую полноту восстановления.</li></ul>"
}