Коротко. Реплика даёт копию на секунды позади и быстрое ручное переключение, но не защищает от ошибок и не переключается сама. Слот плюс max_slot_wal_keep_size, мониторинг в байтах, wal_log_hints для pg_rewind, резервные копии отдельно, и переключение отрепетировано на тестовом стенде вместе с RX.

Реплику базы Directum RX обычно поднимают с формулировкой «для отказоустойчивости». Через год выясняется, что реплика отстала на неделю и никто не заметил, переключаться на неё никто не репетировал, а ошибочно удалённые данные исчезли и с реплики тоже, потому что она честно повторила удаление. Разбираем, что реплика PostgreSQL действительно даёт, где её границы и что подготовить заранее, чтобы переключение прошло спокойно.

Симптом

  • На ведущем сервере кончается место: каталог pg_wal растёт из-за слота отставшей или выключенной реплики.
  • Отчёт или выгрузка на реплике обрываются с canceling statement due to conflict with recovery.
  • Мониторинг показывает огромное отставание реплики ночью, хотя она в порядке.
  • При аварии мастера переключились на реплику, а потом старый мастер поднялся сам, и часть сервисов писала в него.
  • После потери данных по ошибке оператора реплику пытаются использовать как резервную копию, но удаление на ней уже применено.

Что реплика даёт и чего не даёт

Потоковая реплика получает журнал изменений (WAL) от ведущего сервера и применяет его у себя. Она всё время находится в режиме восстановления и принимает только чтение.

Даёт Не даёт
Копию базы на другом сервере, отстающую на секунды Защиту от ошибок: DELETE и DROP применяются на реплике так же быстро
Возможность переключиться на неё за минуты при потере мастера Автоматическое переключение: для него нужен отдельный менеджер кластера
Место для тяжёлых запросов на чтение: отчёты, выгрузки, BI Разгрузку самой RX: её сервисы подключены к ведущему серверу
Источник для резервного копирования без нагрузки на мастер Резервную копию: нет истории, нельзя вернуться на вчера

Поэтому реплика дополняет резервное копирование и не заменяет его. Восстановление на момент до ошибки даёт только резервная копия с архивом WAL. Как проверять, что такая копия у вас есть, рассказано в статье про бэкап RX на PostgreSQL.

Асинхронная или синхронная

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

Синхронная репликация (synchronous_standby_names и synchronous_commit) ждёт подтверждения от реплики. Потерь при отказе мастера нет, но каждая запись ждёт сеть, а если единственная синхронная реплика недоступна, мастер перестаёт подтверждать записи, и RX встаёт. Синхронную схему делают с двумя репликами и правилом «любая одна из двух» (ANY 1 (replica1, replica2)), тогда выключение одной реплики не останавливает систему.

Для большинства инсталляций RX хватает асинхронной реплики плюс архив WAL: потеря нескольких секунд при аварии мастера допустима, а лишняя задержка на каждой записи заметна пользователям.

Слоты репликации и диск

Слот репликации гарантирует, что мастер не удалит WAL, который реплика ещё не получила. Без слота отставшая реплика может обнаружить, что нужный ей сегмент уже удалён, и её придётся пересоздавать (разбор ошибки).

Обратная сторона: если реплика выключена или сломана, слот держит WAL бесконечно, и каталог pg_wal на мастере растёт до заполнения диска. Мастер с полным диском останавливается. Защита:

  • max_slot_wal_keep_size (PostgreSQL 13 и новее) ограничивает, сколько WAL может удерживать слот. Превысивший слот становится недействительным, реплику придётся пересоздать, но мастер выживает.
  • Мониторинг удерживаемого объёма по каждому слоту:
SELECT slot_name, active,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained
FROM pg_replication_slots;
  • Удаление слотов реплик, которые больше не нужны: SELECT pg_drop_replication_slot('имя');.

Подробнее о том, почему растёт pg_wal, в статье про архив и слоты.

Как мерить отставание

На мастере pg_stat_replication показывает каждую реплику и три задержки: запись, сброс на диск и применение.

SELECT application_name, client_addr, state, sync_state,
       write_lag, flush_lag, replay_lag,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replay_bytes
FROM pg_stat_replication;

