Коротко. Архив WAL сам по себе не чистится. Удалять можно только сегменты раньше начала самой старой копии, которую вы храните, а не всё старше N дней. Для копий через pg_basebackup это делает pg_archivecleanup по файлу .backup, для pg_probackup delete --delete-expired --delete-wal, для WAL-G delete retain, для pgBackRest expire. Нужный объём оценивается по суточному объёму WAL, умноженному на срок хранения, а очистка стоит в расписании рядом с созданием копий.

Резервные копии настроили, восстановление однажды проверили, и про них забыли. Через полгода приходит тревога: на сервере резервных копий кончается место, а каталог с архивом журналов PostgreSQL занимает четыре терабайта при базе на двести гигабайт. Первое желание удалить всё старше недели обычно заканчивается тем, что ни одна копия больше не восстанавливается. Ниже разбор, откуда берётся объём, что удалять можно и чем.

Если растёт не архив, а каталог pg_wal внутри самой базы, это другая история с другими причинами: она разобрана в статье «Каталог pg_wal растёт, диск кончается».

Симптом

  • Каталог архива WAL или репозиторий инструмента резервного копирования растёт без остановки.
  • Объём архива в разы больше самой базы.
  • Свободное место на сервере копий уменьшается с одинаковой скоростью, даже когда копии вроде бы удаляются.

Почему так

PostgreSQL пишет все изменения в журнал (WAL) сегментами по 16 МБ. При включённом архивировании каждый заполненный сегмент копируется командой archive_command в архив. Восстановление на любой момент времени устроено так: берётся полная копия базы и к ней по порядку применяются сегменты из архива начиная с момента этой копии. Отсюда главное правило: для каждой копии, которую вы храните, нужны все сегменты от её начала и дальше без единого пропуска.

Сам PostgreSQL архив никогда не чистит, это задача того, кто его ведёт. Архив разрастается по одной из причин.

Хранение не настроено вовсе. archive_command копирует файлы командой cp или rsync, а удалять их никто не поручал. Объём растёт линейно и бесконечно.

Правило хранения есть, но не срабатывает. У инструмента копий задано «хранить последние две полные копии», но очистка запускается отдельной командой, а её забыли поставить в расписание. Или новые копии последние недели падают, и по правилу самые старые успешные копии удалять нельзя: вместе с ними держится весь архив с момента их создания.

Одна старая копия держит весь архив. Копию когда-то закрепили «навсегда», например перед обновлением. Пока она есть, все сегменты от её начала тоже нужны.

Изменений стало больше. Объём WAL зависит не от размера базы, а от количества изменений. Массовые обновления, загрузка данных, перестроение таблиц и индексов (VACUUM FULL, REINDEX, pg_repack) за ночь порождают журналов больше, чем вся база весит. Архив за неделю с такими работами может оказаться в несколько раз больше обычного.

Диагностика

Сколько занимает архив и сколько в нём файлов:

du -sh /backup/wal_archive
ls /backup/wal_archive | wc -l
ls /backup/wal_archive | head -1; ls /backup/wal_archive | tail -1

Имя файла WAL хранит его порядковый номер, поэтому первый и последний файл в списке показывают, какой период лежит в архиве.

Как работает архивирование со стороны базы:

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

Растущий failed_count значит, что сегменты не доходят до архива. Тогда опасность уже не в объёме архива, а в дыре в нём и в росте pg_wal на самой базе.

Сколько WAL база порождает в сутки. Запомните текущую позицию журнала, через час посмотрите разницу:

SELECT pg_current_wal_lsn();          -- например 5A3/1C000000
-- через час:
SELECT pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '5A3/1C000000'));

Умножив часовой объём на 24 и на срок хранения, вы получите оценку нужного места. В PostgreSQL 14 и новее то же самое накопительно показывает SELECT wal_bytes, stats_reset FROM pg_stat_wal;.

Что смотрим Где Норма
Объём архива du -sh каталога архива примерно суточный объём WAL, умноженный на срок хранения
Ошибки архивирования pg_stat_archiver.failed_count не растёт
Самая старая копия список копий инструмента не старше срока хранения
Последняя успешная копия там же не старше суток или вашего расписания

Что сделать

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

Копии через pg_basebackup и архив командой cp

После каждой полной копии в архиве появляется небольшой файл с суффиксом .backup, в имени которого записан сегмент начала копии. Утилита pg_archivecleanup из состава PostgreSQL удаляет всё, что было раньше него. Сначала посмотрите, что будет удалено, ключом -n:

ls /backup/wal_archive/*.backup
pg_archivecleanup -n /backup/wal_archive 00000001000005A30000001C.00000028.backup | wc -l
pg_archivecleanup /backup/wal_archive 00000001000005A30000001C.00000028.backup

В команду подставляется файл .backup той копии, которая теперь самая старая из оставляемых. Запускать очистку разумно в том же скрипте, что удаляет старую полную копию, чтобы архив и копии не расходились.

pg_probackup

У pg_probackup правило хранения задаётся в настройках экземпляра, а применяется отдельной командой:

pg_probackup set-config -B /backup --instance main --retention-redundancy=2 --retention-window=7
pg_probackup delete -B /backup --instance main --delete-expired --delete-wal
pg_probackup show -B /backup --instance main --archive

--delete-wal удаляет сегменты, которые больше не нужны ни одной оставшейся копии. Последняя команда показывает диапазон сегментов в архиве и копии, которые на них опираются. Удаление ставят в расписание сразу после создания копии.

WAL-G и pgBackRest

WAL-G удаляет копии вместе с ненужными журналами одной командой:

wal-g backup-list
wal-g delete retain FULL 7 --confirm

pgBackRest чистит репозиторий сам после каждой копии по параметру repo1-retention-full в pgbackrest.conf, а вручную та же очистка запускается командой pgbackrest --stanza=main expire.

Чтобы не повторилось

  1. Срок хранения задаётся числом копий или днями и записывается в регламент, а не живёт в голове.
  2. Очистка стоит в том же расписании, что и создание копий, и её результат проверяется.
  3. Мониторинг смотрит на свободное место на сервере копий, на рост failed_count в pg_stat_archiver и на возраст последней успешной копии. Неудачные копии и неработающая очистка видны раньше, чем кончится диск.
  4. Тяжёлые работы с базой (перестроение таблиц, массовые загрузки) планируются с запасом места под архив на эти дни.
  5. Сжатие архива включается в инструменте копий: pg_probackup, WAL-G и pgBackRest умеют сжимать сегменты, и обычно это сокращает объём в несколько раз.

Чего не делать

  • Не удалять файлы архива по дате, например find -mtime +7 -delete. Самой старой оставшейся копии нужны все сегменты с момента её начала, и такая чистка молча делает её невосстанавливаемой.
  • Не чистить вручную архив, которым управляет pg_probackup, WAL-G или pgBackRest. У инструмента свой каталог копий и журналов; удаление файлов в обход него ломает его учёт, и следующая проверка или восстановление падает с ошибкой вроде WAL segment is absent.
  • Не отключать архивирование, чтобы остановить рост. Если очистить archive_command при включённом archive_mode, PostgreSQL перестаёт архивировать, но продолжает копить сегменты в pg_wal на самой базе. Проблема переедет с сервера копий на сервер базы, где она опаснее.
  • Не удалять старые копии, пока новые не проверены. Если последние копии падают, самые старые и есть единственные рабочие.