Настройка синхронной репликации для высокой доступности в PostgreSQL

Узнайте, как настроить высокую доступность PostgreSQL с нулевой потерей данных (RPO=0) с помощью синхронной потоковой репликации. Это пошаговое руководство охватывает основные конфигурации `wal_level`, слотов репликации, `pg_basebackup` и правильную настройку параметров `synchronous_commit` на основном и резервном серверах для гарантии долговечности транзакций в критических средах.

Настройка синхронной репликации для высокой доступности в PostgreSQL

Настройка PostgreSQL для высокой доступности (HA) обычно начинается с трудного вопроса: сколько данных вы можете позволить себе потерять, если основной сервер исчезнет сразу после фиксации? При обычной асинхронной потоковой репликации ответ — «возможно, некоторые». Основной сервер может сообщить приложению, что транзакция зафиксирована до того, как резервный сервер получил или воспроизвел запись WAL. Если основной сервер выйдет из строя в течение этого небольшого окна, повышенный резервный сервер может не содержать последние несколько зафиксированных транзакций.

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

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

Предварительные требования

Перед началом убедитесь, что у вас есть два сервера PostgreSQL (основной и резервный), работающие с одинаковыми основными версиями PostgreSQL. Оба сервера должны иметь сетевое соединение. Для этого руководства мы предполагаем:

  • Имя хоста/IP основного сервера: pg_primary
  • Имя хоста/IP резервного сервера: pg_standby
  • Пользователь репликации: repl_user
  • Имя базы данных: mydb

Вам также понадобится рабочая резервная копия и окно обслуживания для начальной базовой резервной копии. Примеры предполагают PostgreSQL 12 или новее, где режим резервного сервера управляется с помощью standby.signal, а настройки подключения обычно записываются с помощью pg_basebackup -R.

Шаг 1: Настройка основного сервера

Основной сервер требует определенных настроек для включения потоковой репликации и управления журналом упреждающей записи (WAL), необходимым для синхронных фиксаций.

A. Настройка postgresql.conf на основном сервере

Отредактируйте файл postgresql.conf основного сервера. Следующие параметры обязательны для потоковой репликации:

# --- Требуется для репликации ---
listen_addresses = '*'         # Разрешает подключения с резервного сервера
wal_level = replica            # Должно быть 'replica' или выше (например, 'logical')
max_wal_senders = 10           # Максимальное количество одновременных подключений от резервных серверов
max_replication_slots = 10     # Слоты, необходимые для постоянных потоков репликации

# --- Важно для синхронной фиксации ---
synchronous_standby_names = 'FIRST 1 (standby1)' # Указывает требуемые резервные серверы по application_name

# --- Необязательно, но рекомендуется ---
wal_log_hints = on             # Рекомендуется для более безопасной репликации, хотя увеличивает объем WAL
shared_preload_libraries = 'pg_stat_statements' # Если используется мониторинг

Пояснение ключевых параметров:

  • wal_level = replica: Это гарантирует, что в WAL записывается достаточно информации, чтобы резервный сервер мог восстановить состояние базы данных. Для синхронных фиксаций этот уровень является минимальным требованием.
  • synchronous_standby_names: Это основная настройка для определения того, какие резервные серверы должны подтверждать записи. Указанные здесь имена являются значениями application_name соединения репликации, а не именами слотов репликации. FIRST 1 (standby1) означает, что PostgreSQL ожидает первый доступный синхронный резервный сервер из этого списка. ANY 1 (standby1, standby2) означает, что любой из перечисленных резервных серверов может удовлетворить фиксацию.

B. Настройка аутентификации на основе хоста (pg_hba.conf)

Основной сервер должен разрешить пользователю репликации с резервного сервера (серверов) подключаться для целей репликации.

Добавьте запись в pg_hba.conf на основном сервере:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     repl_user       pg_standby/32           scram-sha-256

Замените pg_standby/32 на фактический IP-адрес или подсеть вашего резервного сервера.

C. Создание слота репликации и пользователя

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

1. Создание пользователя репликации:

CREATE ROLE repl_user WITH REPLICATION LOGIN PASSWORD 'a_strong_password';

2. Создание слота репликации:

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

SELECT pg_create_physical_replication_slot('standby1_slot');

