Добрый день, меня зовут Андрей, я специалист по администрированию баз данных в компании Т1 Сервионика. За 2,5 года под моим контролем побывало около 700 кластеров баз данных, из которых 80 % — High Avaiability, треть из них — это трёхнодовые полноценные кластеры, где есть мастер, синхронная и асинхронная реплики. Также были успешно проведены проекты по миграции с Oracle и MSSQL на PostgreSQL.

Резервное копирование — один из важнейших процессов администрирования баз данных. К сожалению, никто не застрахован от сбоев оборудования или логических ошибок. Однажды мы столкнулись с ошибкой резервного копирования PostgreSQL, которая возникает у многих пользователей Pgbackrest. В сети нет единого описания её исправления. Расскажу о том, к какому решению мы пришли, и как в компании реализовано резервное копирование PostgreSQL.

Ошибка

PostgreSQL поддерживает логическое и физическое копирование и архивацию данных. В повседневной работе в функционирующем кластере использование процедуры физического копирования оправдано. Это удобно и быстро. К тому же позволяет вернуть кластер в состояние на нужный нам момент времени.

Помимо штатных инструментов PostgreSQL, существует стороннее программное обеспечение для физического копирования и архивации WAL-файлов, такое как Pg_probackup, Pgbackrest и другое. Каждый из этих инструментов имеет свои преимущества, но для выполнения поставленной задачи мы выбрали Pgbackrest. В отличие от Pg_probackup, Pgbackrest легко настраивается для работы с кластерами High Avaiability Patroni без необходимости в дополнительных скриптах на узлах кластера. При переключении мастера архивирование WAL не прерывается, а резервная копия создается без проблем, независимо от того, какой узел является мастером. При необходимости можно настроить pgbackrest для создания резервных копий со Standby-сервера. В этом случае pgbackrest копирует только те файлы, которые реплицируются с резервного хоста, а затем копирует оставшиеся файлы с primary-хоста.

Cхема работы pgbackrest.
Cхема работы pgbackrest.

Для резервного копирования Pgbackrest требуется создать контрольную точку и архивировать последний WAL-файл. На этом этапе мы столкнулись с проблемой, упомянутой в начале статьи. В нашей инфраструктуре используется несколько Zabbix с базой данных на PostgreSQL. Каждый экземпляр Zabbix оперирует множеством хостов, на каждом из которых регистрируется в среднем более 1000 метрик. При создании копии с помощью Pgbackrest возникала следующая ошибка:

P00 ERROR: [082]: WAL segment 000000110000064900000018.ready was not archived before the 60000ms timeout

Для поиска причин мы обратились к логам и проверили статусы архивирования WAL-файлов. Cегменты WAL хранятся в каталоге $PGDATA/pg_wal:

ls -la $PGDATA /pg_wal -rw-------  1 postgres postgres 16777216 Mar 20 21:25 000000110000064900000049
-rw-------  1 postgres postgres 16777216 Mar 20 21:26 00000011000006490000004A
-rw-------  1 postgres postgres      725 Mar 16 12:01 00000011.history
drwx------  2 postgres postgres    16384 Mar 21 02:01 archive_status

Затем мы перешли в каталог archive_status, в котором хранится статус архивирования всех сегментов WAL:

ls -la $PGDATA /pg_wal/archive_status/
-rw------- 1 postgres postgres     0 Mar 21 01:48 000000110000064900000005.done

…………

-rw------- 1 postgres postgres     0 Mar 21 02:02 000000110000064900000018.ready
-rw------- 1 postgres postgres     0 Mar 21 02:03 000000110000064900000019.ready
-rw------- 1 postgres postgres     0 Mar 21 02:04 00000011000006490000001A.ready
-rw------- 1 postgres postgres     0 Mar 21 02:05 00000011000006490000001B.ready

После точки с именем WAL указан статус. Ready означает, что WAL находится в очереди на архивирование. При успешном выполнении должен быть статус done. В списке был представлен лишь один сегмент с таким статусом, в начале перечня.

Исправление ошибки

Для настройки параметров контрольных точек мы воспользовались представлением pg_stat_bgwriter:

