Резервное копирование PostgreSQL с pgBackRest: полные и дифференциальные бэкапы, WAL и PITR | AdminWiki

Резервное копирование PostgreSQL с pgBackRest: полные и дифференциальные бэкапы, WAL и PITR

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

pgBackRest закрывает три задачи, которые штатные утилиты PostgreSQL решают только по отдельности. pg_dump отдаёт логическую копию: PITR с ней невозможен, инкрементальных режимов нет. pg_basebackup копирует кластер целиком: каждый бэкап полный, retention и параллельное сжатие отсутствуют. Когда сервис требует RPO в 5 минут и RTO в 30 минут, такая связка разваливается на первом же реальном сбое.

pgBackRest даёт полные, дифференциальные и инкрементальные копии, непрерывный архив WAL, проверку контрольных сумм и восстановление на заданную секунду. Дальше в статье: установка и рабочий конфиг, стратегия бэкапов, WAL-архив, PITR на отдельном инстансе, регламент проверок, разбор типовых ошибок и runbook отката. Целевые версии: PostgreSQL 14-17 и pgBackRest 2.5x.

Зачем pgBackRest в production и что он даёт по сравнению с pg_dump и pg_basebackup

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

pgBackRest добавляет к физическому бэкапу четыре вещи, которых не хватает в проде: дифференциальные и инкрементальные режимы, непрерывная архивация WAL, retention policy и verify для проверки контрольных сумм. Восстановление идёт на конкретный timestamp, а не только на момент бэкапа. Для базы 500 ГБ это разница между «потеряли 6 часов транзакций» и «потеряли 30 секунд».

Статья рассчитана на администратора, который настраивает бэкапы production-кластера PostgreSQL и хочет получить рабочую конфигурацию, а не пересказ официального мануала. Все команды проверены на PostgreSQL 14-17, пути указаны для Debian/Ubuntu, для RHEL-совместимых систем отличается только расположение бинарников.

Ключевые понятия: stanza, repository, WAL-архив

Stanza это логическая единица в конфигурации pgBackRest, привязанная к одному кластеру PostgreSQL. Имя stanza (в примерах ниже main) попадает в каждую команду и в archive_command, поэтому переименование задним числом ломает архив WAL.

Repository это хранилище бэкапов. Варианты: локальный каталог, отдельный сервер по SSH, объектное хранилище S3, Azure Blob, Google Cloud Storage. Конфигурация первого репозитория задаётся префиксом repo1-, второго через repo2-. Второй репозиторий удобен для копии в другом регионе или на другом носителе.

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

Требования к окружению и предварительная проверка

Перед настройкой соберите факты о сервере. Пять команд ниже отвечают на вопросы о версиях, правах и свободном месте.

pgbackrest version
psql -V
id postgres
df -h /var/lib/pgbackrest
sudo -u postgres psql -c "show wal_level; show archive_mode;"

Что проверить в выводе:

  • Версия pgBackRest на всех хостах, участвующих в бэкапе и восстановлении, совпадает. Расхождение минорных версий даёт ошибку протокола при обращении к репозиторию.
  • PostgreSQL и pgBackRest собраны под одну мажорную версию СУБД. На хосте, куда пойдёт restore, мажорная версия PostgreSQL должна совпадать с версией кластера в бэкапе.
  • wal_level = replica или logical. При minimal физический архив WAL и PITR не работают.
  • Каталог репозитория доступен на запись пользователю postgres. Локальный репозиторий вне /var/lib/pgbackrest требует явных прав, иначе archive-push падает с permission denied.
  • Свободное место: минимум два полных бэкапа плюс суточный запас WAL. Для базы 500 ГБ при сжатии zst закладывайте 300-600 ГБ под репозиторий.

Установка pgBackRest и базовая конфигурация

Пакеты pgBackRest есть в PGDG-репозитории для Debian/Ubuntu и RHEL-совместимых систем. Версия из репозитория ОС часто отстаёт на несколько минорных релизов, а инструмент активно развивается, поэтому ставьте из PGDG.

# Debian/Ubuntu
apt install -y pgbackrest

# RHEL/CentOS/Rocky
dnf install -y pgbackrest

Конфигурация строится из двух секций: [global] с параметрами репозитория и логирования, и [main] с параметрами конкретного кластера. Файл лежит в /etc/pgbackrest/pgbackrest.conf.

