Архивация WAL PostgreSQL: настройка PITR без потерянных сегментов | AdminWiki

Архивация WAL PostgreSQL: настройка PITR без потерянных сегментов

11 сентября 2026 15 мин. чтения
Содержание статьи

Что такое архивация WAL и PITR в PostgreSQL

WAL (Write-Ahead Log, журнал предзаписи) хранит каждое изменение до того, как оно попадёт в файлы данных. Журнал разбит на сегменты по 16 МБ; с PostgreSQL 11 размер задаётся при initdb ключом --wal-segsize, но менять его без причины не стоит.

Архивация WAL означает копирование завершённых сегментов за пределы кластера. Копирует сам сервер: когда сегмент закрыт, PostgreSQL вызывает команду из параметра archive_command и ждёт код возврата. Ноль означает, что файл лежит в архиве. Любой другой код заставит сервер повторить вызов, а сегмент останется в pg_wal.

PITR (Point-In-Time Recovery) собирает рабочее состояние базы из двух частей: физического base backup, то есть снимка файлов данных, и непрерывной цепочки WAL после него. Нет цепочки, нет PITR: base backup без архива WAL откатывает ровно к моменту своего создания.

Практический эффект выглядит так: ошибочный DELETE выполнен в 14:32, вы восстанавливаетесь на 14:31 и теряете секунды. RPO измеряется интервалом между последней заархивированной транзакцией и моментом сбоя, а не расписанием ночного дампа.

Чем PITR отличается от pg_dump и репликации

pg_dump снимает логический снимок на момент запуска. Восстановиться из него можно только на этот момент, всё, что произошло позже, потеряно. Дамп базы в 500 ГБ идёт часами и сильно нагружает диск.

Физическая реплика защищает от отказа железа: узел умер, второй подхватил нагрузку. Логическая ошибка уезжает на реплику за миллисекунды. DELETE FROM orders без WHERE выполнится и на primary, и на standby, и откатить его на реплике нечем.

PITR закрывает обе дыры: даёт непрерывность в секундном масштабе и позволяет встать на момент до ошибочного запроса.

МетодОт чего защищаетRPOВосстановление на произвольный момент
pg_dump или pg_dumpallЛогические ошибки, перенос между версиямиЧасы или сутки, зависит от расписанияНет, только момент дампа
Физическая репликаОтказ сервера, диска, сетиСекунды при синхронной репликацииНет, реплика повторяет все ошибки primary
Base backup и архивация WALОтказ сервера и логические ошибкиСекунды, ограничено archive_timeoutДа, на время, LSN, XID или именованную точку

Что нужно подготовить до настройки

Первым делом проверьте версию. До PostgreSQL 12 восстановление настраивалось файлом recovery.conf, с 12 он заменён на recovery.signal, а параметры переехали в postgresql.conf. Инструкция для старой версии на новом сервере не сработает.

  • Доступ к postgresql.conf и возможность перезапустить кластер: archive_mode меняется только рестартом.
  • Хранилище под архив WAL. Считайте объём: 16 МБ на сегмент, при записи 50 ГБ в сутки архив растёт примерно на 50 ГБ в сутки плюс запас под пики.
  • Пользователь postgres должен иметь права записи в директорию архива и право запускать команду архивации.
  • Место под base backup: полный размер data directory плюс запас на pg_wal и на два бэкапа, если планируете ротацию.
  • Отдельный инстанс или виртуальная машина для тестовых восстановлений. Разворачивать проверку поверх продакшена никто не станет.

Настройка archive_command: как заставить PostgreSQL архивировать WAL

Архивацию включают три параметра. Минимальный рабочий набор выглядит так:

wal_level = replica
archive_mode = on
archive_command = 'test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f'
archive_timeout = 300

Плейсхолдеры %p и %f подставляет сервер. %p это полный путь к сегменту внутри pg_wal, например /var/lib/postgresql/16/main/pg_wal/0000000100000000000000A5, %f это только имя файла. Команда выполняется от пользователя postgres с его окружением, поэтому не рассчитывайте на PATH из своей сессии: прописывайте полные пути к бинарникам.

