Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Восстановление Postgres: PITR и WAL

Резервное восстановление PostgreSQL: бэкапы, WAL и PITR на практике

Как восстанавливать Postgres на нужную точку времени: pg_dump против pg_basebackup, WAL-архивирование, PITR через recovery_target_time и регулярные учебные восстановления.

Прямой ответ

Бэкап Postgres бывает двух принципиально разных видов, и путать их дорого. Логический (pg_dump) — дамп SQL-ом: удобен для переноса/выборочного восстановления таблиц, но медленен на больших базах и восстанавливает только на момент дампа. Физический (pg_basebackup + непрерывный WAL-архив) — копия каталога данных + журнал транзакций: даёт PITR (point-in-time recovery) — откат на любую секунду между бэкапами: «восстановить базу на 14:32, за минуту до злополучного DELETE». Рабочий минимум продакшена: базовый бэкап раз в сутки + WAL-архивирование на отдельное хранилище (archive_command на S3/файловый — 3-2-1), готовый инструмент вместо самописи — pgBackRest (инкрементальные бэкапы, параллельность, retention из коробки). И главное правило, отличающее бэкап от иллюзии: восстановление тренируется — раз в месяц база поднимается из бэкапа на тестовом хосте и проверяется (нетренированный PITR в момент инцидента обнаруживает, что WAL-архив обрывался три недели и никто не заметил). RPO/RTO задают конфигурацию: RPO «максимум 5 минут данных» = интервал принудительной ротации WAL, RTO «час» = скорость восстановления, которую надо замерить заранее, а не в день аварии.

WAL: журнал, который делает PITR возможным

WAL (write-ahead log) — журнал изменений: каждая транзакция сначала пишется в него (отсюда crash-safety — после падения база восстанавливает незавершённое по журналу). Для бэкапов WAL — поток всех изменений с момента базовой копии: базовый бэкап + непрерывная цепочка WAL = любое состояние между ними. Настройка архивирования: wal_level = replica, archive_mode = on, archive_command = 'pgbackrest arch-push %p' (или rsync/aws s3 cp — команда обязана быть идемпотентной и возвращать ненулевой код при провале: Postgres повторит отправку, но переполнение pg_wal при недоступном хранилище остановит базу — мониторинг архивирования обязателен); archive_timeout = 300 — принудительная ротация сегмента раз в 5 минут (иначе на тихой базе WAL не переключится и RPO растянется на часы). Целостность цепочки — критична: пропущенный сегмент WAL рвёт PITR (восстановление возможно только до разрыва) — алерты на лаг архивирования (pg_stat_archiver: failed_count, last_archived_time).

pgBackRest: рабочий инструмент вместо скриптов

Самописный «pg_basebackup в cron» работает до первого инцидента; pgBackRest закрывает производственные требования из коробки: инкрементальные/дифференциальные бэкапы (полный раз в неделю + дельты — место и окно бэкапа в разы меньше), параллельное сжатие (восстановление большой базы — часы не из-за копирования, а из-за однопоточности), retention-политики (repo1-retention-full=2 — автоматическая чистка), S3/GCS/azure из коробки, stanza — одна конфигурация на несколько баз/серверов. Конфиг минимален: stanza (путь к базе, тип), repo (хранилище + шифрование), расписание — systemd-timer: pgbackrest --stanza=main --type=full backup еженедельно + diff ежедневно; pgbackrest check — встроенная проверка целостности репозитория (включать в мониторинг). Альтернативы того же класса — Barman, WAL-G (популярнее в русскоязычной инфраструктуре, S3-first); принципиально все они — обёртка над basebackup+WAL, выбор — по вкусу эксплуатации. Чего делать не стоит: хранить бэкапы только на том же хосте/диске (смерть диска = смерть бэкапа — правило 3-2-1), и «бэкапить» снапшотами диска работающей базы без заморозки (несогласованный снапшот — лотерея при восстановлении).