#Контрольная точка
SELECT * FROM pg_stat_bgwriter \gx
-[ RECORD 1 ]---------+------------------------------

checkpoints_timed

1814 — контрольные точки по расписанию (по достижении checkpoint_timeout).

checkpoints_req

0 — контрольные точки по требованию (в том числе по достижении max_wal_size).

checkpoint_write_time

532074

checkpoint_sync_time

86654

buffers_checkpoint

4840 — информация о количестве страниц, записанных процессом контрольной точки.

buffers_clean

0 — информация о количестве страниц, записанных процессом фоновой записи.

maxwritten_clean

0 — информация о количестве остановок bgwriter из-за превышения максимального количества страниц. Когда вы увидите высокие значения, вам следует увеличить bgwriter_lru_maxpages.

buffers_backend

40 — информация о количестве страниц, записанных обслуживающими процессами (<<buffers_checkpoint и buffers_clean).

buffers_backend_fsync

0 — показывает, вынуждены ли бэкенды делать собственные запросы fsync для синхронизации буферов с хранилищем. Любые значения выше нуля говорят о проблемах с хранилищем при полном заполнении очереди fsync. В новых версиях PostgreSQL эти проблемы решены, и я уже давно не видел ненулевых значений.

buffers_alloc

1907

stats_reset

2023-11-06 22:19:41.408446+03

В представлении pg_stat_bgwriter есть столбцы checkpoints_timed и checkpoints_req, в которых указано количество контрольных точек, произошедших с момента последнего сброса статистики. Существует общее правило: значение checkpoints_timed должно быть намного выше, чем checkpoints_req. Желательно, чтобы последний был близок к нулю. Причина этого в следующем. Основная работа контрольных точек заключается в синхронизации «грязных» страниц в shared buffers с файлами данных на диске. Высокие значения checkpoints_req свидетельствуют о том, что при записи данных на диск PostgreSQL находится в постоянной стрессовой ситуации. Это приводит к снижению производительности PostgreSQL.

Высокая доля checkpoints_req_pct говорит о частых срабатываниях контрольных точек из-за полного буфера (достигнут max_wal_size). Что, в свою очередь, указывает либо на слишком маленький таймаут контрольной точки, либо на интенсивную запись в базу данных. Во избежание переполнения буфера обычно рекомендуется изменить настройки контрольных точек или max_wal_size.

Для просмотра текущего статуса архивации мы воспользовались представлением pg_stat_archiver:

SELECT * FROM pg_stat_archiver \gx
-[ RECORD 1 ]------+---------------------------------

archived_count

45107 — общее количество.

last_archived_wal

000000080000042D000000FE — имя последнего файла WAL, который был успешно архивирован.

last_archived_time

2023-12-14 21:52:47.647179+03 — время последнего успешного архивирования.

failed_count

1 — количество неудачных попыток архивации.

last_failed_wal

000000070000037D000000ED.partial — имя последнего файла WAL, который не удалось архивировать.

last_failed_time

2023-11-13 09:36:40.38233+03 — время последнего неудачного архивирования.

stats_reset

2023-10-27 01:45:17.13674+03 — время сброса статистики.

После изучения журналов PostgreSQL и Pgbackrest, а также каталог $PGDATA /pg_wal/archive_status/, мы пришли к выводу, что архивация PostgreSQL работает корректно, WAL-файлы архивируются на сервер с Pgbackrest, но очередь продолжает копиться. Причина такого поведения кроется в том, что Pgbackrest по умолчанию копирует на сервер сегменты WAL в одном потоке. Частое срабатывание контрольных точек приводит к тому, что Pgbackrest не успевает копировать. Для решения этой проблемы достаточно увеличить количество потоков. Если значения параметров checkpoint_timeout или max_wal_size слишком велики, это может привести к увеличению длительности восстановления базы данных. Обратите внимание, что значение параметра max_wal_size — это пожелание, а не ограничение. Это значение может быть превышено. Но в таком случае при архивации WAL может возникнуть нехватка скорости передачи данных между хостами, поэтому будет возникать очередь архивирования. При совпадении двух факторов — большой очереди архивирования и резкого падения ноды вследствие аварийного отключения или потери сетевой доступности — возможны нарушения RPO (recovery point objective), так как журналы WAL останутся на упавшей ноде. Поэтому необходимо, чтобы очередь архивирования не росла.