Проверка test ! -f нужна, чтобы не перезаписать сегмент, который уже лежит в архиве. PostgreSQL может вызвать archive_command повторно для того же файла, и запись поверх готовой копии иногда даёт битый файл.

Код возврата решает всё. Команда вернула ноль: сегмент считается заархивированным, в pg_wal/archive_status появляется метка .done. Вернула что-то другое: сервер оставит метку .ready, повторит вызов и не станет удалять сегмент из pg_wal. Опасность голого cp в том, что при отвалившемся и некорректно размонтированном NFS команда пишет в локальную директорию точки монтирования и возвращает ноль. Файл формально скопирован, в архиве его нет, а узнаете вы об этом при восстановлении.

ПараметрЗначениеЧто делает
wal_levelreplica или logicalОпределяет объём данных в WAL; для PITR достаточно replica
archive_modeonВключает архивацию; смена значения требует рестарта кластера
archive_commandКоманда с %p и %fКопирует сегмент в архив; вызывается для каждого закрытого сегмента
archive_timeout60 или 300 секундПринудительно закрывает сегмент по таймеру, ограничивает RPO на тихих базах
wal_keep_sizeПо нагрузке репликДержит сегменты в pg_wal для реплик; PITR не заменяет
max_wal_sizeПо объёму записиУправляет частотой checkpoint; косвенно влияет на объём pg_wal
restore_commandКоманда с %f и %pДостаёт сегмент из архива при восстановлении

Рабочие примеры archive_command

Локальная или сетевая директория через rsync. Флаг --ignore-existing защищает уже лежащий в архиве файл, а ненулевой код возврата rsync гарантирует, что PostgreSQL повторит попытку:

archive_command = 'test ! -f /mnt/wal_archive/%f && rsync -a --ignore-existing %p /mnt/wal_archive/%f'

Копирование с журналом для разбора инцидентов. Цепочка через && сохраняет код возврата cp, поэтому ошибка не спрячется за успешным echo:

archive_command = 'cp --preserve=timestamps %p /mnt/wal_archive/%f && echo "$(date -Is) archived %f" >> /var/log/postgresql/wal_archive.log'

Отправка в объектное хранилище через WAL-G. Ошибка сети даёт ненулевой код, сегмент остаётся в pg_wal и уезжает в архив при следующей попытке:

archive_command = 'wal-g wal-push %p --config /etc/wal-g/config.yaml'

Тот же принцип у pgBackRest, но архивацией управляет его собственная подкоманда:

archive_command = 'pgbackrest --stanza=main archive-push %p'

Проверка, что сегменты действительно архивируются

Не ждите следующего checkpoint, переключите сегмент вручную и посмотрите статистику:

SELECT pg_switch_wal();
SELECT archived_count, failed_count, last_archived_wal, last_failed_wal, last_failed_time
FROM pg_stat_archiver;

После переключения archived_count вырастает на единицу, а last_archived_wal показывает имя сегмента, уехавшего в архив. Сверьтесь с содержимым директории: ls -l /mnt/wal_archive | tail -5. Файл должен появиться в течение нескольких секунд. В PostgreSQL 9.6 и старше функция переключения называется pg_switch_xlog().

На базе с низкой активностью сегмент закрывается только по archive_timeout или по checkpoint. Если архив пуст, выполните CHECKPOINT и повторите pg_switch_wal().

Правки archive_mode требуют рестарта, изменение archive_command подхватывается через SELECT pg_reload_conf(). После этого обязательно проверьте лог PostgreSQL: ошибки прав и недоступного монтирования видны там сразу.

Base backup: снимаем основу для восстановления

pg_basebackup копирует файлы кластера по протоколу репликации и отдаёт каталог, с которого кластер стартует. Запускайте его только при уже работающей архивации WAL: если архивация выключена, цепочка после бэкапа не начнётся и PITR невозможен.

