DevOps и инфраструктура

Backup есть — а восстановиться можно? Как проверять резервные копии на реальный restore

В панели мониторинга горит зелёный статус:

Backup: OK
Последняя копия: сегодня, 03:00
Размер: 2.4 GB

Кажется, всё хорошо.

База резервируется каждый день.

На сервере лежат архивы.

Есть отдельная папка backups.

Можно поставить галочку:

Резервное копирование настроено.

А потом происходит авария.

Основная база потеряна.

Администратор берёт последний backup, запускает восстановление — и получает ошибку.

Например:

archive corrupted

или:

decryption failed

или:

role does not exist

или:

extension is missing

или восстановление формально проходит, но приложение после него не запускается.

В этот момент выясняется неприятная вещь:

наличие backup и возможность восстановиться — не одно и то же.

Поэтому в production нас интересует не вопрос:

Создаётся ли резервная копия?

А гораздо более строгий:

Сможем ли мы из этой конкретной копии восстановить работающую систему?

Разберём, как превратить резервное копирование из формального cron-задания в проверяемый recovery-процесс.


Backup — это только половина задачи

Упрощённая схема обычно выглядит так:

Production DB
     ↓
pg_dump
     ↓
backup.pgdump

После появления файла система считает задачу выполненной.

Но между:

backup.pgdump существует

и:

из него можно восстановить production

находится ещё несколько важных проверок.

Например:

Файл полностью записан?

Он не обрезан?

Шифрование завершилось?

Ключ расшифровки существует?

Checksum совпадает?

Формат понимает pg_restore?

Внутри действительно нужная база?

Создаются ли таблицы?

Восстанавливаются ли данные?

Есть ли необходимые роли?

Есть ли extensions?

После restore запускается ли приложение?

Пока эти вопросы не проверены, backup является кандидатом на восстановление, а не доказанной точкой восстановления.


Самая опасная строка — «backup completed successfully»

Представим скрипт:

pg_dump database > backup.sql

После выполнения shell возвращает:

exit code 0

Это хороший сигнал.

Но даже он не отвечает на вопрос:

Умеем ли мы вернуть из этого файла рабочий сервис?

Например, сама база может восстановиться.

Но production ещё использует:

пользовательские файлы;
Object Storage;
ключи шифрования;
environment;
внешние credentials.

Получается:

Database restore: PASS
Application recovery: FAIL

Поэтому recovery нужно рассматривать на нескольких уровнях.


Что вообще означает «восстановиться»

Есть как минимум три разных уровня.

Уровень 1. Файл backup читается

Например:

checksum совпадает;
шифрование корректно;
архив открывается.

Это проверка целостности артефакта.

Уровень 2. База реально восстанавливается

Создаём отдельную пустую БД:

restore_test

и выполняем настоящий restore.

После него существуют:

таблицы;
индексы;
constraints;
данные.

Уровень 3. Восстановленный продукт работает

Приложение подключается к восстановленной базе.

Проходят критические проверки:

пользователь существует;
проект открывается;
данные связаны;
миграции согласованы;
основные запросы выполняются.

Именно третий уровень больше всего похож на то, что понадобится при реальной аварии.


Почему checksum недостаточно

Контрольная сумма чрезвычайно полезна.

Например:

SHA-256:
a8b5...

При создании backup мы сохраняем:

backup file
+
manifest

Позже считаем SHA-256 повторно.

Если:

expected != actual

копию нельзя считать целой.

Но если checksum совпал, мы доказали только:

Файл не изменился после создания checksum.

Мы ещё не доказали:

Внутри хороший backup.

Если сам архив был создан неправильно и сразу после этого для него рассчитали checksum, хеш прекрасно совпадёт с повреждённым файлом.

Поэтому:

checksum verification

и:

restore verification

решают разные задачи.


Шифрование добавляет ещё один уровень риска

Production backup часто содержит:

пользователей;
email;
проекты;
финансовые данные;
внутреннюю переписку.

Хранить такой архив в открытом виде не всегда разумно.

Поэтому backup можно шифровать.

Например:

pg_dump
   ↓
AES-256-GCM
   ↓
backup.pgdump.aesgcm

Теперь к recovery добавляется критическая зависимость:

ключ шифрования.


Зашифрованный backup без ключа равен отсутствующему backup

Представим идеальную ситуацию:

100 ежедневных backup

все:

SHA-256 PASS