Используя документацию Pgbackrest, мы выбрали интересующие нас настройки:

--archive-async — асинхронная отправка и получение сегментов WAL. Эта команда включает асинхронную работу для команд archive-push и archive-get. В результате появляется возможность распараллелить потоки и повторно использовать соединения

--process-max — максимальное количество процессов, используемых для сжатия и передачи. Каждый процесс будет сжимать данные, чтобы команда выполнялась быстрее. Однако следует избегать слишком высокого значения max-max, потому что это может привести к снижению производительности базы данных. Если задать такое значение:

[global:archive-push]
Process-max=2
[global:archive-get]
Process-max=2

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

--archive-push-queue-max — максимальный размер очереди архивирования PostgreSQL. Однако для решения нашей задачи в нём нет потребности, так как после достижения лимита Pgbackrest уведомит PostgreSQL об успешном архивировании WAL, а затем удалит его. Предупреждение будет выведено в журнал PostgreSQL. В этом случае поток архивного журнала будет прерван, и с этого момента PITR будет невозможен. Для полного восстановления потребуется новая резервная копия, которую необходимо создавать по расписанию, чтобы избежать снижения производительности других процессов.

Изменение параметра [global:archive-get] помогает при восстановлении PITR за счёт увеличения количества потоков получения сегментов WAL. Этот параметр можно использовать в совокупности с параметром archive-get-queue-max, который определяет максимальный размер очереди получения архива Pgbackrest и работает при включённой асинхронной архивации. Очередь хранится в spool-path (пути, по которому хранятся временные данные) и используется для ускорения предоставления WAL в PostgreSQL. Также spool-path используется для хранения данных для асинхронных команд archive-push и archive-get.

Асинхронная команда archive-push записывает подтверждения в spool-path при успешном сохранении WAL в архиве (и при возникновении ошибок в случае сбоя), поэтому приоритетный процесс может быстро уведомить PostgreSQL. Размер файлов подтверждений очень мал (0 в случае успеха и несколько сотен байтов в случае ошибки).

Асинхронная команда archive-get ставит WAL в очередь в spool-path, чтобы его можно было очень быстро предоставить по запросу PostgreSQL. Перемещать файлы в PostgreSQL эффективнее всего, когда spool-path находится в той же файловой системе, что и pg_xlog (для версий PostgreSQL 9 и младше) или pg_wal (для версий PostgreSQL 10 и старше).

Данные, хранящиеся в spool-path, не являются строго временными, поскольку они могут и должны пережить перезагрузку. Однако в случае их потери Pgbackrest перепроверит, чтобы каждый сегмент WAL был безопасно заархивирован для archive-push, и перестроит очередь для archive-get.

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

[host] pg1-path=/pg_data/data pg1-socket-path=/var/run/PostgreSQL
[global] repo1-host=host
Recovery time 02:37:45

При таких настройках восстановление PITR ускоряется во много раз:

[hosth] pg1-path=/pg_data/data pg1-socket-path=/var/run/PostgreSQL
[global] repo1-host=host archive-async=y spool-path=/pg_data/pgbackres archive-get-queue-max=4GiB
[global:archive-get] process-max=4
[global:archive-push]
Recovery time 00:08:08

Pgbackrest — один из лучших продуктов для реализации резервного копирования PostgreSQL. К его преимуществам можно отнести подробную документацию и обширный набор настроек для решения задач различной сложности. Однако подчеркнём, что выбор инструмента зависит от целей, задач и конкретной инфраструктуры со своими архитектурными особенностями.

Комментарии (3)


  1. Sleuthhound
    22.06.2024 08:12

    Спасибо за статью. А кроме pgbackres вы что то иное тестировали для сравнения?

    pgMoneta, pg_rman, pg_probackup, wal-g?


  1. ashkraba
    22.06.2024 08:12

    Мне лично wal-g больше симпатизирует. И дока лучше и сам инструмент проще. Плюс из ci удобно ранать прям из контейнера.


    1. chemtech
      22.06.2024 08:12

      Можете поделиться вашим ci?