pg_basebackup -h 10.0.0.5 -U replicator -D /backup/base/2026-09-11 -Ft -z -Xs -P
КлючЧто делаетЗачем нужен
-D /pathКаталог для бэкапаЦелевая директория должна быть пустой
-FtФормат tarУдобно переносить и распаковывать на другом сервере
-zСжатие gzipЭкономит место, на выходе base.tar.gz
-XsПотоковая передача WALВ бэкап попадут сегменты, нужные для старта восстановления
-PПрогрессВидно, что процесс идёт, а не завис
-RЗапись restore_commandПолезно, когда бэкап сразу превращают в реплику

Для нестандартных схем есть ручной путь: SELECT pg_backup_start('manual_label', true); далее rsync или tar файлов данных, затем SELECT pg_backup_stop(); . В PostgreSQL 15 и новее функции называются pg_backup_start() и pg_backup_stop(), в 14 и старше это pg_start_backup() и pg_stop_backup(). Второй аргумент true включает быстрый режим, при котором checkpoint не ждёт завершения всех активных транзакций.

Бэкап связан с WAL через файл backup_label в корне снимка. В нём записаны стартовый адрес WAL (START WAL LOCATION), метка времени и timeline. PostgreSQL читает этот файл при восстановлении и понимает, с какого сегмента начинать проигрывание. Удалите backup_label, и кластер либо не стартует, либо начнёт с неверной точки.

Проверка целостности base backup

pg_basebackup создаёт файл backup_manifest с контрольными суммами. Проверка выполняется одной командой:

pg_verifybackup /backup/base/2026-09-11

Утилита сверяет каждый файл с манифестом и сообщает о расхождениях, лишних или недостающих файлах. Проверка не прошла: бэкап не используйте, снимайте заново. Частые причины сбоя это оборванное соединение, переполнение диска и ошибки сетевого хранилища.

Дополнительно убедитесь, что в бэкапе есть backup_label и, при -Xs, сегменты WAL, записанные во время снятия. Их отсутствие означает разрыв цепочки на самом старте, и такой снимок годится только для запуска кластера без отката на момент времени.

Восстановление PITR: restore_command и recovery target

Порядок действий на отдельном инстансе:

  1. Остановите кластер: pg_ctl stop -m fast или systemctl stop postgresql@16-main.
  2. Переименуйте текущий data directory, не удаляйте его. Если новый инстанс не поднимется, вернётесь к исходному состоянию.
  3. Распакуйте base backup в чистый каталог: mkdir -p /var/lib/postgresql/16/main и tar -xzf /backup/base/2026-09-11/base.tar.gz -C /var/lib/postgresql/16/main.
  4. Настройте restore_command в postgresql.conf. Здесь %f это имя нужного сегмента, %p это путь, куда его положить.
  5. Создайте файл recovery.signal в data directory. В PostgreSQL 11 и старше вместо него нужен recovery.conf с параметрами standby_mode = 'off' и restore_command.
  6. Задайте цель восстановления и поведение по её достижении.
  7. Запустите кластер и следите за логом.
restore_command = 'cp /mnt/wal_archive/%f %p'
recovery_target_time = '2026-09-11 14:31:00+03'
recovery_target_action = 'pause'

Значение pause удобно для разбора инцидента: инстанс доходит до цели и замирает в режиме recovery. Вы проверяете данные обычными SELECT, затем снимаете паузу командой SELECT pg_wal_replay_resume(); или переводите кластер на запись через SELECT pg_promote(); В PostgreSQL 11 и старше перевод выполняют через pg_ctl promote.

ПараметрЧто задаётКогда использовать
recovery_target_timeМетку времениОткат перед ошибочным запросом: recovery_target_time = '2026-09-11 14:31:00+03'
recovery_target_lsnАдрес в WALСамая точная цель, когда LSN известен из логов или из плана миграции
recovery_target_xidНомер транзакцииКогда XID проблемной транзакции виден в логах приложения или в pg_stat_activity
recovery_target_nameИменованную точкуПлановые операции: точку создаёт pg_create_restore_point('before_migration')
recovery_target_actionПоведение при достижении целиpause для проверки данных, promote для автоматической выдачи нагрузки, shutdown для остановки
recovery_target_inclusiveВключать ли целевую транзакциюon по умолчанию; off, когда нужно встать строго перед транзакцией