и все находятся на независимом сервере.

Но encryption key существовал только:

в /root/.env
на потерянном production VPS.

После аварии имеем:

100 отличных
нечитаемых файлов.

Поэтому backup strategy должна отдельно отвечать:

Где находится ключ?
Кто имеет к нему доступ?
Как он восстанавливается?
Что происходит при rotation?
Есть ли независимая защищённая копия?

Мы разделяем проверку backup на несколько последовательных барьеров

Практическая схема может выглядеть так:

Production DB
     ↓
Disk preflight
     ↓
Temporary dump
     ↓
Encryption
     ↓
SHA-256 manifest
     ↓
Decrypt verification
     ↓
Restore verification
     ↓
Application checks
     ↓
Publish as known-good backup

Каждый следующий этап повышает уверенность.


Первый барьер — достаточно ли вообще места для backup

Есть довольно неприятный сценарий.

На VPS осталось:

3 GB

Текущая база занимает:

2.7 GB

Cron начинает backup.

Через некоторое время диск заканчивается.

Результат:

backup оборван;
PostgreSQL тоже потерял
свободное место;
логи больше не пишутся.

То есть сама процедура защиты данных создаёт production-инцидент.


Поэтому backup начинается с disk preflight

В одном из production-контуров, который мы проверяли, перед pg_dump оценивается размер PostgreSQL и требуемое свободное пространство.

Логика концептуально выглядит так:

DB size
   ↓
Estimated backup size
   ↓
Current free disk
   ↓
Safety reserve

Backup разрешается только если после операции останется эксплуатационный запас.

Например:

free
-
estimated backup
>=
5 GiB reserve

Само конкретное значение зависит от проекта.

Важно другое:

backup не должен иметь право съесть последние свободные гигабайты production-сервера.


До этой проверки полезно удалить старые временные файлы

Предыдущий backup мог аварийно завершиться и оставить:

backup.tmp

размером несколько гигабайт.

Если следующий запуск считает свободное место, не очистив stale temporary files, он может либо ошибочно отказаться от backup, либо постепенно накопить мусор.

Поэтому последовательность:

cleanup stale temp
↓
check disk
↓
start new backup

надёжнее.


Второй барьер — писать сначала во временный файл

Плохой вариант:

backup-latest.pgdump

перезаписывается непосредственно во время создания новой копии.

Представим предыдущий backup рабочий.

Новый процесс начал запись:

backup-latest.pgdump

и на 63% аварийно завершился.

Теперь:

старого хорошего backup нет;
новый неполный.

Мы сами уничтожили последнюю доказанно рабочую копию.


Поэтому используется принцип known-good backup

Например:

ritm-latest.pgdump.aesgcm

является текущей подтверждённой копией.

Новый backup сначала создаётся отдельно:

ritm-next.pgdump.tmp

Дальше проходит проверки.

И только когда новая копия признана валидной:

new backup
     ↓
atomic replace
     ↓
latest

Если любой этап завершился ошибкой:

старый known-good backup
остаётся нетронутым.

Это маленькое архитектурное решение сильно меняет надёжность.


Третий барьер — проверить шифрование сразу после создания

Если backup зашифрован:

dump
↓
encrypt
↓
encrypted backup

нельзя считать успешным только тот факт, что появилась .aesgcm.

Мы можем выполнить обратную операцию:

encrypted backup
↓
decrypt
↓
temporary decrypted dump

Если расшифровка не проходит, backup нельзя публиковать как рабочий.

Таким образом мы сразу проверяем:

ключ подходит;
формат корректен;
authentication tag проходит;
данные реально расшифровываются.

Почему проверять нужно новым чтением файла

Если программа только что зашифровала byte stream и сама себе сказала:

Всё хорошо.

это слабее, чем:

закрыть output
↓
снова открыть готовый файл
↓
расшифровать
↓
проверить

Второй вариант ближе к реальной процедуре восстановления.


Четвёртый барьер — настоящий pg_restore

Для custom/archive-форматов pg_dump PostgreSQL предоставляет pg_restore.

Но наличие команды:

pg_restore backup.pgdump

в документации проекта ещё не означает, что конкретная копия успешно восстановится.

Нужно это выполнить.


Restore нельзя тестировать поверх production

Очевидно, но чрезвычайно важно.

Не:

production database
← restore test

а:

temporary isolated database

Например:

