
Статья подготовлена вместе с экспертами
Дарья Шибкова, менеджер продукта

Бэкапы PostgreSQL есть почти в каждой компании — но между «у нас настроено резервное копирование» и «мы точно восстановимся на нужную секунду» лежит пропасть, которую обычно обнаруживают в худший момент. Дамп pg_dump или ночной снапшот диска откатывают базу максимум к моменту, когда они были сняты, — а если инженер уронил таблицу в середине рабочего дня, между «вчера ночью» и «сейчас» может быть восемь часов чужих заказов, платежей и правок, которые никто не готов терять.
Point-in-Time Recovery (PITR) решает именно эту задачу: base backup плюс непрерывный архив WAL позволяют воспроизвести состояние базы на любую секунду между двумя бэкапами, а не только на момент снапшота. Проблема в том, что PITR — это не «включил и забыл»: сломанный archive_command тихо копит недоархивированные WAL, неправильная retention-политика удаляет как раз тот сегмент, который нужен для восстановления, а timeline и recovery_target путают даже опытных инженеров в момент инцидента, когда цена ошибки максимальна. В этой статье разберём механику WAL-архива, timeline, retention и восстановление в новый кластер, — а в конце соберём автоматизированную проверку точки восстановления и измерение RPO, чтобы доказывать способность восстановиться, а не предполагать её.

Дарья Шибкова, менеджер продукта