Как выбрать recovery target

Ошибочный DELETE в 14:32 это классический сценарий метки времени. Ставьте recovery_target_time на 14:31 и указывайте смещение часового пояса, иначе сервер применит TimeZone из postgresql.conf и промахнётся на несколько часов.

Когда известен номер проблемной транзакции, работает recovery_target_xid. Восстановление остановится на коммите этой транзакции, независимо от времени. Номер обычно есть в логах приложения с log_min_duration_statement или в истории pg_stat_activity.

Для плановых работ создавайте именованную точку заранее: SELECT pg_create_restore_point('before_migration'); Имя попадает в WAL и переживёт любые операции, пока архив не почистили. Восстановление задаётся как recovery_target_name = 'before_migration'.

Самая предсказуемая цель это recovery_target_lsn: точный байтовый адрес, который вы записали перед рискованной операцией. Восстановление на LSN исключает путаницу с часовыми поясами и округлением времени.

Не проскочить точку помогает recovery_target_inclusive. По умолчанию параметр включён, и транзакции, зафиксированные ровно в целевую метку, будут применены. Поставьте off, если нужно встать строго перед ними.

Если WAL до цели не хватает, инстанс завершит проигрывание раньше и напишет об этом в лог. Проверьте наличие сегментов: ls /mnt/wal_archive | tail -20. Сдвиньте цель на более ранний момент и перезапустите восстановление: оно продолжится с последней применённой позиции, а не с нуля.

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

SELECT pg_is_in_recovery();
SELECT pg_last_wal_replay_lsn(), pg_last_xact_replay_timestamp();

pg_is_in_recovery() возвращает false после promote, значит инстанс готов принимать запись. Пока идёт проигрывание, функция вернёт true, и для промежуточной проверки это нормально.

Дальше проверяйте данные: SELECT count(*) FROM orders WHERE created_at >= '2026-09-11 14:30'; Ожидаемый ноль подтверждает, что удаление откатили. Сверьте суммы и количество строк в ключевых таблицах с эталонными значениями, снятыми заранее. Без эталона проверить корректность восстановления нечем.

Отдельно посмотрите timeline. После PITR сервер создаёт новую линию, например с ID 2. Реплики, оставшиеся на старой, автоматически не переключатся, их нужно пересобирать с нового primary. Общий сценарий аварийного восстановления с репликами разобран в материале Восстановление PostgreSQL после сбоя в продакшне: полное руководство по WAL, PITR и автоматизации.

Контроль архива WAL: мониторинг, очистка, защита от разрастания

Архив без контроля превращается в источник проблем: он либо забивает диск, либо молча теряет сегменты. Обе ситуации ловятся по статистике pg_stat_archiver.

Столбец pg_stat_archiverЧто показываетПорог для алерта
archived_countЧисло успешно заархивированных сегментовРост должен совпадать с частотой pg_switch_wal
failed_countЧисло неудачных попыток архивацииБольше нуля: критичный алерт
last_archived_walИмя последнего сегмента в архивеОтставание от текущего WAL больше 10 сегментов: предупреждение
last_failed_walСегмент, на котором упала командаЛюбое значение: разбирать причину
last_failed_timeВремя последнего сбоя архивацииСвежее время при ненулевом failed_count: авария прямо сейчас
stats_resetКогда статистика обнуляласьНе порог, используется при разборе инцидента

Что мониторить и какие пороги ставить

Базовый запрос сравнивает последний архивный сегмент с текущим:

SELECT archived_count, failed_count, last_archived_wal,
       pg_walfile_name(pg_current_wal_lsn()) AS current_wal
FROM pg_stat_archiver;

