Симптом в разборе простой: команда видит свежий archive, но не может ответить, что произойдёт после команды restore. Неизвестны scope, ожидаемые объекты, допустимая цель и проверка результата. Цена не в том, что «не хватает документации»; в реальном сбое такой пробел превращает восстановление в эксперимент над источником. Оператор выбирает файл по дате, запускает знакомую команду и получает либо неполный набор, либо ещё одну неизвестную переменную вместо доказательства.
Ниже не production-инцидент и не отчёт о достигнутом RPO/RTO. Это синтетический разбор учебной пробы: две логические схемы, один custom archive, manifest с SHA-256 и изолированный кандидат. Цель — показать, как runbook превращает расплывчатый вопрос «backup есть?» в последовательность «симптом → причина → проверка → действие». После такой пробы можно честно перечислить непроверенные границы и назначить следующую проверку, не притворяясь, что уже проведён disaster recovery.
Собираем пакет доказательств до первой команды
Для drill нужны не только archive и пароль к базе — пароль в этом материале намеренно отсутствует. Нужны четыре безопасных артефакта: manifest, сам archive, checksum и лист ожидаемых checks. Manifest связывает файл с scope; checksum связывает manifest с конкретными bytes; список checks связывает restore с наблюдаемым результатом. Если начать с команды без этих связей, любая ошибка будет выглядеть одинаково: «не получилось». Тогда нельзя отличить повреждённый файл от неверной цели или разумно спорить, была ли нужная таблица вообще включена.
Учебный manifest не хранит реальных имён окружений и не выдаёт секреты. Он использует вымышленные labels catalog и reference, а excludes прямо называют cluster roles, tablespaces и external files. Это важнее красивого JSON: исключённая область должна быть видна до drill, чтобы позже никто не искал её в archive. PostgreSQL 12 разделяет dump одной базы и cluster-wide объекты; если роли нужны будущей цели, это отдельная часть плана, а не скрытый побочный эффект pg_dump.
Учебная шкала не равна измеренному RPO или RTO
В runbook полезно записывать последовательность событий, но нельзя подменять их историей о измеренном времени. Отметка restore_started нужна, чтобы в следующем разрешённом drill измерить интервал до semantic_checks_passed. До этого она не становится RTO. Дата archive помогает задать вопрос о максимально допустимой потере данных, но не становится RPO без требований к данным, частоты копий и понимания журналов изменений. В таблице ниже слова «вопрос» намеренны: они удерживают автора от ложной цифры.
| Наблюдение | Что можно записать сейчас | Какой вопрос остаётся | Следующее действие |
|---|---|---|---|
| Дата и ID archive | какой учебный артефакт участвовал в пробе | какая потеря данных допустима между копиями | согласовать требования данных до следующего drill |
| Начало и конец restore | две отметки учебного маршрута | сколько времени допустимо для конкретного сервиса | измерить на разрешённой цели, не называть интервал RTO заранее |
| Checksum | bytes совпали или нет | достаточен ли scope для прикладного восстановления | сверить includes/excludes и checks |
| Structural checks | две relations и синтетические счётчики | готово ли всё приложение и внешние зависимости | расширить contract или зафиксировать исключение |
| Isolated target | источник не был частью restore | подходит ли цель для будущего сценария | описать требования к отдельному стенду |
Такая таблица делает разбор полезнее отчёта «успешно». Она показывает, что было фактически наблюдаемо, и не превращает план в факт. Если будущая команда измерит длительность, ей всё равно придётся указать условия: размер данных, версия инструмента, параллелизм, доступность цели, набор checks. Без условий одно число не переносится на следующий restore. Здесь автор 2020 года формирует дисциплину фиксации, а не заявляет опыт управления программой надёжности.
Один synthetic trace вместо легенды о катастрофе
Вместо реального лога используется короткий учебный trace. Он показывает порядок решений: сначала читают scope, затем сравнивают digest, потом смотрят list archive, восстанавливают только в отдельного кандидата и только после этого запускают semantic checks. У каждой строки есть значение для диагностики. Если trace заканчивается после hash, это не «почти успешный restore»: это означает, что evidence о результате ещё не получен.
{"step":"scope_read","result":"catalog + reference; cluster roles excluded","kind":"training"}
{"step":"artifact_hash","result":"matches manifest","kind":"training"}
{"step":"archive_list","result":"expected relations listed","kind":"training"}
{"step":"restore_candidate","result":"completed without source overwrite","kind":"training"}
{"step":"semantic_check","result":"2 relations and expected row counts","kind":"training"}
{"step":"verdict","result":"evidence recorded; next drill still required","kind":"training"}
Этот пример намеренно не содержит host, username, credentials, реальных timestamps или описания пользовательских данных. В настоящий runbook можно добавить минимальный correlation ID и место хранения evidence, но не следует логировать сам archive, содержимое dump или секретные connection strings. Для учебного случая хватает backup ID, версии manifest, результата gate и причины остановки. Это делает заметки сравнимыми между drills и не превращает журнал в ещё одну копию чувствительных данных.
Диагностика начинается с вопроса о scope
Представим первый симптом: archive существует, но restore list не содержит reference.codes. Самая вероятная причина не в checksum: hash может идеально совпадать с теми байтами, которые изначально были сохранены неполно. Проверка — сравнить list archive с scope.includes и restoreChecks.relations из manifest. Действие — остановить restore, открыть creation command и решить, должен ли scope расшириться либо relation была внешней зависимостью, которую нужно описать отдельно. Повторный запуск restore в надежде, что relation «появится», не добавляет доказательств.
Второй симптом: list выглядит правильно, но checksum не совпал. Причина находится между записью manifest и текущим файлом: другой archive под тем же именем, неполная передача, повреждение или ошибка выбора. Проверка однозначна — повторно вычислить SHA-256 над фактически используемыми bytes. Действие тоже однозначно — не восстанавливать этот файл. Нужно получить доверенный artifact или создать новую учебную копию, а затем обновить manifest только вместе с ним. Подмена hash в JSON «чтобы пройти gate» уничтожает сам смысл контроля.
После checksum всё ещё нельзя трогать источник
Третий симптом: bytes и list прошли, но оператор собирается восстановить «туда, где видно данные». Причина — runbook не делает безопасную цель явным входом. Проверка должна быть предельно скучной: имя кандидата и запрет перезаписи источника зафиксированы до команды. Действие — создать отдельного учебного кандидата и повторить проверку цели. Даже маленький drill не оправдывает запуск с destructive flags ради удобства. Если под рукой нет безопасной цели, честный verdict — «restore не проверен», а не «backup успешен».
Четвёртый симптом: restore завершился, но relation пуста или schema version не та. Причина может быть в scope, версии инструмента, неполном содержимом или в слишком слабом критерии. Проверка состоит из заранее записанных read-only запросов против кандидата и сопоставления с manifest. Действие — сохранить фактический verdict, не маскировать его ручной вставкой строк и не менять expected count после факта. Если критерий был неверным, обновляют contract, создают новый артефакт и повторяют пробу как новую версию, а не переписывают историю старой.
Фикстура проверяет контракт, а не инфраструктуру
В revision-модуле есть небольшая in-memory fixture. Она строит synthetic manifest для двух relations, вычисляет SHA-256 фиксированных bytes и проверяет восемь условий: digest совпал, scope полон, safety запрещает overwrite источника, relations и rows совпали, изменение bytes отклонено, а суженный scope отклонён. У fixture нет файла, сети, PostgreSQL, cron, облачного хранилища или настоящего restore. Поэтому её можно запускать как проверку логики статьи, но нельзя прикладывать вместо реального drill.
# Запуск учебной fixture из revision-модуля:
node web/scripts/upgrade-2020-10.mjs --verify-fixture
# Ожидаем не скорость, а логические результаты:
# checksumMatches=true, scopeMatches=true, expectedRowsPresent=true
# alteredBytesRejected=true, narrowedScopeRejected=true, sourceWasUntouched=true
Нумерованный маршрут разбора
- Назвать backup ID и прочитать manifest целиком: format, includes, excludes, checksum и checks. Не выбирать archive только по дате в имени.
- Сверить scope с вопросом восстановления. Если нужная область не заявлена, зафиксировать это как design gap, а не как сбой
pg_restore. - Вычислить checksum фактического archive и сравнить с manifest. При несовпадении остановить маршрут до попытки чтения или restore.
- Получить list archive и сравнить его с ожидаемыми relations. Расхождение отделяет проблему creation path от проблемы target.
- Подтвердить isolated candidate и запрет действий над источником. Отсутствие безопасной цели — корректная причина не запускать drill.
- Восстановить archive в кандидате, выполнить read-only structural and semantic checks, сохранить результат и список непокрытых областей.
- Сделать из найденного пробела конкретную правку manifest, команды или checks и назначить новый учебный drill. Не превращать один запуск в заявление о готовности к любой аварии.
Как выглядит честный итог
Честный verdict после учебного drill может быть положительным и всё равно ограниченным: «вымышленный custom archive с двумя схемами совпал с manifest, развернулся в isolated candidate, прошёл два структурных checks; роли, tablespaces, внешние файлы, реальные требования к потере данных и сроки не проверялись». Такой текст полезнее громкого «резервное копирование настроено». Он сохраняет путь, по которому другой инженер сможет повторить проверку, и оставляет список того, что ещё нужно обсудить до любого production recovery.
Документация PostgreSQL помогает не размывать эту границу: logical dump одной базы, archive format и восстановление через pg_restore — конкретные механизмы, а не общая метафора «бэкапа». NIST-определённый SHA-256 даёт проверку bytes, но не заменяет semantic check. Соединяя их в manifest и drill, мы не обещаем невозможного; мы получаем один контролируемый путь, который можно сделать лучше после следующего измерения.
Имена, данные, timestamps, размеры, команды и результаты ниже учебные. Здесь нет реальной базы, access key, production-окружения или отчёта о disaster recovery.
Проверяемые источники
- PostgreSQL 12: Backup and Restore — разделяет SQL dump, копирование на уровне файловой системы и continuous archiving; у способов разные предпосылки и границы восстановления
- PostgreSQL 12: pg_dump — документирует согласованный логический dump, форматы custom и directory, а также ограничение выборки схемы или таблицы без зависимостей
- PostgreSQL 12: pg_restore — описывает восстановление non-plain archive, просмотр содержания и параметры, которые нужно сверять с версией выбранного инструмента
- PostgreSQL 12: pg_dumpall — показывает отдельную область cluster-wide объектов, включая роли; один dump базы не следует выдавать за копию всего кластера
- NIST FIPS 180-4: Secure Hash Standard — задаёт семейство SHA-2; hash сверяет байты артефакта, но сам по себе не доказывает пригодность результата к восстановлению