PITR: механика восстановления на точку

Процедура (pgBackRest): на целевой хост — pgbackrest --stanza=main --type=time --target="2026-10-21 14:32:00" --target-action=promote restore — инструмент сам раскладывает последний полный+инкременты, подставляет настройки recovery, стартует Postgres, который накатывает WAL до целевого времени и останавливается, становясь первичным. Ключевые параметры: --type=time|lsn|xid|name (точка = время / позиция журнала / конкретная транзакция / именованная точка — pg_create_restore_point('before_migration') перед рискованной миграцией — alembic-практика: откат на «до миграции» точный, без подбора времени); длительность наката WAL ≈ объёму изменений с бэкапа (час пиковой записи = минуты восстановления — RTO измеряется заранее); после promote база разведена во времени с приложениями (последующие транзакции потеряны по определению PITR — бизнес решает, компенсировать ли из логов/очередей). Частые грабли: восстановление на том же хосте поверх живой базы (остановить, очистить PGDATA — pgBackRest требует пустой каталог), часовой пояс в --target (серверное время БД vs локальное — указывать явно), и «восстановили, а таблицы нет» — PITR не выборочен: одна таблица из PITR-копии вынимается поднятием копии целиком и pg_dump -t из неё.

Практический сценарий: DROP TABLE в 14:33

Инцидент: в 14:33 миграция уронила таблицу orders в проде; заметили в 14:40, записи после 14:33 в приложении шли (7 минут данных после точки). Действия: (1) остановить запись в приложение (режим обслуживания — новые данные после восстановления придётся восстанавливать из логов приложения/очередей); (2) на резервном хосте — pgbackrest --type=time --target="2026-10-21 14:32:00" restore из последнего полного (вчерашнего) + WAL — накат занимает 18 минут (замерено заранее — RTO в норме); (3) проверка копии: таблица на месте, счётчики сходятся с мониторингом на 14:32; (4) промоут копии, переключение приложения (DNS/connection string), снятие обслуживания — сервис жив в 15:10; (5) постмортем: 7 минут заказов восстановлены по application-логам (создание заказа логируется с параметрами — структурированные логи); выводы: pg_create_restore_point перед каждой миграцией (откат по имени, не по времени), алерт на failed_count архивации (в этот раз архив работал — повезло, проверяемо стало регулярным pgbackrest check). Сценарий занял 40 минут потому, что восстановление тренировалось ежемесячно: нетренированный вариант того же инцидента — это часы раскопок документации в момент аварии.

FAQ

Достаточно ли одного pg_dump? Для базы до гигабайта с RPO «сутки» — практически да (просто и восстанавливается); PITR невозможен (только момент дампа), восстановление большое базы — медленное, выборочное восстановление — плюс. Продакшен с транзакциями — физический бэкап+WAL; pg_dump — дополнение для миграций/экспортов, не замена.

Как проверить, что бэкап рабочий, дёшево? Автоматический прогон: timer раз в месяц — restore на временный контейнер/VPS (docker run postgres + restore из репозитория), смоук-запросы к данным (таблицы, счётчики), удаление; результат — метрика «last successful recovery» с алертом на протухание (метрики).

Сколько хранить WAL? По RPO/политике: минимум — до старейшего полного бэкапа в retention (иначе цепочка рвётся); типично retention-full=2 недели + WAL за тот же срок; объём WAL ≈ объёму записи в базу (гиганайты/сутки на активной) — хранилище дешёвое, жадность здесь неуместна.

Репликация (standby) заменяет бэкап? Нет: реплика мгновенно воспроизводит и DROP TABLE (ошибка оператора реплицируется так же верно, как данные); standby — про доступность (failover), бэкап — про восстановление от логических повреждений. Нужны оба, плюс PITR.

Источники: документация PostgreSQL (continuous archiving), pgBackRest, man pg_dump — ссылки в шапке.