Разница между этими двумя именами показывает отставание. Десять сегментов и больше означают, что архив не успевает или команда падает. Учтите, что отставание растёт при большой нагрузке на запись, поэтому порог подбирайте под свою базу.

Размер pg_wal проверяйте напрямую: du -sh /var/lib/postgresql/16/main/pg_wal. Устойчивый рост выше max_wal_size в два или три раза говорит о том, что сегменты копятся вместо отправки в архив, и диск скоро кончится.

archive_timeout закрывает открытый сегмент по таймеру. Без него база, пишущая 100 МБ в сутки, держит сегмент открытым около 160 минут, и RPO вырастает до этих же минут. Значение 60 или 300 секунд ограничивает потерю данных сверху, ценой роста числа сегментов и объёма архива.

wal_keep_size оставляет сегменты в pg_wal для реплик. К PITR он отношения не имеет: если сегмент не ушёл в архив, после перезаписи в pg_wal восстановить его нечем.

Очистка архива без потери нужных сегментов

Чистить архив вручную через rm нельзя: легко снести сегмент, без которого цепочка разорвётся. Штатный инструмент это pg_archivecleanup:

pg_archivecleanup /mnt/wal_archive 0000000100000000000000A5

Аргумент это имя самого старого сегмента, который ещё нужен. Утилита удалит всё, что младше него по порядку нумерации. Имя берётся из backup_label самого старого base backup, который вы планируете сохранить.

Правило без исключений: удаляйте WAL только старше самого старого нужного base backup. Если чистить по дате файла, есть шанс снести сегмент, нужный для восстановления, потому что сегменты пишутся заранее и время изменения файла не связано с содержимым.

Ретеншен держите с запасом: минимум два base backup. Когда последний окажется битым, у вас останется предыдущий вместе с полной цепочкой WAL. Автоматизируйте очистку через cron, но перед удалением скрипт должен убедиться, что старый base backup прошёл pg_verifybackup и восстановление из него действительно возможно.

Регулярная проверка восстановления: регламент и чек-лист

Бэкап, который никогда не разворачивали, рабочим считать нельзя. Раз в квартал это минимум для продакшена, при частых изменениях схемы проверку проводят раз в месяц.

Считайте два показателя. RPO показывает, сколько данных вы теряете между последней транзакцией и последним заархивированным WAL. RTO показывает, сколько времени проходит от команды восстановления до готовности принимать нагрузку. Для базы в 500 ГБ с архивом на сетевом хранилище RTO часто измеряется часами, и это число нужно знать заранее, а не во время аварии. Общую логику проверок и оформление отчёта разбирает статья Как проверить резервную копию: тестовое восстановление файлов, баз данных и настроек.

Чек-лист тестового восстановления

  1. Поднимите чистый инстанс PostgreSQL той же мажорной версии, что и продакшен.
  2. Распакуйте свежий base backup и проверьте его через pg_verifybackup.
  3. Настройте restore_command и создайте recovery.signal.
  4. Задайте recovery_target_action = 'promote', чтобы восстановление завершилось без ручного вмешательства.
  5. Дождитесь выхода из recovery и проверьте лог на строки об остановке проигрывания.
  6. Сверьте контрольные данные: количество строк, суммы, последние записи в журнальных таблицах.
  7. Зафиксируйте RTO и фактический RPO, сравните с целевыми значениями.
  8. Запишите результат в журнал проверок: дата, версия, длительность, выявленные проблемы.

Такой прогон вскрывает то, что не видно в мониторинге: неполную цепочку WAL, неверный restore_command, забытые tablespace, расхождение контрольных сумм.

Диагностика ошибок архивации и восстановления

Большинство сбоев PITR сводится к шести причинам. Таблица связывает сообщение в логе с действием.

