Перейти к содержанию

Репликация и HA

Репликация и отказоустойчивость PostgreSQL

Репликация — создание копий данных на другие серверы. Нужна для двух целей:

  1. Отказоустойчивость (HA) — если primary упал, standby занимает его место за секунды
  2. Масштабирование чтения — read-запросы уходят на реплики, разгружая primary

Физическая vs логическая репликация

Критерий Физическая Логическая
Что передаётся WAL-файлы (побайтово) Логические изменения (INSERT/UPDATE/DELETE)
Гранулярность Весь кластер Отдельные таблицы
Версии PG Одинаковые Могут отличаться
DDL Реплицируется Не реплицируется
Направление Primary → Standby (read-only) Publisher → Subscriber (read-write)
Failover Standby → новый Primary Нет автоматического failover
Лучше для HA и DR Миграция, агрегация, выборочная репликация

Streaming Replication (физическая)

Standby непрерывно получает WAL-записи от primary через TCP-соединение. Задержка — обычно < 1 секунды.

Настройка Primary

INI
# postgresql.conf
wal_level = replica              # или logical (перекрывает replica)
max_wal_senders = 5              # макс потоков репликации
wal_keep_size = 2GB              # сколько WAL хранить для отставших реплик
Text Only
# pg_hba.conf — разрешить репликацию
host    replication    replicator    10.0.0.0/24    scram-sha-256
SQL
-- Создать роль для репликации
CREATE ROLE replicator LOGIN REPLICATION PASSWORD 'strong_password';

Настройка Standby

Bash
# Создать базовую копию primary
pg_basebackup -h primary-host -U replicator -D /var/lib/postgresql/data \
    -Fp -Xs -P -R

# -R создаёт standby.signal и postgresql.auto.conf с primary_conninfo
INI
# postgresql.auto.conf (создаётся автоматически с -R)
primary_conninfo = 'host=primary-host port=5432 user=replicator password=strong_password'

# postgresql.conf
hot_standby = on                 # разрешить read-запросы на standby

Проверка

SQL
-- На primary: слоты репликации
SELECT slot_name, active, restart_lsn
FROM pg_replication_slots;

-- На primary: состояние реплик
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
       sent_lsn - replay_lsn AS lag_bytes
FROM pg_stat_replication;

-- На standby: отставание
SELECT now() - pg_last_xact_replay_timestamp() AS replication_lag;

Синхронная vs асинхронная репликация

Асинхронная (по умолчанию)

Primary не ждёт подтверждения от standby. Быстрее, но при падении primary можно потерять последние транзакции (RPO > 0).

Синхронная

Primary ждёт подтверждения от standby перед COMMIT. Нулевая потеря данных (RPO = 0), но каждый COMMIT замедляется на RTT до реплики.

INI
# postgresql.conf на primary
synchronous_standby_names = 'standby1'

# Варианты:
synchronous_standby_names = 'FIRST 1 (standby1, standby2)'  # хотя бы одна
synchronous_standby_names = 'ANY 2 (standby1, standby2, standby3)'  # любые 2 из 3
Параметр synchronous_commit Что гарантирует
off Нет Ничего (быстро, можно потерять ~0.5s)
local Локальный WAL Данные на primary
on (sync) WAL на primary + standby RPO = 0
remote_write WAL записан в ОС standby Между local и on
remote_apply WAL применён на standby Read-after-write consistency

Синхронная репликация и доступность

Если единственная синхронная реплика упала — primary зависает на каждом COMMIT. Используй ANY 2 (...) с 3+ репликами или мониторь отставание и переключай на async.


WAL Archiving и PITR

WAL-архивирование сохраняет WAL-файлы в внешнее хранилище. Это позволяет восстановить базу на любой момент времени (Point-in-Time Recovery).

INI
# postgresql.conf
archive_mode = on
archive_command = 'cp %p /archive/%f'           # простой вариант
# archive_command = 'aws s3 cp %p s3://backup/%f'  # в S3

Восстановление на момент времени