Имя слота не обязательно должно совпадать с synchronous_standby_names. В этом примере standby1 — это application_name, используемый для выбора синхронного резервного сервера, а standby1_slot — это физический слот репликации, используемый для хранения WAL.

D. Перезапуск основного сервера

Примените все изменения конфигурации, перезапустив службу PostgreSQL на основном сервере.

sudo systemctl restart postgresql

Шаг 2: Настройка резервного сервера

Резервный сервер настраивается для потоковой передачи записей WAL с основного сервера с использованием конфигурации восстановления.

A. Базовая резервная копия

Перед запуском потоковой передачи резервному серверу нужна полная копия каталога данных основного сервера. Сначала остановите PostgreSQL на резервном сервере.

sudo systemctl stop postgresql

Сделайте базовую резервную копию с помощью pg_basebackup. При необходимости замените пути и детали подключения:

# Пример использования утилиты pg_basebackup
pg_basebackup -h pg_primary -D /var/lib/postgresql/15/main/ -U repl_user -P -Xs -R -W
  • -D: Целевой каталог данных на резервном сервере.
  • -U: Пользователь репликации.
  • -P: Показать прогресс.
  • -Xs: Включить необходимые файлы WAL во время базовой резервной копии.
  • -R: Автоматически создать файл standby.signal и сгенерировать необходимые настройки подключения в postgresql.auto.conf (или конфигурации восстановления).

B. Настройка postgresql.conf на резервном сервере

На резервном сервере убедитесь, что PostgreSQL знает, как подключиться обратно к основному серверу. Ключевая деталь для синхронной репликации — application_name; он должен соответствовать имени, указанному в synchronous_standby_names.

# --- Требуется на резервном сервере ---
primary_conninfo = 'host=pg_primary port=5432 user=repl_user password=a_strong_password application_name=standby1'
primary_slot_name = 'standby1_slot'
hot_standby = on          # Разрешает запросы на чтение во время восстановления/режима резервного сервера

C. Запуск резервного сервера

Запустите службу PostgreSQL на резервном сервере.

sudo systemctl start postgresql

Шаг 3: Проверка и тестирование синхронной фиксации

После запуска обоих серверов проверьте соединение, а затем протестируйте синхронное поведение.

A. Проверка статуса репликации

Подключитесь к основной базе данных и проверьте представление pg_stat_replication:

SELECT client_addr, application_name, state, sync_state FROM pg_stat_replication;

Вы должны увидеть запись для standby1 с sync_state как sync. Если отображается potential, резервный сервер подключен, но в данный момент не удовлетворяет синхронные фиксации. Если отображается async, PostgreSQL не рассматривает его как синхронный резервный сервер; проверьте написание application_name и synchronous_standby_names.

B. Тестирование синхронной фиксации

Глобальный параметр, который определяет, как долго PostgreSQL ожидает, — это synchronous_commit. Для RPO=0 вы должны использовать значение, которое принудительно включает синхронизацию.

1. Установка глобального поведения

Если вы настроили synchronous_standby_names на основном сервере, как показано в шаге 1, значение по умолчанию synchronous_commit = on ожидает, пока синхронный резервный сервер не сбросит запись WAL на постоянное хранилище. remote_write ожидает, пока резервный сервер не запишет запись WAL в операционную систему, что обычно быстрее, но не так надежно, если хост резервного сервера выйдет из строя до сброса. remote_apply ожидает, пока резервный сервер не воспроизведет транзакцию, что полезно, когда ваше приложение читает с резервного сервера сразу после записи на основной.

Для большинства конфигураций HA с нулевой потерей данных on является практической отправной точкой. Используйте remote_apply только в том случае, если поведение «чтение после записи» на резервном сервере достаточно важно, чтобы оправдать дополнительную задержку.

# В postgresql.conf на основном сервере
synchronous_commit = on

Предупреждение: Синхронная фиксация может заметно увеличить задержку записи по сравнению с асинхронными режимами (off или local). Добавленная задержка возникает из-за сетевых往返ов, скорости записи WAL на резервном сервере и, для remote_apply, скорости воспроизведения.

2. Тестирование в рамках транзакции

Чтобы протестировать транзакционно (без необходимости глобального изменения конфигурации), вы можете установить его для сеанса или транзакции:

-- Подключиться к основному серверу