СимптомПричинаРешение
could not archive WAL file ... No such file or directoryКоманда архивации не находит директорию: её не создали или отвалилось монтированиеПроверьте mount, создайте каталог и выдайте права: chown postgres:postgres /mnt/wal_archive
archive command failed with exit code 1archive_command вернул ненулевой код: сеть, права, переполненный дискЗапустите ту же команду руками от пользователя postgres и посмотрите вывод
requested WAL segment 0000000100000000000000A5 has already been removedЦепочка разорвана: архив почистили слишком рано или restore_command смотрит в другую директориюВосстанавливайтесь из более старого base backup, проверьте полноту архива
recovery ended before configured recovery targetНе хватает WAL до заданной целиСдвиньте recovery_target_time назад и проверьте наличие сегментов в архиве
permission denied для pg_wal или директории архиваПроцесс запущен не от postgres или права сменились после копированияВерните владельца: chown -R postgres:postgres, права 0700 на data directory
FATAL: could not open file pg_wal/0000000100000000000000A5Сегмента нет в архиве или restore_command подставляет не тот путьПроверьте шаблоны %f и %p, вручную достаньте сегмент из архива

Как читать логи PostgreSQL при восстановлении

Ключевых строк немного, и по ним восстанавливается вся картина:

  • starting point-in-time recovery to 2026-09-11 14:31:00+03: цель принята, проигрывание началось.
  • restored log file "0000000100000000000000A5": сегмент найден и применён; отсутствие таких строк указывает на проблему с restore_command.
  • recovery stopping before commit of transaction 1042, time 2026-09-11 14:32:07+03: инстанс встал перед проблемной транзакцией, это ожидаемый результат.
  • recovery stopping after commit of transaction 1042: цель включила транзакцию, иногда это не то, чего вы хотели.
  • selected new timeline ID: 2: создана новая линия, старая осталась в прошлом.
  • archive recovery complete: проигрывание закончено, инстанс готов к promote.

Стратегия хранения WAL: локально, NFS, S3, pgBackRest и WAL-G

Архив WAL критичен так же, как сами данные: его потеря обнуляет возможность PITR. Выбор места хранения определяет, от каких аварий вы защищены.

ВариантПлюсыМинусы
Локальная директория на том же сервереБыстро, просто, нет сетиОтказ сервера уносит и данные, и архив
Отдельный диск или NASАрхив переживает отказ основного томаОдин физический отказ диска убивает PITR; нужен RAID и мониторинг
NFS или SMB-шараСетевое хранилище с общим доступомРазрыв сети ломает archive_command, нужны надёжный сервер и таймауты
S3 или объектное хранилищеЗащита от отказа площадки, версионированиеНужен WAL-G или pgBackRest, на каждый сегмент добавляется задержка

Рабочая схема для продакшена: архив пишется в две точки, локально для быстрого восстановления и в объектное хранилище для защиты от отказа площадки. Принципы 3-2-1 и 4-3-2 разобраны в статье Резервное копирование в стратегии отказоустойчивости: лучшие практики 2026. Для тестовых восстановлений удобно держать отдельный инстанс в облаке: его проще поднять и погасить после проверки, чем искать свободное железо в стойке. Подойдет, например, Timeweb Cloud с виртуальными серверами и объектным хранилищем.

Когда переходить на pgBackRest или WAL-G

archive_command с rsync закрывает задачу для одного инстанса с небольшим архивом. Признаки, что пора брать специализированный инструмент: несколько кластеров, нужен автоматический ретеншен, шифрование, дедупликация, параллельная архивация и единая точка управления бэкапами.

pgBackRest и WAL-G работают поверх того же механизма, подставляясь в archive_command, и дают сверх базовой настройки проверку бэкапов, инкрементальные копии, сжатие и отправку сразу в несколько репозиториев. Пошаговая настройка pgBackRest с полными и дифференциальными бэкапами и восстановлением до точки времени описана в отдельном руководстве: Резервное копирование PostgreSQL с pgBackRest: полные и дифференциальные бэкапы, WAL и PITR.

Что бы вы ни выбрали, порядок работ одинаков: включить архивацию WAL, проверить pg_stat_archiver, снять base backup, протестировать восстановление на отдельном инстансе и только потом считать систему защищённой.

Поделиться:
Сохранить гайд? В закладки браузера