Bash
# 1. Остановить PostgreSQL
# 2. Восстановить base backup
# 3. Настроить recovery
INI
# postgresql.conf (восстановление)
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2025-03-15 14:30:00'
recovery_target_action = promote

Logical Replication

Логическая репликация передаёт изменения на уровне строк (не WAL-байты). Работает через Publication/Subscription.

Когда нужна

  • Миграция между версиями PostgreSQL (14 → 16)
  • Репликация отдельных таблиц (не всего кластера)
  • Агрегация данных из нескольких БД в одну
  • Подписчик может быть read-write (в отличие от standby)

Настройка

INI
# postgresql.conf на publisher
wal_level = logical
max_replication_slots = 5
max_wal_senders = 5
SQL
-- На publisher: создать публикацию
CREATE PUBLICATION sales_pub FOR TABLE orders, order_items, customers;

-- Или все таблицы:
CREATE PUBLICATION all_pub FOR ALL TABLES;
SQL
-- На subscriber: создать подписку
CREATE SUBSCRIPTION sales_sub
CONNECTION 'host=publisher-host dbname=etl_kitchen user=replicator password=...'
PUBLICATION sales_pub;

Ограничения логической репликации

  • DDL не реплицируется — ALTER TABLE нужно выполнять вручную на обоих серверах
  • Sequences не реплицируются — при failover нужно синхронизировать
  • TRUNCATE реплицируется только в PG 11+
  • Нет поддержки foreign keys между реплицированными и локальными таблицами
  • Большие таблицы: начальная синхронизация может быть медленной
SQL
-- Мониторинг подписки
SELECT subname, received_lsn, latest_end_lsn,
       latest_end_lsn - received_lsn AS lag_bytes
FROM pg_stat_subscription;

Patroni — автоматический failover

Patroni — Python-демон, который управляет кластером PostgreSQL и автоматизирует failover через распределённое хранилище (etcd, ZooKeeper, Consul).

Архитектура

Text Only
          ┌─── etcd cluster ───┐
          │  (consensus store)  │
          └────────┬───────────┘
    ┌──────────────┼──────────────┐
    │              │              │
┌───▼───┐    ┌────▼────┐    ┌───▼───┐
│ node1 │    │  node2  │    │ node3 │
│ PG +  │    │  PG +   │    │ PG +  │
│Patroni│    │ Patroni │    │Patroni│
│(leader)│   │(replica)│    │(replica)
└────────┘   └─────────┘    └───────┘

Как работает failover

  1. Leader Patroni получает lease в etcd (обычно 30 секунд)
  2. Leader обновляет lease каждые 10 секунд
  3. Если leader не обновил lease → etcd сообщает остальным
  4. Реплики проводят выборы (побеждает с наименьшим lag)
  5. Победитель делает pg_ctl promote → становится новым primary
  6. Остальные переключаются на нового primary
YAML
# patroni.yml (минимальный пример)
scope: etl-cluster
name: node1

restapi:
  listen: 0.0.0.0:8008

etcd3:
  hosts: etcd1:2379,etcd2:2379,etcd3:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576  # 1MB — макс lag для участия в выборах
  postgresql:
    parameters:
      wal_level: replica
      max_wal_senders: 5
      max_replication_slots: 5

postgresql:
  listen: 0.0.0.0:5432
  data_dir: /var/lib/postgresql/data
  authentication:
    replication:
      username: replicator
      password: rep_password
    superuser:
      username: postgres
      password: postgres_password

Patroni + HAProxy

HAProxy направляет трафик на текущий leader:

Text Only
client → HAProxy:5432 → leader (read-write)
client → HAProxy:5433 → replicas (read-only, round-robin)

Метрики репликации

Метрика Что смотреть Порог
Replication lag (bytes) sent_lsn - replay_lsn < 1 MB
Replication lag (time) now() - pg_last_xact_replay_timestamp() < 5 sec
WAL sender state pg_stat_replication.state streaming
Replication slots pg_replication_slots.active true
Patroni leader /leader endpoint HTTP 200

Проверь себя


Источники