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 | последний full | 10-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 оборвана раньше целевой точки.
Проверка бэкапов и регулярные тесты восстановления
Бэкап, который создался без ошибок, не равен бэкапу, который восстановится. Три уровня проверки закрывают разные риски.
- pgbackrest check ежедневно: доступ к PostgreSQL, репозиторию и WAL-архиву.
- pgbackrest verify после каждого бэкапа: контрольные суммы файлов в репозитории.
- Тестовое восстановление раз в месяц или квартал на отдельный инстанс: реальная проверка процедуры.
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 за час до бэкапа. Логику проверяет только тестовое восстановление с контрольными запросами.
Чек-лист тестового восстановления
- Выбрать точку времени и зафиксировать её в журнале проверок.
- Восстановить бэкап в отдельный pgdata на отдельном порту.
- Запустить PostgreSQL и дождаться строки completed в логе.
- Проверить pg_is_in_recovery и версию кластера.
- Выполнить контрольные запросы: count по ключевым таблицам, последние записи, сверка с эталоном.
- Замерить время от старта restore до готовности принять запросы. Это фактический RTO.
- Записать результат: дата, набор бэкапа, точка времени, время восстановления, найденные проблемы.
- Удалить тестовый 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 и заполнить конкретными значениями.
- Критерии сбоя: потеря данных, повреждение кластера, логическая ошибка, отказ хранилища.
- Целевые RTO и RPO для каждого сервиса.
- Ответственные: кто принимает решение о восстановлении, кто выполняет, кто подтверждает успех.
- Каналы связи и точка сбора статуса.
- Пошаговый сценарий: остановка приложения, restore, promote, проверки, переключение трафика.
- Критерии успеха и условия отката к предыдущему состоянию.
- Пост-мортем в течение 48 часов.
Как определить RTO и RPO для вашего сервиса
RPO задаёт допустимый объём потерянных данных, RTO задаёт допустимое время простоя. Из них выводятся частота бэкапов и режим архивации.
| Сервис | RPO | RTO | Схема |
|---|---|---|---|
| Внутренний портал | 1 час | 4 часа | full раз в сутки, diff каждые 6 часов |
| Клиентский API | 5 минут | 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 и приложения нет ошибок целостности.
- Свободного места на диске хватает для работы под нагрузкой.
Переключайте трафик только после того, как все пункты пройдены. Если приложение пишет в базу во время восстановления, сначала переведите его в режим только чтения или остановите полностью, иначе часть транзакций уйдёт в потерянный кластер.