Симптом выглядит успокаивающе: по расписанию появляется файл архива, задача заканчивается без ошибки, а в таблице есть зелёная отметка. Но при первом вопросе «что именно вернётся из этой копии?» обычно выясняется, что никто не открывал archive, не сверял его состав и не поднимал отдельного кандидата на restore. Цена такой уверенности — не только потеря времени в сбое. Можно восстановить неполный набор данных, затронуть исходную базу неверной командой или обнаружить отсутствующую зависимость тогда, когда выбирать уже некогда.
В октябре 2020 года я бы не называл это зрелой SRE-программой и не рисовал реальные RPO или RTO. Ниже — учебный restore drill для вымышленной логической базы с двумя схемами. Его задача скромнее: отделить факт создания архива от доказательства, что известный набор байтов можно развернуть в изолированном кандидате и проверить заранее выбранными запросами. Это runbook, который ещё надо измерить и привязать к конкретному проекту, а не обещание recovery в production.
Backup и restore отвечают на разные вопросы
Backup отвечает на вопрос «какие байты и с каким scope мы сохранили в этот момент». Restore отвечает на другой: «получится ли из этих байтов собрать нужный набор объектов в безопасной цели и доказать это проверками». Между ними лежат формат archive, версия инструмента, список объектов, права, внешние файлы и само место, куда идёт восстановление. Поэтому строка «backup completed» — полезное событие, но не финальный verdict. Она сообщает о завершении одной команды, а не о работоспособности будущего маршрута.
Для логической базы PostgreSQL удобен формат custom, который затем читает pg_restore. Документация PostgreSQL 12 отдельно описывает SQL dump, файловую копию и continuous archiving: это не три названия одной операции, а разные подходы с разными допущениями. В материале не смешиваем их. Берём только учебный logical dump, не делаем вывод о полном кластере и не выдаём проверку двух схем за готовность к point-in-time recovery. Такая граница нужна, чтобы runbook не стал набором команд без предмета восстановления.
До команды называем scope и цену пропуска
Симптом неполного backup обычно начинается не с повреждённого файла, а с неоговорённой границы. Владелец ожидает роли, таблицы, вложения или соседнюю схему, а выбранная команда сохраняет только часть базы. Причина в том, что удобная выборка --schema и особенно --table не превращает выбранный объект в самостоятельную систему. PostgreSQL предупреждает: dump отдельной схемы или таблицы может не включить зависимости, нужные для восстановления в чистой базе. Проверка здесь не догадка по размеру файла, а записанный список включений и исключений до запуска.
| Поле | Учебное значение | Какой риск снимает | Что не доказывает |
|---|---|---|---|
| Логическая единица | training_catalog | не путает один database dump с cluster backup | наличие ролей и tablespaces |
| Включено | schema:catalog, schema:reference | даёт проверяемый список ожидаемых объектов | что внешние файлы вернутся сами |
| Исключено | cluster roles, tablespaces, external files | не прячет заведомо отсутствующие области | что исключения допустимы для конкретной задачи |
| Формат | pg_dump custom (-Fc) | связывает archive с маршрутом через pg_restore | совместимость любой версии без отдельной проверки |
| Цель drill | изолированный учебный кандидат | не даёт restore писать в источник | фактическое время восстановления |
В этой таблице сознательно нет обещанного числа минут. Плановое окно можно записать отдельно как вопрос к следующей проверке: когда началось восстановление, когда archive стал читаемым, когда прошли смысловые запросы. Пока эти точки не сняты на конкретном стенде, называть их RTO было бы подменой измерения. Аналогично, дата archive сама по себе не даёт RPO: она не объясняет, какие изменения между копиями допустимо потерять и входит ли в scope журнал изменений.
Manifest кладём рядом с archive, а не в память команды
Имя файла обычно содержит дату, но дата не заменяет контракт. Рядом с archive нужен небольшой manifest: версия его формы, идентификатор учебной копии, алгоритм checksum, scope, формат, список ожидаемых relations и критерии учебного restore. Manifest не содержит пароль, URL или access key. Он не делает копию криптографически защищённой и не назначает retention; его роль уже: не дать спустя неделю гадать, что именно предполагалось восстановить и каким результатом считается успех.
{
"manifestVersion": 1,
"backupId": "training-backup-2020-10-a",
"artifact": {
"file": "training-catalog-2020-10.dump",
"format": "pg_dump custom (-Fc)",
"sha256": "здесь хранится вычисленный hash",
},
"scope": {
"includes": ["schema:catalog", "schema:reference"],
"excludes": ["cluster roles", "tablespaces", "external files"],
},
"restoreChecks": {
"relations": ["catalog.items", "reference.codes"],
"rows": { "catalog.items": 3, "reference.codes": 2 },
},
"safety": "restore only into an isolated training candidate"
}
Checksum здесь проверяет конкретные байты. Если archive был обрезан при передаче, подменён другим файлом или повреждён после записи, hash из manifest не совпадёт и drill остановится до pg_restore. Но совпавший hash не означает, что схема подходит приложению, нужные роли существуют или семантическая проверка пройдёт. Это важная граница: checksum — gate перед restore, не сертификат работоспособности. Именно поэтому manifest хранит и технический digest, и независимый список смысловых checks.
Минимальная учебная последовательность
Команды ниже показывают форму маршрута, а не запускаются этой статьёй. Имя training_catalog вымышленное; пароль, host и строка подключения намеренно отсутствуют. Перед практическим применением команда должна выбрать способ аутентификации вне текста, проверить права и зафиксировать версию клиента рядом с manifest. Для учебной копии полезно сначала получить archive и его оглавление, а уже затем разрешать восстановление в отдельной цели.
# Учебная последовательность: команды не выполнялись этой статьёй.
# training_catalog — вымышленное имя логической базы, не адрес окружения.
pg_dump --format=custom --file=training-catalog-2020-10.dump training_catalog
shasum -a 256 training-catalog-2020-10.dump
pg_restore --list training-catalog-2020-10.dump
# Полученные hash и список объектов записывают в manifest рядом с архивом.
Список из pg_restore --list не заменяет восстановление, но он быстро ловит другой класс ошибок: archive другого формата, отсутствующий ожидаемый объект или не тот файл под правильным именем. Если оглавление не похоже на manifest, следующий шаг не «попробовать всё равно», а остановиться и выяснить, где расходятся scope, команда и опубликованный артефакт. Так диагностика остаётся короткой: симптом — список не совпал; причина — не тот archive или неполный scope; проверка — manifest плюс list; действие — не запускать restore.
Restore должен быть безопасной пробой, а не риском для источника
Самая опасная ошибка в runbook — команда, которую можно случайно выполнить против источника. Поэтому учебный маршрут заранее запрещает перезапись исходной базы, не использует --clean как «исправление» и требует отдельного кандидата. В нём нет реальных данных: набор relations и счётчиков синтетический. Даже успешный restore кандидата не даёт разрешения заменить источник; он только создаёт evidence, что конкретный archive и конкретный путь были проверены в оговорённой границе.
# Только изолированный учебный кандидат; источник не перезаписывается.
createdb training_restore_candidate
pg_restore --dbname=training_restore_candidate training-catalog-2020-10.dump
psql --dbname=training_restore_candidate --command="SELECT count(*) FROM catalog.items;"
psql --dbname=training_restore_candidate --command="SELECT count(*) FROM reference.codes;"
# Результат сравнивают с manifest, а не с ожиданием «команда завершилась без текста».
После команды важнее не её exit code, а результат проверок. Для учебного набора это существование двух relations и их ожидаемые маленькие счётчики. В прикладном проекте к ним добавятся версия схемы, критичный reference-набор, чтение одной записи без персональных данных или проверка migration state. Проверка не должна выполнять опасную бизнес-операцию и не должна зависеть от внешнего сервиса, если его нет в scope. Иначе drill превращается в нереплицируемый мини-инцидент.
Нумерованный маршрут для первого drill
- Сформулировать один вопрос восстановления: какой набор объектов нужен и чего в нём точно нет. Записать includes, excludes и владельца следующей проверки в manifest.
- Создать учебный logical archive выбранным инструментом и сразу сохранить его имя, формат, размер и вычисленный SHA-256 рядом с manifest.
- Сверить checksum и вывести оглавление archive. При расхождении остановить маршрут до restore: файл с неверными байтами нельзя «проверить дальше».
- Подготовить изолированный учебный кандидат. Явно подтвердить, что он не является источником и что runbook не содержит команд удаления исходных данных.
- Выполнить restore только в кандидате и собрать структурные checks: ожидаемые relations, версия схемы и безопасные счётчики или иные заранее оговорённые признаки.
- Записать verdict вместе с тем, что не проверялось: cluster roles, tablespaces, внешние файлы, реальные сроки и любой production recovery. По этому списку планировать следующий drill, а не закрывать тему зелёным статусом.
Что считать отрицательным результатом
Отрицательный результат drill не означает, что команда «сломала backup». Он показывает место, где контракт пока не выдерживает проверку. Hash не совпал — нельзя доверять артефакту. Archive читается, но в нём нет relation из manifest — неверен scope или выбран другой файл. Restore завершается, но счётчик не совпадает — недостаточен критерий или фактически сохранён не тот набор. Цель оказалась не изолированной — останавливаемся ещё до команды. У каждого случая одно действие: сохранить наблюдение, поправить manifest или runbook и повторить учебную пробу после изменения.
Имена, данные, timestamps, размеры, команды и результаты ниже учебные. Здесь нет реальной базы, access key, production-окружения или отчёта о disaster recovery.
Граница этой практики
Этот текст не назначает retention, не выбирает хранилище, не проверяет encryption, не запускает PostgreSQL и не измеряет время. Он также не утверждает, что custom dump подходит любой базе или что один logical archive покрывает роли, tablespaces и внешние вложения. Его результат — более честная точка старта: backup становится набором байтов с описанным scope, а restore — отдельной, безопасной и повторяемой проверкой. Когда появятся реальные требования и разрешённый стенд, этот учебный runbook можно расширять измерениями, но не заменять ими текущие доказательства.
Проверяемые источники
- PostgreSQL 12: Backup and Restore — разделяет SQL dump, копирование на уровне файловой системы и continuous archiving; у способов разные предпосылки и границы восстановления
- PostgreSQL 12: pg_dump — документирует согласованный логический dump, форматы custom и directory, а также ограничение выборки схемы или таблицы без зависимостей
- PostgreSQL 12: pg_restore — описывает восстановление non-plain archive, просмотр содержания и параметры, которые нужно сверять с версией выбранного инструмента
- NIST FIPS 180-4: Secure Hash Standard — задаёт семейство SHA-2; hash сверяет байты артефакта, но сам по себе не доказывает пригодность результата к восстановлению