BEGIN;
SET LOCAL synchronous_commit = on;

INSERT INTO sales (item, amount) VALUES ('Widget A', 100);
-- Этот INSERT подготавливает WAL, который должен быть подтвержден синхронным резервным сервером.

COMMIT;
-- COMMIT завершается успешно только после того, как резервный сервер подтвердит запись WAL.

Если во время фиксации нет настроенного синхронного резервного сервера, фиксация ожидает. В этом суть функции, но это может удивить команды во время сбоя: основной сервер может все еще работать, но записи кажутся замороженными, потому что PostgreSQL ожидает синхронного подтверждения. Не существует общей настройки PostgreSQL, которая автоматически «переключается» на асинхронную фиксацию, когда синхронный резервный сервер исчезает. Если вам нужно такое поведение, ваши инструменты HA должны изменить synchronous_standby_names, или у вас должна быть инструкция для ручного выполнения после принятия решения о том, что доступность важнее, чем нулевая потеря данных.

Более безопасный шаблон с двумя резервными серверами

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

synchronous_standby_names = 'ANY 1 (standby1, standby2)'

С такой настройкой любой из резервных серверов может удовлетворить фиксацию. Если standby1 перезагружается, standby2 все еще может подтверждать записи. Вам все равно нужно отслеживать обе реплики, потому что длительный сбой на одном резервном сервере может привести к тому, что его слот репликации сохранит большой объем WAL, но основной сервер с меньшей вероятностью остановится из-за сбоя одного резервного сервера.

Возможно требование двух подтверждений:

synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'

Это более строгий выбор долговечности. Обычно он зарезервирован для сред с очень низкой задержкой в сети и четкой причиной требовать более одной удаленной копии перед фиксацией. Для многих баз данных приложений «любой из двух близлежащих резервных серверов» является лучшим балансом.

Что отслеживать после включения

Не останавливайтесь на «резервный сервер подключается». Синхронная репликация может технически работать, в то время как пользовательская задержка ухудшается. Следите за этими сигналами после развертывания:

SELECT
    application_name,
    client_addr,
    state,
    sync_state,
    write_lag,
    flush_lag,
    replay_lag
FROM pg_stat_replication;

На основном сервере sync_state = 'sync' говорит вам, какой резервный сервер в настоящее время является синхронным. write_lag, flush_lag и replay_lag помогают объяснить, куда уходит время. Если write_lag высок, подозревайте сеть или давление записи WAL на резервном сервере. Если flush_lag высок, подозревайте хранилище. Если только replay_lag высок, резервный сервер может получать WAL, но применять его медленно из-за ввода-вывода, ЦП, блокировок или долго выполняющихся запросов на резервном сервере.

Также отслеживайте хранение слотов:

SELECT
    slot_name,
    active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;

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

Практический план развертывания

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

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

Во-вторых, установите application_name и убедитесь, что основной сервер видит резервный сервер именно так, как вы ожидаете, в pg_stat_replication. Ошибки в написании здесь распространены, потому что synchronous_standby_names соответствует application_name времени выполнения, а не имени хоста и не слоту.

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

Наконец, запишите решение о сбое. Если синхронный резервный сервер исчез, а основной сервер ожидает фиксации, кому разрешено ослабить synchronous_standby_names? При каких условиях? Как вы проверите, содержит ли старый основной или старый резервный сервер самые новые данные, прежде чем повторно подключать узлы? Это операционные решения, а не просто настройки базы данных.

Лучшие практики для синхронной HA

  • Используйте выделенные резервные серверы: Назначайте в список синхронной репликации только резервные серверы, которые физически близки (низкая задержка) к основному. Высокая задержка будет напрямую влиять на время фиксации.
  • Отслеживайте задержку репликации: Даже в синхронном режиме отслеживайте задержку резервного сервера. Медленный резервный сервер, который все еще технически является «синхронным», но слишком долго обрабатывает WAL, все равно может повлиять на пользовательский опыт.
  • Планируйте компромисс доступности: Заранее решите, может ли оператор временно удалить отсутствующий резервный сервер из synchronous_standby_names во время инцидента.
  • Используйте несколько резервных серверов: Для лучшей доступности записи настройте synchronous_standby_names = 'ANY 1 (standby1, standby2)', чтобы любой из резервных серверов мог подтверждать фиксации.