Если реплики нет в этом списке, она не подключена, и это авария, даже если отставание никто не меряет.

На реплике часто смотрят время последней применённой транзакции: now() - pg_last_xact_replay_timestamp(). У этой метрики известная ловушка: ночью, когда в базу никто не пишет, применять нечего, и «отставание» растёт на часы при полностью исправной реплике. Надёжнее мерить в байтах на мастере или сравнивать pg_last_wal_receive_lsn() и pg_last_wal_replay_lsn() на самой реплике.

Функции мастера на реплике не работают, и запрос мониторинга, который их вызывает, получает recovery is in progress. Запросы мониторинга должны проверять роль сервера через pg_is_in_recovery().

Запросы на реплике

Долгий запрос на реплике может мешать применению WAL: например, мастер вычистил строки, которые запрос ещё читает. PostgreSQL ждёт max_standby_streaming_delay (по умолчанию 30 секунд) и отменяет запрос с canceling statement due to conflict with recovery.

Настройка Эффект Цена
Увеличить max_standby_streaming_delay запросы реже отменяются реплика отстаёт на время долгих запросов
hot_standby_feedback = on мастер не вычищает строки, нужные запросам реплики раздувание таблиц на мастере при долгих запросах

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

Переключение

Вручную

  1. Убедиться, что мастер действительно недоступен и не вернётся сам. Если он может подняться, его нужно остановить и не дать запуститься: два сервера, принимающих запись, хуже, чем простой.
  2. Продвинуть реплику: SELECT pg_promote(); (PostgreSQL 12 и новее) или pg_ctl promote.
  3. Переключить клиентов на новый адрес.
  4. Проверить, что новый мастер принимает запись: SELECT pg_is_in_recovery(); должно вернуть f.

Как RX узнает новый адрес

Варианты, от простого к надёжному:

  • Поменять адрес в строке подключения и перезапустить сервисы. Работает всегда, но это ручной шаг в момент аварии.
  • Общее имя в DNS или виртуальный адрес, который переносится на новый мастер. Строка подключения не меняется, сервисы переподключаются сами после обрыва соединений (в журналах в этот момент будут 57P01 и 57P03).
  • Несколько адресов в строке подключения. Npgsql умеет перебирать адреса и выбирать ведущий сервер (Host=db01,db02;Target Session Attributes=primary). Поддерживает ли это ваша версия RX без побочных эффектов, проверяйте на тестовом стенде. Keycloak и другие Java-приложения делают то же через targetServerType=primary, и когда ведущего нет, пишут Could not find a server with specified targetServerType.

Автоматически

Автоматическое переключение делает менеджер кластера: Patroni с хранилищем состояния (etcd, Consul) и виртуальным адресом или прокси, у Postgres Pro Enterprise есть встроенный механизм BiHA. Менеджер решает, кто мастер, переносит адрес и не даёт старому мастеру принимать запись. Это заметно сложнее в сопровождении, и без опыта эксплуатации кластер с автоматическим переключением ломается чаще, чем одиночный сервер. Для многих инсталляций RX надёжнее ручное переключение по отрепетированной инструкции.

Возврат старого мастера

Старый мастер после переключения нельзя просто запустить как реплику: у него могут быть транзакции, которых нет на новом. pg_rewind откатывает его до точки расхождения и подключает к новому мастеру без полного копирования. Для этого заранее должны быть включены wal_log_hints = on или контрольные суммы страниц. Если ни того, ни другого нет, старый мастер пересоздаётся с нуля через pg_basebackup.

Что сделать

  1. Реплика со слотом и max_slot_wal_keep_size, мониторинг отставания в байтах и наличия реплики в pg_stat_replication.
  2. Резервное копирование и архив WAL отдельно от реплики. Копию можно снимать с реплики, чтобы не нагружать мастер: pg_probackup и другие инструменты это умеют.
  3. wal_log_hints = on на обоих серверах заранее, чтобы после переключения работал pg_rewind.
  4. Письменная инструкция переключения с проверками и репетиция на тестовом стенде с RX: переключить, проверить вход, создание документа, фоновые задания, вернуть.
  5. Решение, как RX узнаёт новый адрес: DNS, виртуальный адрес или правка строки подключения.

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

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