Добрый день, меня зовут Андрей, я специалист по администрированию баз данных в компании Т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-хоста.
Для резервного копирования 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 — контрольные точки по расписанию (по достижении |
checkpoints_req | 0 — контрольные точки по требованию (в том числе по достижении |
checkpoint_write_time | 532074 |
checkpoint_sync_time | 86654 |
buffers_checkpoint | 4840 — информация о количестве страниц, записанных процессом контрольной точки. |
buffers_clean | 0 — информация о количестве страниц, записанных процессом фоновой записи. |
maxwritten_clean | 0 — информация о количестве остановок |
buffers_backend | 40 — информация о количестве страниц, записанных обслуживающими процессами ( |
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. К его преимуществам можно отнести подробную документацию и обширный набор настроек для решения задач различной сложности. Однако подчеркнём, что выбор инструмента зависит от целей, задач и конкретной инфраструктуры со своими архитектурными особенностями.