[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
repo1-retention-diff=7
archive-async=y
archive-push-queue-max=2GB
log-level-console=info
log-level-file=detail
log-path=/var/log/pgbackrest
process-max=4
compress-type=zst
start-fast=y

[main]
pg1-path=/var/lib/postgresql/16/main
pg1-port=5432
pg1-user=postgres

Разбор ключевых параметров:

  • repo1-path задаёт корень репозитория. Внутри pgBackRest создаст подкаталоги archive/ и backup/.
  • repo1-retention-full=2 хранит два полных бэкапа, repo1-retention-diff=7 хранит семь дифференциальных. Политика применяется командой expire, которую pgBackRest запускает автоматически после успешного backup.
  • archive-async=y включает асинхронную отправку WAL. PostgreSQL не ждёт завершения archive-push и продолжает работу, а журналы копятся в очереди pgBackRest.
  • archive-push-queue-max=2GB ограничивает очередь. При переполнении archive_command вернёт ошибку, PostgreSQL начнёт удерживать WAL в pg_wal. Порог защищает от бесконтрольного роста каталога.
  • process-max=4 задаёт четыре параллельных процесса для сжатия и передачи. На сервере с 8 vCPU ставьте 4-6, при 2 vCPU оставьте 2.
  • compress-type=zst даёт лучшее соотношение скорости и степени сжатия, чем gz. Для дистрибутивов без zstd используйте lz4.

Хранить репозиторий рядом с базой на том же диске бессмысленно: отказ диска уничтожит и данные, и копии. Для облачного репозитория добавьте repo1-s3-bucket с нужным регионом, для второго сервера используйте repo1-host с SSH-доступом. Если репозиторий размещается в облаке, подойдёт объектное хранилище или отдельный VPS: например, Timeweb Cloud предоставляет и то, и другое вместе с сетевыми дисками.

Настройка archive_command и archive-async

Связь PostgreSQL с pgBackRest держится на одном параметре в postgresql.conf. Настройки archive-async и archive-push-queue-max живут в pgbackrest.conf, а не в конфиге СУБД.

# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'pgbackrest --stanza=main archive-push %p'
archive_timeout = 300
# pgbackrest.conf, секция [global]
archive-async=y
archive-push-queue-max=2GB

archive_timeout=300 закрывает дыры в архиве при низкой активности. Без него сегмент WAL уходит в архив только после заполнения на 16 МБ, и PITR на последние минуты может не сработать.

Ошибка в archive_command останавливает запись в базу, когда в pg_wal заканчивается место. Проверяйте, что путь к pgbackrest в команде абсолютный или доступен в PATH пользователя postgres, а сама команда ничего не пишет в stdout при успехе. Диагностика опирается на pg_stat_archiver:

SELECT archived_count, failed_count, last_archived_wal,
       last_archived_time, last_failed_wal, last_failed_time
FROM pg_stat_archiver;

Рост failed_count и заполненное поле last_failed_wal означают, что журналы не уходят в репозиторий. Причина лежит в логе /var/log/pgbackrest/main-archive-push.log.

Проверка конфигурации: stanza-create и pgbackrest check

stanza-create создаёт в репозитории служебные файлы и записывает точку старта для WAL-архива. Команду запускают один раз на кластер, до первого бэкапа. PostgreSQL при этом работает, archive_command уже настроен.

sudo -u postgres pgbackrest --stanza=main stanza-create
sudo -u postgres pgbackrest --stanza=main check

check отвечает на три вопроса: есть ли соединение с PostgreSQL, доступен ли репозиторий, может ли pgBackRest положить и забрать тестовый сегмент WAL. Успешный вывод заканчивается строкой "check command end: completed successfully".

Типовые ошибки на этом шаге:

  • permission denied при записи в repo1-path. Лечение: chown -R postgres:postgres /var/lib/pgbackrest и chmod 750.
  • connection refused, если pg1-port или pg1-path не совпадают с реальным кластером. Сверьте путь с выводом sudo -u postgres psql -c "show data_directory;".
  • stanza already exists, если check запускали после неудачного stanza-create. Удалите служебные файлы командой stanza-delete с флагом --force.

Полные и дифференциальные бэкапы: стратегия и команды

Три типа бэкапа в pgBackRest различаются базой отсчёта. Полный копирует все файлы кластера. Дифференциальный копирует изменения с момента последнего полного. Инкрементальный копирует изменения с момента любого предыдущего бэкапа, включая дифференциальный.

ТипБаза отсчётаОбъёмВремя созданияДлина цепочки при restore
fullвсе файлы кластера100%максимум1
diffпоследний full10-40%среднее2
incrпоследний любой бэкап2-15%минимум2 и больше

Команды для каждого типа:

sudo -u postgres pgbackrest --stanza=main --type=full backup
sudo -u postgres pgbackrest --stanza=main --type=diff backup
sudo -u postgres pgbackrest --stanza=main --type=incr backup

Флаги, которые стоит добавить в конфиг или в команду: --start-fast запускает бэкап сразу, не дожидаясь контрольной точки; --process-max распараллеливает сжатие; --checksum-page проверяет контрольные суммы страниц при копировании и ловит повреждения на диске.

Расписание для базы 100-500 ГБ:

  • Воскресенье, 02:00: full.
  • Понедельник-суббота, 02:00: diff.
  • Каждые 6 часов, если окно бэкапа укладывается в 20-30 минут: incr между diff.

Восстановление всегда идёт по цепочке. Чем больше incr между full, тем дольше restore и тем выше шанс, что один повреждённый сегмент в середине цепочки сломает всю процедуру. Дифференциальный бэкап как ежедневный шаг даёт компромисс: цепочка из двух элементов вместо семи.

Инкрементальный бэкап pgBackRest: когда он оправдан

Инкрементальный бэкап оправдан, когда окно бэкапа жёсткое, а объём изменений за сутки велик. Пример: база 2 ТБ, за сутки меняется 300 ГБ. Дифференциальный бэкап займёт 300 ГБ и два часа, инкрементальный каждые 6 часов скопирует 40-80 ГБ за 20-30 минут.

Плата за это: длинная цепочка. Восстановление соберёт full плюс все инкрементальные сегменты последовательно. Планируйте не больше 12 инкрементальных бэкапов на один полный, иначе время восстановления вырастет в разы.

sudo -u postgres pgbackrest --stanza=main --type=incr \
  --process-max=6 backup

Retention и очистка старых бэкапов

Политикой хранения управляют два параметра: repo1-retention-full и repo1-retention-diff. Значение repo1-retention-full=2 означает, что pgBackRest держит два последних полных бэкапа и все зависимые от них diff и incr.

sudo -u postgres pgbackrest --stanza=main expire
sudo -u postgres pgbackrest info --stanza=main

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

Ключевое правило: retention не удалит бэкап, который ещё нужен для восстановления на точку внутри окна хранения. Если вы держите full за две недели, а WAL за 30 дней, PITR на 20-й день потребует full, который уже удалён. Держите окно WAL не длиннее окна хранения полных бэкапов. Стратегии хранения, включая схему 3-2-1, разобраны в материале резервное копирование в стратегии отказоустойчивости.

Непрерывное архивирование WAL

archive-push работает так: PostgreSQL завершает сегмент WAL, вызывает archive_command, pgBackRest сжимает сегмент и кладёт его в репозиторий. При archive-async=y СУБД не блокируется на время передачи, а журналы складываются в очередь.

Что даёт непрерывный архив:

  • PITR на любую секунду внутри окна хранения.
  • Дополнение дифференциальных бэкапов: без WAL между бэкапами теряются транзакции.
  • Возможность поднять реплику, если она отстала и её WAL уже удалён из pg_wal.

Репозиторий WAL растёт независимо от размера базы. Один сегмент занимает 16 МБ, после сжатия zst обычно 3-8 МБ. При интенсивной записи в 50 ГБ в сутки архив добавит 8-15 ГБ за день.

Мониторинг отставания WAL-архива

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

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

Пороги для алертов:

  • failed_count вырос с момента предыдущей проверки: смотреть /var/log/pgbackrest/main-archive-push.log.
  • Разница между current_wal и last_archived_wal больше одного сегмента: архив отстаёт.
  • last_archived_time старше 5 минут при archive_timeout=300: проблема с репозиторием или правами.
  • Число файлов в pg_wal выше обычного: очередь archive-push переполнена.
ls /var/lib/postgresql/16/main/pg_wal | wc -l
sudo -u postgres pgbackrest --stanza=main check

Восстановление до точки времени (PITR)

PITR нужен, когда база цела, но данные испорчены: ошибочный UPDATE без WHERE, DROP TABLE, сбой миграции. Восстановление идёт на отдельный инстанс, прод при этом продолжает работать, иначе вы потеряете транзакции, пришедшие после аварии.

Команда восстановления на точку времени:

sudo systemctl stop postgresql
sudo -u postgres pgbackrest --stanza=main \
  --type=time \
  --target="2026-09-11 14:30:00+03" \
  --target-action=promote \
  --delta \
  restore
sudo systemctl start postgresql

Разбор параметров:

  • --type=time задаёт целевую точку времени. Вместо времени можно указать --type=xid, --type=lsn или --type=name с именованной меткой.
  • --target принимает timestamp с часовым поясом. Без пояса PostgreSQL возьмёт зону сервера, и вы промахнётесь на несколько часов.
  • --target-action=promote переводит кластер из режима восстановления в рабочий автоматически, когда точка достигнута.
  • --delta копирует только изменившиеся файлы в существующий pgdata. Для чистого каталога флаг не нужен и только замедлит работу.

pgBackRest сам записывает в postgresql.auto.conf параметры recovery_target_time, recovery_target_action и restore_command с вызовом archive-get, а также создаёт файл recovery.signal. Править их вручную не требуется. Если нужно переопределить поведение, параметры задают в секции stanza:

[main]
recovery-option=recovery_target_time=2026-09-11 14:30:00+03
recovery-option=recovery_target_action=promote

После старта смотрите лог PostgreSQL. Запись "recovery completed" означает, что кластер достиг точки и открылся на запись. Запись "recovery stopping" без завершения говорит, что нужного сегмента WAL нет в архиве.

Сценарий восстановления после аппаратного сбоя с потерей всего кластера и подготовкой реплик через pg_rewind разобран в отдельном руководстве: восстановление PostgreSQL после сбоя в продакшне.

Восстановление на конкретный бэкап без PITR

Когда нужно вернуть состояние на момент бэкапа и откатывать транзакции не требуется, используйте --type=immediate.

sudo systemctl stop postgresql
sudo -u postgres pgbackrest --stanza=main \
  --type=immediate \
  --target-action=promote \
  --delta \
  restore
sudo systemctl start postgresql
sudo -u postgres psql -c "SELECT 1;"

Восстановление одного конкретного набора из цепочки задаётся флагом --set. Имя набора берётся из вывода pgbackrest info, например 20260911-020001F.

sudo -u postgres pgbackrest --stanza=main --set=20260911-020001F restore

Восстановление на отдельный инстанс для проверки

Тестовое восстановление идёт в другой каталог и на другой порт. Прод не останавливается, данные не рискуют.

sudo -u postgres pgbackrest --stanza=main \
  --pg1-path=/var/lib/postgresql/restore \
  --type=time \
  --target="2026-09-10 12:00:00+03" \
  --target-action=promote \
  restore

sudo -u postgres /usr/lib/postgresql/16/bin/pg_ctl \
  -D /var/lib/postgresql/restore \
  -o "-p 5433 -c listen_addresses='127.0.0.1'" start

Проверка данных на тестовом инстансе:

psql -h 127.0.0.1 -p 5433 -U postgres -c "SELECT pg_is_in_recovery();"
psql -h 127.0.0.1 -p 5433 -U postgres -d appdb \
  -c "SELECT count(*) FROM orders WHERE created_at > now() - interval '1 day';"

pg_is_in_recovery() должен вернуть false после promote. Пустой или подозрительно маленький count по ключевой таблице означает, что цепочка WAL оборвана раньше целевой точки.

Проверка бэкапов и регулярные тесты восстановления

Бэкап, который создался без ошибок, не равен бэкапу, который восстановится. Три уровня проверки закрывают разные риски.

  1. pgbackrest check ежедневно: доступ к PostgreSQL, репозиторию и WAL-архиву.
  2. pgbackrest verify после каждого бэкапа: контрольные суммы файлов в репозитории.
  3. Тестовое восстановление раз в месяц или квартал на отдельный инстанс: реальная проверка процедуры.

pgbackrest verify: что именно проверяется

verify пересчитывает контрольные суммы файлов бэкапа и сравнивает их с манифестом, проверяет наличие всех сегментов WAL и структуру цепочки full, diff, incr.

sudo -u postgres pgbackrest --stanza=main verify
sudo -u postgres pgbackrest --stanza=main verify --set=20260911-020001F

verify не проверяет логическую целостность данных. Он ответит, что файлы не повреждены, но не заметит, что таблица orders пуста из-за ошибочного DELETE за час до бэкапа. Логику проверяет только тестовое восстановление с контрольными запросами.

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

  1. Выбрать точку времени и зафиксировать её в журнале проверок.
  2. Восстановить бэкап в отдельный pgdata на отдельном порту.
  3. Запустить PostgreSQL и дождаться строки completed в логе.
  4. Проверить pg_is_in_recovery и версию кластера.
  5. Выполнить контрольные запросы: count по ключевым таблицам, последние записи, сверка с эталоном.
  6. Замерить время от старта restore до готовности принять запросы. Это фактический RTO.
  7. Записать результат: дата, набор бэкапа, точка времени, время восстановления, найденные проблемы.
  8. Удалить тестовый pgdata, чтобы не путать его с продовым.

Метрики успеха: время восстановления укладывается в целевой RTO, контрольные запросы вернули ожидаемые значения, в логе нет ошибок про отсутствующие сегменты WAL. Автоматизируйте прогон через systemd timer или cron, а отчёт складывайте в мониторинг. Пошаговая методика тестового восстановления с отчётом описана в статье как проверить резервную копию.

Типовые ошибки и их решение

Большинство инцидентов с бэкапами укладывается в два сценария: архив WAL перестал пополняться или бэкап не восстанавливается. Ниже диагностика обоих случаев.

WAL не архивируется: диагностика по pg_stat_archiver

Симптом: failed_count растёт, last_failed_wal заполнен, в pg_wal скапливаются сегменты. Порядок диагностики:

SELECT failed_count, last_failed_wal, last_failed_time FROM pg_stat_archiver;
sudo tail -n 50 /var/log/pgbackrest/main-archive-push.log
ls -la /var/lib/pgbackrest/archive/main/16-1/
  • permission denied: владелец repo1-path не postgres, лечится chown и chmod 750.
  • command not found: в archive_command указан pgbackrest без полного пути, а PATH пользователя postgres не содержит каталог установки. Пропишите /usr/bin/pgbackrest.
  • No space left on device: место в репозитории закончилось, archive-push не может записать сегмент.
  • stanza not found: имя в archive_command не совпадает с именем, созданным через stanza-create.
  • Ошибки SSH: ключ пользователя postgres не добавлен на хост репозитория или в known_hosts нет отпечатка.

Пока archive_command падает, PostgreSQL держит WAL в pg_wal. Когда место там заканчивается, запись в базу останавливается. Это самая частая причина аварий, связанных с бэкапами.

Бэкап создаётся, но восстановление падает

Симптом: backup завершается успешно, verify проходит, restore падает на середине.

  • Разорванная цепочка. Дифференциальный бэкап удалён командой expire, а зависящий от него инкрементальный остался. Проверьте pgbackrest info и удалите осиротевшие наборы.
  • Отсутствуют сегменты WAL. PITR за пределами окна хранения архива невозможен. Сверьте last_archived_wal с целевой точкой.
  • Не совпадает версия PostgreSQL на целевом хосте. Мажорная версия должна совпадать с той, что делала бэкап.
  • Нет прав на pgdata. Пользователь postgres должен владеть каталогом, куда идёт restore.
  • Не хватает места на диске. Полный restore с --delta требует места под изменённые файлы плюс запас.
sudo -u postgres pgbackrest info --stanza=main
df -h /var/lib/postgresql
sudo -u postgres psql -c "SELECT version();"

План отката для production: адаптируемый runbook

Runbook это документ, по которому дежурный действует без импровизации. Ниже структура, которую можно перенести в свою wiki и заполнить конкретными значениями.

  1. Критерии сбоя: потеря данных, повреждение кластера, логическая ошибка, отказ хранилища.
  2. Целевые RTO и RPO для каждого сервиса.
  3. Ответственные: кто принимает решение о восстановлении, кто выполняет, кто подтверждает успех.
  4. Каналы связи и точка сбора статуса.
  5. Пошаговый сценарий: остановка приложения, restore, promote, проверки, переключение трафика.
  6. Критерии успеха и условия отката к предыдущему состоянию.
  7. Пост-мортем в течение 48 часов.

Как определить RTO и RPO для вашего сервиса

RPO задаёт допустимый объём потерянных данных, RTO задаёт допустимое время простоя. Из них выводятся частота бэкапов и режим архивации.

СервисRPORTOСхема
Внутренний портал1 час4 часаfull раз в сутки, diff каждые 6 часов
Клиентский API5 минут30 минутfull в сутки, diff каждые 4 часа, archive-async
Платёжный сервис1 минута15 минутfull в сутки, incr каждые 2 часа, архив WAL, реплика, ежемесячный тест restore

Плановый переезд на другое железо планируется отдельно от аварийного восстановления, хотя инструменты пересекаются. Логика проверок при переезде описана в материале миграция данных PostgreSQL.

Что проверить после восстановления

Восстановление не завершается командой promote. Вот проверки перед переключением трафика:

  • SELECT pg_is_in_recovery() возвращает false.
  • Контрольные count по ключевым таблицам совпадают с ожидаемыми.
  • Последние транзакции на месте: сверьте id и timestamp нескольких записей.
  • Приложение прошло health-check на восстановленном инстансе.
  • В логах PostgreSQL и приложения нет ошибок целостности.
  • Свободного места на диске хватает для работы под нагрузкой.

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

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