app_restore_test_20260928

Схема:

Backup
  ↓
Decrypt
  ↓
Create empty DB
  ↓
pg_restore
  ↓
Validation
  ↓
Drop test DB

Production при этом не изменяется.


Сам факт завершения pg_restore — ещё не конец

После restore полезно проверить данные.

Например:

SELECT COUNT(*) FROM users;
SELECT COUNT(*) FROM projects;
SELECT COUNT(*) FROM migrations;

Но одних COUNT(*) тоже мало.


Лучше проверять бизнес-инварианты

Например:

Есть хотя бы один пользователь?

Нет проектов без владельца?

Последняя migration присутствует?

Количество критичных таблиц ожидаемое?

Существуют необходимые индексы?

Нет broken foreign keys?

Или конкретный заранее известный контрольный объект.

Например:

system settings row exists

Ещё сильнее — подключить приложение к восстановленной базе

Это уже очень полезный restore drill.

Вместо проверки исключительно SQL:

Restored PostgreSQL
        ↓
Test application instance
        ↓
/api/ready
        ↓
Critical queries

Теперь проверяется не только PostgreSQL.

Мы проверяем совместимость:

backup
+
schema
+
current application

Почему это важно после развития проекта

Представим backup физически прекрасен.

Но новая версия приложения ожидает:

schema version 74

В копии:

schema version 69

В нормальном recovery процессе это может быть совершенно допустимо — нужно просто прогнать migrations.

Но если процедура этого не предусматривает, аварийное восстановление внезапно потребует импровизации.

Лучше знать это заранее.


Restore drill должен проверять и migrations

Например:

Backup from version 3.2
        ↓
Restore
        ↓
Run migrations
        ↓
Current version 3.4
        ↓
Application checks

Теперь мы отвечаем на значительно более реальный вопрос:

Можем ли мы взять сохранённую копию и действительно поднять текущий продукт?

Резервная копия БД — это ещё не резервная копия продукта

Это особенно важно для современных веб-приложений.

Например PostgreSQL хранит:

users;
projects;
file metadata;
permissions.

А S3/Object Storage:

documents;
images;
audio.

Если восстановить только БД:

File #1842
storage_key = abc...

а object abc... потерян, пользователь увидит запись о документе, но скачать его не сможет.


Нужно восстановить весь persistent state

В зависимости от продукта:

PostgreSQL
+
Object Storage
+
critical secrets
+
configuration

Иногда:

Redis

вообще можно не резервировать, если он используется только как cache.

Но если там хранится уникальное состояние, ситуация другая.

Каждый persistent component нужно классифицировать заранее:

Можно восстановить из других источников?

или:

Его потеря означает потерю данных?

Очень полезно составить recovery inventory

Например:

КомпонентНужно backupПочему
PostgreSQLДаОсновные пользовательские данные
Object StorageДаПользовательские файлы
Redis cacheНетПерестраивается
Application imageНе обязательноВоспроизводится из release
.env secretsДа, безопасноБез них часть данных/интеграций недоступна
TLS certificateОбычно нетМожно перевыпустить
LogsЗависитНужны для аудита/диагностики

Теперь disaster recovery становится конкретным.


Главное — не сохранять то, что можно воспроизвести

Если Docker image можно снова собрать из:

Git
+
release tag
+
Dockerfile

он не так ценен, как:

пользовательская база.

Backup должен в первую очередь защищать невоспроизводимые данные.


Но секреты являются особым случаем

Например данные в Object Storage зашифрованы application-level ключом.

Исходный код воспроизводится.

Файлы хранятся.

Но ключ исчез.

Получается:

source ✓
database ✓
objects ✓
key ✗

Результат:

restore impossible.

Поэтому recovery inventory должен учитывать зависимости между компонентами.


Можно сделать красивую карту восстановления

Например:

Encrypted DB Backup
        │
        ├── requires → Backup Key
        │
        └── restores → PostgreSQL
                           │
                           ├── requires → migrations
                           │
                           └── used by → Application

Object Storage Backup
        │
        └── requires → Storage credentials

Такая схема иногда полезнее десятка страниц документации.


Offsite backup должен быть действительно offsite

Допустим:

production:
VPS A

backup:

/opt/app/backups
на VPS A

Это полезно при:

случайном DROP;
ошибочной migration;
удалении записи.

Но если:

VPS A потерян полностью

backup исчезает вместе с ним.