Дмитрий Ковальчук, менеджер продукта
PostgreSQL непрерывно ведёт write-ahead log (WAL) — журнал всех изменений в файлах данных, изначально нужный для восстановления после сбоя. PITR использует этот же журнал иначе: если сохранять WAL-сегменты по мере их заполнения и держать один базовый бэкап, можно «доиграть» журнал до любой точки между бэкапом и текущим моментом, получив консистентное состояние базы на нужную секунду.
Восстановление через PITR всегда собирается из двух независимых частей: базового бэкапа файлов кластера (base backup) и непрерывной последовательности WAL-сегментов, которая начинается не позже старта этого бэкапа. По отдельности эти компоненты не имеют ценности: копия данных без WAL-файлов восстановит кластер только на фиксированный момент времени, а WAL-сегменты невозможно применить без исходных файлов базы данных.
archive_command — это просто shell-команда, которую PostgreSQL считает успешной по нулевому коду возврата, и здесь чаще всего проседает вся цепочка PITR.
Восстановление базы данных на момент времени в прошлом (PITR) создаёт развилку: PostgreSQL присваивает новую timeline каждой такой развилке, чтобы WAL, сгенерированный после восстановления, не перезаписал сегменты из истории, к которой ещё можно захотеть вернуться. Timeline ID зашит в первых восьми символах имени WAL-файла, а при каждом переключении в архив попадает небольшой .history-файл с описанием, откуда эта ветка была создана.
Это значит, что можно пробовать несколько точек восстановления методом проб и ошибок, не теряя возможность вернуться к любой из них позже, — каждая попытка живёт в своей ветке timeline, а не затирает предыдущую.
| recovery_target_timeline | Что означает |
| current | Остаться на той же timeline, что была активна на момент base backup |
| latest | Восстановиться до последней доступной timeline в архиве — обычно то, что нужно на standby-сервере |
| <числовое ID> | Восстановиться на конкретную ранее созданную ветку — например, вернуться к одной из промежуточных попыток восстановления |
Retention в PITR — это компромисс между стоимостью хранения и глубиной восстановления, но у него есть жёсткое ограничение снизу: нельзя хранить WAL меньше, чем интервал между базовыми бэкапами, иначе цепочка восстановления разрывается физически, а не по регламенту. Чем реже снимается base backup, тем больше WAL нужно хранить и тем дольше длится реплей при восстановлении — это не бесплатная экономия места.
Начиная с PostgreSQL 17, pg_basebackup поддерживает инкрементальные бэкапы (--incremental) поверх WAL summary, а pg_combinebackup собирает из цепочки полного и инкрементальных бэкапов синтетический полный бэкап при восстановлении — это снижает объём хранения по сравнению с ежедневными полными бэкапами при сопоставимой скорости восстановления.
| Объект | Что определяет retention | Типичная ошибка |
| WAL-сегменты | Интервал между base backup + запас на случай задержки восстановления | Удаляют WAL старше N дней по расписанию, не проверив, не нужен ли этот сегмент текущему base backup |
| Base backup | Целевой RPO/RTO и допустимое время реплея при восстановлении | Один старый full backup хранится «на всякий случай», а WAL перед ним давно вычищен — фактически восстановиться некуда |
| .history-файлы timeline | Хранятся бессрочно, они маленькие | Иногда удаляются вместе со «старыми» WAL, что рвёт способность восстановиться в прошлые timeline |
| Инкрементальные бэкапы (PG17+) | Зависимость от всех бэкапов в цепочке до последнего full | Удаляют «старый» full backup, не заметив, что от него зависят более новые incremental |
Момент восстановления задаётся одним из нескольких взаимоисключающих параметров: recovery_target_time — конкретная временная метка, recovery_target_lsn — позиция в WAL, recovery_target_xid — конкретная транзакция, recovery_target_name — именованная точка, заранее созданная функцией pg_create_restore_point(), или recovery_target=immediate — восстановиться ровно до момента консистентности base backup. recovery_target_inclusive определяет, включается ли сама целевая транзакция или момент в восстановление.
| Параметр | Когда использовать |
| recovery_target_time | Известна примерная временная метка инцидента («уронили таблицу около 14:32») |
| recovery_target_lsn | Известна точная позиция в WAL, например из логов репликации |
| recovery_target_xid | Известен ID конкретной транзакции, которую нужно исключить или включить последней |
| recovery_target_name | Заранее создана именованная точка pg_create_restore_point() перед рискованной операцией (миграция, релиз) |
| recovery_target_action | Что делать по достижении цели: pause — оставить в режиме только чтения для проверки, promote — сразу открыть на запись, shutdown — остановить сервер |
Восстанавливать поверх боевого каталога данных — плохая идея: если точка восстановления выбрана неверно, откатить этот шаг уже нельзя. Практика — разворачивать PITR в отдельный, новый каталог/кластер, проверять данные и только потом переключать трафик.
| Риск | Механизм PITR | Бизнес-ценность |
| Человеческая ошибка (DROP TABLE, UPDATE без WHERE) в рабочее время | recovery_target_time на секунду до ошибочной транзакции, восстановление в отдельный кластер | Возврат к состоянию до инцидента без потери всех изменений за день |
| Атака шифровальщика или компрометация учётки | Восстановление на точку до заражения в изолированной сети, при необходимости через recovery_target_lsn по логам SIEM | Чистое восстановление без переноса вредоносных изменений вместе с бэкапом |
| Неудачная миграция или релиз необратимо меняет данные | Именованная точка pg_create_restore_point() перед миграцией, recovery_target_name при откате | Точный откат к состоянию «прямо перед релизом» без потерь на границе бэкапа |
| Никто не проверял, что бэкап реально восстанавливается | Автоматизированный restore-drill по расписанию с проверкой контрольных данных | RPO и RTO подтверждены тестом, а не предположением |
| Retention удалил WAL, нужный для восстановления | Явный расчёт нижней границы retention от интервала base backup + мониторинг зависимостей инкрементальных бэкапов | Восстановление остаётся физически возможным, а не только «по регламенту» |
| Критерий | pg_dump (логический бэкап) | Файловый/снапшот-бэкап без WAL | PITR через непрерывный WAL-архив |
| Точность точки восстановления | Только момент снятия дампа | Только момент снятия снапшота | Любая секунда между бэкапами |
| Восстановление отдельных объектов | Да, можно восстановить одну таблицу | Нет, только весь кластер | Не напрямую, но можно поднять отдельный кластер и выгрузить нужное |
| Время восстановления большой базы | Медленное — реплей SQL/COPY | Быстрое — восстановление файлов | Быстрое восстановление файлов + время реплея WAL |
| Нагрузка на прод при снятии | Заметная на больших базах | Минимальная (снапшот ФС/диска) | Минимальная, WAL архивируется непрерывно |
| Требования к хранилищу | Компактный (логический формат) | Полный размер кластера на каждый снапшот | Base backup + непрерывный поток WAL |
| Точность восстановления при инциденте в моменте | Ограничена частотой снятия | Ограничена частотой снятия | Секундная точность внутри окна retention |
| Этап | Содержание | Ориентировочная длительность |
| 1. Discovery | Определить целевые RPO/RTO для каждого класса баз, посчитать объём WAL-трафика и текущий подход к бэкапам | 1–2 недели |
| 2. Настройка архивирования | Включить wal_level, archive_mode, archive_command/library, настроить мониторинг pg_stat_archiver | 1 неделя |
| 3. Base backup и retention | Настроить расписание base backup (pg_basebackup или инкрементальные), retention с учётом зависимостей цепочки | 1–2 недели |
| 4. Тестовый стенд | Прогнать восстановление в новый кластер на разные recovery_target, замерить фактическое время реплея | 1–2 недели |
| 5. Автоматизация проверки | Настроить регулярный restore-drill с проверкой контрольных данных и измерением RPO | 2–3 недели |
| 6. Промышленная эксплуатация | Runbook для инцидента, регулярный пересмотр retention и RPO/RTO при росте нагрузки | Постоянно |
| Возражение | Как отвечать |
| «У нас есть снапшоты диска каждую ночь, зачем ещё и WAL-архив» | Снапшот восстанавливает только на момент своего снятия — если инцидент случился днём, потери исчисляются часами данных. WAL-архив закрывает именно этот разрыв. |
| «Боимся, что настройка archive_command сама станет источником сбоя» | archive_command — отдельный, легко мониторимый процесс (pg_stat_archiver), а не изменение логики самой базы. Предложить пилот на некритичной базе. |
| «PITR это же долго — легче поднять реплику» | Реплика защищает от отказа железа, но не от логической ошибки — она реплицирует DROP TABLE так же честно, как и любую другую транзакцию. PITR и реплика решают разные классы риска и обычно нужны вместе. |
| «Мы не знаем точную секунду инцидента» | recovery_target_action=pause и подбор recovery_target_time позволяют пробовать точки методом проб — каждая попытка живёт в своей timeline и не мешает следующей. |
| «Кто вообще проверяет, что бэкап живой» | Показать автоматизированный restore-drill: расписание, контрольные данные, алерт при неудаче или превышении целевого RPO. |
| «Сколько это будет стоить в хранилище» | Посчитать вместе объём WAL-трафика и целевую глубину retention. Инкрементальные бэкапы (PG17+) снижают объём по сравнению с ежедневными полными. |
| Термин | Определение |
| WAL (Write-Ahead Log) | Журнал всех изменений в файлах данных PostgreSQL, из которого строится и crash recovery, и PITR. |
| Base backup | Копия файлов кластера на момент старта резервного копирования, отправная точка для реплея WAL. |
| archive_command / archive_library | Команда или модуль, которым PostgreSQL копирует заполненный WAL-сегмент в архив. |
| Timeline | Ветка истории WAL, создаваемая при каждом завершении восстановления, чтобы не перезаписывать предыдущую историю. |
| recovery_target_time / lsn / xid / name | Параметры, задающие момент, до которого нужно доиграть WAL при восстановлении. |
| recovery_target_action | Что делать по достижении цели восстановления: pause, promote или shutdown. |
| recovery.signal / standby.signal | Файлы-маркеры в каталоге данных, включающие режим восстановления при старте сервера. |
| pg_create_restore_point() | Функция, создающая именованную точку в WAL для последующего recovery_target_name. |
| RPO (Recovery Point Objective) | Максимально допустимый объём потерянных данных, измеряется временем между инцидентом и последней успешно сохранённой транзакцией. |
| RTO (Recovery Time Objective) | Максимально допустимое время простоя до восстановления работоспособности. |
| pg_stat_archiver | Системное представление со статистикой архивирования WAL: время последнего успеха, число неудач. |
| Инкрементальный бэкап (PG17+) | Бэкап, содержащий только блоки, изменившиеся с предыдущего бэкапа; восстанавливается вместе со всей цепочкой через pg_combinebackup. |
| Restore-drill | Регулярная автоматизированная проверка: реальное восстановление бэкапа и WAL в отдельный кластер с проверкой контрольных данных. |
| pgBackRest / WAL-G / Barman | Популярные инструменты управления WAL-архивом, бэкапами и retention поверх встроенного механизма PostgreSQL. |
| Вопрос для самопроверки | Комментарий |
| Знаем ли мы целевые RPO и RTO для каждого класса баз? | Без цифр невозможно спроектировать retention и расписание base backup. |
| Мониторим ли мы pg_stat_archiver на предмет отставания и ошибок архивирования? | Тихо сломанный archive_command обнаруживается обычно только в момент восстановления. |
| Проверяли ли мы восстановление в отдельный кластер за последние несколько недель? | Бэкап, который никогда не восстанавливали, — предположение, а не гарантия. |
| Учитывает ли retention зависимость инкрементальных бэкапов от полного? | Удаление «старого» full backup может обесценить все зависящие от него incremental. |
| Есть ли runbook с точными шагами восстановления и recovery_target на инцидент? | В момент реального инцидента не время читать документацию с нуля. |
| Создаём ли именованные точки восстановления перед рискованными миграциями? | pg_create_restore_point() перед релизом упрощает точный откат. |
| Измеряем ли мы фактический RPO по данным архива, а не предполагаем его? | Целевой и фактический RPO могут отличаться, если архивирование отстаёт под нагрузкой. |
Рассмотрим несколько задач, с которыми регулярно сталкиваются команды, эксплуатирующие PostgreSQL в проде, и разберём, как PITR решает их с точки зрения точности восстановления, изоляции риска и доказуемости результата.
Скомпрометированная учётная запись используется для шифрования или массового удаления данных. Момент проникновения и момент видимого ущерба могут отличаться на часы или дни, а бэкапы, снятые уже после компрометации, могут содержать вредоносные изменения.
PITR позволяет восстановиться на точку до предполагаемого момента компрометации, а не просто на «последний бэкап». Восстановление выполняется в изолированной от прод-сети среде — как минимум до тех пор, пока не подтверждено, что источник компрометации закрыт.
Восстановиться на заведомо чистое состояние до компрометации, а не на первый доступный бэкап, и иметь возможность подтвердить это документально.
Бэкапы настроены и снимаются по расписанию годами, но реальное восстановление никто не проверял — метрики говорят, что архивирование работает, но нет доказательства, что из этих файлов можно действительно поднять рабочий кластер и получить консистентные данные.
Превратить способность восстановиться из предположения в регулярно подтверждаемый и измеряемый факт.
Команде аналитики или QA нужен консистентный слепок прод-базы на определённый момент времени — например, состояние на начало квартала для отчётности или состояние прямо перед конкретным релизом для воспроизведения бага. Прямое клонирование прод-кластера нагружает боевую базу и не даёт зафиксировать конкретный исторический момент.
Дать аналитике и QA воспроизводимые исторические слепки данных без нагрузки на прод и заодно регулярно проверять сам механизм PITR.
Способность восстановиться доказывается только реальным восстановлением, а не наличием файлов в архиве. Ниже — минимальный стенд, который можно запускать по расписанию (cron/CI) и получать измеренный RPO, а не предполагаемый.
| Шаг стенда | Команда или механизм | Что проверяем |
| Base backup | pg_basebackup -D /backup -Ft -X stream -c fast | Бэкап снят и содержит backup_label |
| Контрольная нагрузка | Периодическая вставка строк с меткой времени (pgbench или свой скрипт) | Есть однозначный маркер «последняя легитимная запись» |
| Именованная точка | SELECT pg_create_restore_point('before_incident') | Точка создана и видна в WAL до эмуляции сбоя |
| Эмуляция сбоя | DROP TABLE / DELETE на контрольной таблице после точки | Инцидент воспроизведён детерминированно |
| Восстановление | restore_command + recovery_target_name='before_incident' + recovery.signal | Новый кластер стартует и завершает реплей без ошибок |
| Проверка данных | SELECT на контрольной таблице в восстановленном кластере | Данные соответствуют состоянию до эмулированного сбоя |
| Измерение RPO | Метка времени последней восстановленной записи минус время инцидента | Измеренный RPO укладывается в целевой SLA |
Целевой RPO — это то, что заявлено в SLA («не более 5 минут потерь»). Фактический RPO конкретного инцидента или прогона restore-drill — это разница между временем инцидента и временной меткой последней транзакции, которая гарантированно попала в архив к этому моменту; её можно оценить по времени последнего успешного архивирования из pg_stat_archiver, ближайшему к моменту инцидента. Если фактический RPO стабильно выше целевого, проблема почти всегда в отставании archive_command под нагрузкой, а не в самой архитектуре PITR.
Возможность восстановить PostgreSQL на нужную секунду не появляется автоматически вместе с включённым архивированием WAL — она складывается из работающего archive_command, retention, который не разрывает цепочку восстановления, понятной модели timeline и recovery_target, а главное — из процесса, который восстановление действительно проверяет.
PITR закрывает класс рисков, которые не решает ни реплика (она честно реплицирует и человеческую ошибку), ни ежедневный снапшот (он восстанавливает только на момент своего снятия): точечные ошибки, компрометацию и любой инцидент, где нужна секундная точность, а не ближайшая полночь.
При этом бэкап, который никогда не восстанавливали, — это предположение, а не гарантия. Автоматизированный restore-drill с измерением фактического RPO переводит вопрос «а мы точно восстановимся» из области веры в область регулярно подтверждаемого факта.
Именно поэтому PITR стоит проектировать вместе с процессом его проверки с самого начала — retention, recovery_target и runbook восстановления бесполезны, если единственный раз, когда их проверяют, — это реальный инцидент.

В Cloud Databases резервное копирование и PITR настраиваются через интерфейс. При сбое выберите нужную точку восстановления и разверните базу данных на новом инстансе — без ручной настройки процесса.



Наши специалисты свяжутся с вами в ближайшее время и ответят на все вопросы.

Будем держать в курсе новостей и облачных трендов