Поэтому нужна независимая копия

В production-контуре, который мы проверяли, удалённая backup-цель отделяется от основного storage.

Идея:

Primary VPS
    ↓
encrypted backup
    ↓
independent offsite storage

Это защищает уже от другого класса аварий.


Но offsite не означает «копируем бесконечно»

Если каждый день хранить полный backup навсегда:

Day 1
Day 2
...
Day 1000

storage будет расти бесконечно.

Нужна retention policy.

Например концептуально:

daily backups
→ ограниченное окно

Точный срок определяется требованиями бизнеса.


Retention — это не только экономия места

Представим данные были повреждены:

10 дней назад

Но никто этого не заметил.

Если хранится только:

последний backup

он уже содержит повреждённое состояние.

Несколько исторических точек дают выбор:

T-1
T-2
T-7
...

Для критичных систем могут использоваться и более развитые схемы — например continuous archiving и point-in-time recovery.


Но больше backup не всегда значит лучше

Есть две независимые характеристики:

Retention

и:

Verified recoverability

Можно иметь:

100 непроверенных копий

и ни одной рабочей.

И:

7 проверяемых копий

с понятной процедурой восстановления.

В аварии второй вариант часто ценнее.


Нужно контролировать возраст последнего backup

Представим backup job перестал работать неделю назад.

Файлы в каталоге существуют.

Поэтому поверхностная проверка:

backups directory not empty

говорит:

OK

Хотя последняя копия:

7 days old.

Это production-дефект.


Мониторинг должен проверять freshness

Например в одном из проверенных production-контуров монитор следит за возрастом последней успешной зашифрованной копии и считает её проблемной при превышении заданного окна — порядка суток с техническим запасом.

То есть проверяется не:

backup exists?

а:

now - latest_verified_backup
<= allowed age?

Это превращает молчаливый отказ cron в alert

Без monitoring:

Day 1:
backup ✓

Day 2:
job broken

Day 30:
авария

И только тогда выясняется:

Последняя копия месячной давности.

С monitoring:

backup stale
↓
alert

проблему можно исправить до настоящей аварии.


Backup job тоже должен иметь наблюдаемое состояние

Например:

Started:
03:00:01

Dump:
PASS

Encrypt:
PASS

Checksum:
PASS

Decrypt verify:
PASS

Offsite upload:
PASS

Completed:
03:03:42

При ошибке:

Stage:
OFFSITE_UPLOAD

Error:
timeout

Это намного полезнее:

backup failed.

Очень важно различать local backup и disaster recovery

Local backup отвечает:

Как быстро откатить данные после обычной ошибки?

Offsite:

Как восстановиться после потери основной инфраструктуры?

PITR:

Как вернуться к конкретному моменту?

Restore drill:

Работает ли вся эта система вообще?

Это разные задачи.


Хорошая backup-стратегия начинается с RPO

Можно не использовать термин, но вопрос простой:

Сколько данных бизнес готов потерять?

Если backup выполняется:

раз в 24 часа

в худшем случае можно потерять почти:

24 часа изменений.

Для блога это может быть приемлемо.

Для активно используемой CRM — уже болезненно.

Для финансовой системы — возможно неприемлемо.


Второй вопрос — RTO

Сколько времени допустимо восстанавливать сервис?

Например:

15 минут?
2 часа?
8 часов?
сутки?

Если база занимает:

500 GB

а restore никогда не измерялся, обещание:

Восстановимся быстро.

не имеет технической основы.


Restore drill позволяет получить реальную цифру

Мы можем измерить:

decrypt: 3 min
create DB: 10 sec
pg_restore: 18 min
migrations: 2 min
application checks: 1 min

И получить:

technical restore path
≈ 24 min

Это уже факт.


Но RTO включает больше, чем pg_restore

При полной потере VPS понадобится:

создать новый сервер;
установить runtime;
получить secrets;
восстановить DB;
вернуть files;
настроить DNS;
проверить HTTPS;
запустить приложение.

То есть настоящий disaster recovery test может быть значительно шире обычного database restore test.


Поэтому полезны два разных теста

Быстрый автоматический restore-check

Регулярно:

latest backup
↓
decrypt
↓
restore to isolated DB
↓
SQL checks
↓
PASS / FAIL

Он обнаруживает большую часть проблем.

Полный disaster recovery drill

Периодически:

чистая инфраструктура
↓
documentation only
↓
backup
↓
secrets
↓
restore
↓
application launch
↓
critical E2E

Это уже проверка всей способности бизнеса восстановить продукт.


Полный DR-test можно проводить без реальной аварии

Например создаём:

temporary VPS / isolated environment

И ставим задачу:

Считайте production потерянным.

Использовать можно только:

документацию;
backup;
release;
хранилище секретов.

После этого пытаемся поднять продукт.


Такой тест быстро обнаруживает скрытые зависимости

Например:

Для запуска нужен файл, который лежит только в /root старого VPS.

Или:

Никто не знает пароль от remote backup.

Или:

Restore-инструкция ссылается на старый Docker image.

Или:

После восстановления не запускается worker.

Или:

Object Storage существует, но ключ восстановления потерян.

Ни одна из этих проблем не видна по зелёному:

backup success.

Backup должен быть автоматизирован, restore — документирован и проверяем

Создание копий происходит постоянно.

Поэтому его логично автоматизировать.

Restore выполняется реже.

Но именно поэтому процедура должна быть особенно хорошо задокументирована.

Например:

1. Получить backup
2. Получить encryption key
3. Verify SHA-256
4. Decrypt
5. Create PostgreSQL database
6. Restore
7. Run migrations
8. Restore object data
9. Start application
10. Run readiness
11. Run critical smoke tests

В экстренной ситуации никто не должен восстанавливать этот процесс по памяти.


Особенно полезно, если restore выполняет не автор backup-скрипта

Это отличный тест документации.

Разработчик, написавший backup, знает:

После команды 7 надо ещё вручную сделать X.

Но в инструкции этого нет.

Сам он автоматически выполнит X и даже не заметит пробел.

Другой инженер — нет.

Если другой человек способен восстановить систему только по документации, процедура действительно переносима.


Самая плохая backup-система зависит от одного человека

Например:

Где ключ?
— У разработчика.

Как restore?
— Он знает.

Где offsite?
— Нужно спросить его.

Технически backup есть.

Организационно recovery отсутствует.


Полезно отделить backup ownership от production access

Например технический процесс может иметь отдельные credentials только для:

write backup

а restore credentials храниться более строго.

Это зависит от модели безопасности проекта.

Главный принцип:

компрометация production-сервера не должна автоматически позволять незаметно уничтожить все независимые резервные копии.


Immutable или защищённая история повышает устойчивость

Представим злоумышленник получил полный доступ к production.

Он удаляет:

database

а затем:

backups.

Если offsite storage доступен теми же credentials с полным delete:

disaster recovery тоже уничтожен.

Для критичных проектов имеет смысл продумывать отдельные права, retention или другие механизмы защиты backup от одновременного удаления вместе с production.


Restore нужно проверять после изменения backup-процесса

Например обновили:

PostgreSQL;
формат dump;
encryption;
storage provider.

Самое неправильное:

Backup job зелёный — значит всё нормально.

После существенного изменения нужно снова пройти restore.


То же относится к rotation ключей

Было:

Key A

Стало:

Key B

Новые backups шифруются B.

Но старые:

A.

Если rotation уничтожил A, старая история перестала быть восстанавливаемой.

Нужно заранее решить:

храним старые keys?

или:

reencrypt backups?

Restore-test должен использовать копию, а не оригинал

Проверяя архив, мы не должны случайно повредить единственный known-good backup.

Работаем:

source backup
   ↓
read-only
   ↓
temporary restore workspace

После проверки temporary artifacts удаляются.

Исходная копия остаётся неизменной.


И сам restore-check не должен заполнить production disk

Здесь появляется забавная рекурсия.

Чтобы проверить backup размером:

20 GB

может потребоваться:

20 GB decrypted dump
+
30 GB restored PostgreSQL

Если всё выполнять на основном VPS без проверки свободного места, тест восстановления может сам положить production.

Поэтому полноценный restore лучше выполнять:

в изолированной среде

или предварительно рассчитывать необходимый headroom.


Это одна из причин разделять быстрый verify и полный restore

На каждом backup:

checksum
+
decrypt verification

может быть относительно дешёвым.

Полный:

pg_restore
+
application test

может потреблять больше CPU, disk и времени.

Поэтому уровни проверки могут иметь разную периодичность.

Важно не конкретное расписание.

Важно, чтобы полный restore действительно когда-нибудь выполнялся автоматически или в рамках регулярного drill, а не существовал только как теоретическая инструкция.


Как тестировать восстановленную базу

Можно начать с технических проверок.

Например:

database opens;
expected tables exist;
migrations table present;
foreign keys valid;
critical queries execute.

Но затем полезны бизнес-проверки.

Например:

последний пользователь найден;
у него есть workspace;
проект связан с владельцем;
история задач читается;
файл имеет metadata.

Для каждого проекта набор будет своим.


Можно хранить контрольные показатели

Например production перед backup имеет:

users = 14 821
projects = 6 402

После restore:

users = 14 821
projects = 6 402

Это полезно.

Но не стоит сравнивать произвольное текущее production-состояние с backup, если между ними уже прошло несколько часов.

Контрольные значения лучше сохранять вместе с backup manifest или вычислять в момент создания копии.


Manifest может содержать больше, чем checksum

Например:

{
  "createdAt": "...",
  "database": "app",
  "format": "pg_dump-custom",
  "sha256": "...",
  "encrypted": true,
  "appVersion": "3.4.0",
  "schemaVersion": 74
}

Можно добавить:

record counts;
backup size;
PostgreSQL version.

Так backup становится самодокументируемым артефактом.


Но manifest тоже нужно защищать

Если злоумышленник может изменить:

backup

и затем пересчитать:

manifest SHA-256

обычный checksum не обнаружит намеренную подмену.

Если threat model включает такую атаку, понадобятся более сильные механизмы проверки происхождения и неизменности.

Для многих небольших продуктов главная задача checksum проще:

Обнаружить повреждение файла при хранении или передаче.

Проверка recovery должна попадать в release process

Например перед серьёзным production update:

current backup freshness
→ PASS

Если последняя подтверждённая копия:

старше допустимого окна

release можно остановить.

Это очень полезный принцип:

не выполнять рискованное изменение, пока нет свежей точки восстановления.


Особенно перед database migration

Например предстоит:

перестроить таблицу;
перенести миллионы строк;
удалить старое поле.

До migration:

verified backup required.

Если backup job сломан, сначала исправляется backup.

Потом migration.

Не наоборот.


Но backup не заменяет rollback

Это важное различие.

Rollback release:

v3.4
↓
v3.3

может занять минуты.

Restore database:

удалить/переименовать текущую БД
↓
создать новую
↓
восстановить гигабайты

намного дороже.

Поэтому backup — последний защитный уровень.

Не основной способ откатывать каждую неудачную версию.


Хорошая последовательность защитных механизмов

Например:

Tests
  ↓
Staging
  ↓
Safe migration
  ↓
Application rollback
  ↓
Database backup
  ↓
Disaster restore

Каждый следующий уровень используется, если предыдущий не помог.


Что происходит при реальном инциденте

Представим:

12:00
ошибочная migration удаляет данные.

Первое действие — остановить дальнейшее повреждение.

Затем определить:

можно исправить forward?

или:

нужен restore?

Если нужен restore, у нас уже существует заранее проверенный путь:

выбрать recovery point
↓
проверить manifest
↓
decrypt
↓
restore
↓
migrations if needed
↓
application readiness
↓
business smoke test
↓
switch traffic

А не:

Найдите где-нибудь backup и попробуем понять, как его открыть.

Отдельно нужно продумать point-in-time recovery

Ежедневный dump отвечает:

Как вернуться к состоянию последней копии?

Но иногда ошибка произошла:

17:43

а backup создан:

03:00.

Возврат на 03:00 означает потерю почти 15 часов нормальных изменений.

Для проектов с более строгими требованиями PostgreSQL поддерживает continuous archiving/WAL и Point-in-Time Recovery, позволяя строить восстановление к более конкретной точке времени. Это уже следующий уровень сложности.


PITR тоже нужно проверять

Очень легко настроить:

WAL archive

и считать:

Теперь у нас есть PITR.

Но пока никто не выполнял:

base backup
+
WAL replay
+
recovery target

это снова только предположение.

Принцип остаётся прежним:

любая recovery-функция должна хотя бы периодически проходить полный путь восстановления.


Мониторинг backup должен отвечать минимум на несколько вопросов

Последняя копия свежая?
Последний backup завершился?
Размер выглядит разумно?
Checksum PASS?
Последний restore-check PASS?
Offsite copy существует?

Так выглядит уже настоящий backup health.


Полезно различать пять статусов

Например:

CREATED

Файл создан.

VERIFIED

Checksum/шифрование проверены.

RESTORED

Прошёл реальный database restore.

APPLICATION_VERIFIED

Приложение прошло контрольные проверки.

OFFSITE_CONFIRMED

Независимая копия доступна.

Теперь зелёное:

Backup: OK

имеет значительно более конкретный смысл.


Что считать failure

Очевидно:

pg_dump error

Но также:

decrypt failed;
checksum mismatch;
restore failed;
critical table missing;
application readiness failed;
offsite upload failed.

В зависимости от backup policy не все failures имеют одинаковую критичность.

Но скрывать их нельзя.


Размер backup тоже стоит мониторить

Представим обычно backup:

2.4 GB

Сегодня:

18 MB.

Cron завершился с exit code 0.

Но резкое изменение размера выглядит подозрительно.

Возможно:

подключились не к той БД;
часть таблиц отсутствует;
изменилась конфигурация.

Аномалия размера — хороший дополнительный сигнал.


То же относится к неожиданно большому росту

Вчера:

2.4 GB

Сегодня:

17 GB.

Может быть:

нормальный импорт данных.

А может:

логическая ошибка;
дублирование;
бесконтрольный рост служебной таблицы.

Backup monitoring иногда первым замечает проблему основного storage.


Backup и retention нужно включать в расчёт диска

Очень легко написать:

храним 14 backup

Не посчитав:

14 × 20 GB
=
280 GB.

На VPS:

60 GB.

Очевидно, политика невозможна.

Поэтому retention должна соответствовать реальной инфраструктуре.

Либо история хранится offsite.


Rolling backup полезен там, где локальный диск ограничен

В одном из production-контуров мы использовали идею:

один локальный
known-good backup

вместо бесконечного каталога timestamped dumps.

Новый проверенный backup атомарно заменяет предыдущий.

Более длинная история хранится уже независимо и с ограниченной retention.

Получается:

VPS
→ быстрый локальный recovery point

Offsite
→ история disaster recovery

Это помогает не превращать системный диск в архив.


Но один local backup нельзя считать всей backup strategy

Если ошибка была обнаружена через два дня, текущая локальная копия уже может содержать проблему.

Именно поэтому offsite history остаётся важной.

Rolling local backup решает:

disk management.

Retention history:

historical recovery.

Это разные задачи.


Что мы считаем хорошим результатом restore-check

Не:

command exited 0

а, например:

Decrypt             PASS
Checksum            PASS
pg_restore          PASS
Schema              PASS
Critical queries    PASS
Application ready   PASS
Cleanup             PASS

И отдельно:

Duration:
18m 42s

Теперь у нас есть не вера.

Есть проверяемый результат.


Restore-check должен оставлять журнал

Если через месяц новый тест провалился, хочется знать:

что изменилось?

Например:

2026-08-20 PASS
2026-08-27 PASS
2026-09-03 PASS
2026-09-10 FAIL:
missing extension

Так можно связать regression с конкретным инфраструктурным изменением.


Тест recovery полезно сделать частью автоматических проверок

Например отдельная команда:

npm run backup:restore-check

или:

./restore-check.sh

которая воспроизводимо выполняет процедуру.

Это гораздо надёжнее инструкции:

Иногда вручную попробуйте восстановить.

Но автоматический тест не отменяет DR-документацию

Автоматизация сама может потеряться вместе с сервером.

Поэтому нужно иметь:

код restore-check;
человеческую инструкцию;
backup location;
key recovery procedure.

Лучше сразу предполагать:

Основной VPS больше недоступен вообще.

Очень хороший вопрос: что останется после полной потери сервера?

Мысленно удаляем:

VPS

целиком.

Что у нас осталось?

Хороший ответ:

source code/release
offsite encrypted backup
object storage backup
secrets/key recovery
deployment documentation
DNS access

Если хотя бы один незаменимый компонент находился только на уничтоженном VPS, disaster recovery неполный.


Backup — часть архитектуры, а не системного администрирования «на потом»

Когда проектируют новую CRM или SaaS, обычно обсуждают:

авторизацию;
роли;
API;
файлы;
платежи.

Backup часто появляется:

Потом перед запуском настроим cron.

Но способ восстановления зависит от архитектуры данных.

Если:

PostgreSQL
+
S3
+
application encryption

нужно заранее знать, как три компонента будут восстановлены согласованно.


Чем сложнее продукт, тем важнее recovery design

У лендинга:

Git
+
несколько форм

потеря сервера может быть относительно простой.

У SaaS:

10 000 пользователей;
платежи;
файлы;
история;
проекты.

самой ценной частью системы постепенно становится уже не исходный код.

А накопленные данные.

Код можно написать заново.

Пять лет истории клиентов — нет.


Поэтому production readiness без restore-check неполна

Перед запуском проекта можно проверить:

DNS ✓
HTTPS ✓
Login ✓
Payments ✓
Mobile ✓

Но если на вопрос:

А если завтра потеряем PostgreSQL?

ответ:

У нас вроде есть backup.

финальная production-проверка ещё не закончена.


Практический checklist резервного копирования

Перед тем как считать backup-контур готовым, мы бы проверили:

  • определено, какие данные являются невоспроизводимыми и действительно требуют backup;
  • известны RPO и приемлемая потеря данных;
  • известен желаемый RTO;
  • перед backup контролируется свободное место и эксплуатационный резерв;
  • новый dump создаётся во временный файл и не уничтожает предыдущую known-good копию до завершения проверки;
  • backup шифруется, если этого требует модель данных;
  • ключ восстановления хранится независимо и его реально можно получить;
  • для backup рассчитывается и проверяется checksum;
  • зашифрованная копия реально расшифровывается;
  • PostgreSQL dump периодически восстанавливается в отдельную БД;
  • после restore проверяются схема и критические данные;
  • при необходимости текущая версия приложения подключается к восстановленной БД и проходит readiness/smoke checks;
  • пользовательские файлы и Object Storage входят в recovery plan отдельно от PostgreSQL;
  • существует независимая offsite-копия;
  • настроена ограниченная retention старых копий;
  • мониторинг контролирует возраст последнего успешного backup;
  • мониторинг знает о failure backup/restore-check;
  • restore procedure документирована;
  • проверено восстановление без доступа к основной production-машине;
  • известен порядок восстановления secrets и encryption keys;
  • после значительных изменений инфраструктуры restore-check запускается снова;
  • перед рискованной database migration существует свежая проверенная recovery point.

Такой список намного строже обычного:

cron настроен ✓

Но именно поэтому он полезнее.


Когда backup можно назвать проверенным

Мы бы не использовали слово:

verified

только потому, что файл существует.

Более честная последовательность:

Created
↓
Integrity verified
↓
Decryptable
↓
Restorable
↓
Application usable
↓
Offsite protected

Чем дальше прошла копия, тем выше реальная уверенность.


Самая дорогая проверка backup — первая авария

Есть два способа узнать, работает ли restore.

Первый:

провести контролируемый тест.

Второй:

дождаться потери production.

Цена второго может быть на несколько порядков выше.

Поэтому restore drill — не избыточная DevOps-процедура.

Это способ заранее купить знание:

Да, вот эта процедура действительно возвращает наши данные.

Вместо вывода

Резервное копирование часто воспринимают как процесс создания файлов:

03:00
backup created

Но бизнесу нужен не файл.

Бизнесу нужна возможность продолжить работу после потери данных или инфраструктуры.

Поэтому зрелая backup-система строится в обратную сторону.

Сначала задаётся вопрос:

Как мы будем восстанавливаться?

И уже из ответа определяется:

что сохранять;
как часто;
куда;
как шифровать;
сколько версий;
как проверять.

Для production важна вся цепочка:

Production
    ↓
Backup
    ↓
Integrity
    ↓
Encryption verification
    ↓
Offsite
    ↓
Restore
    ↓
Application check
    ↓
Recovery

Если цепочка заканчивается на:

backup file exists

у нас пока есть резервная копия.

Но ещё нет доказанного восстановления.

Именно поэтому один из самых полезных production-тестов звучит не:

Создался ли сегодняшний backup?

А:

если прямо сейчас удалить основной сервер, сможем ли мы из этой копии вернуть рабочий продукт — и знаем ли мы, сколько это займёт?

Когда на этот вопрос можно ответить не предположением, а результатом реального restore-теста, резервное копирование наконец начинает выполнять свою настоящую задачу.

Есть похожая задача?

Опишите продукт, интеграции и ограничения. До разработки зафиксируем объём, риски и критерии приёмки.

Обсудить проект →