Репликация и HA
Репликация и отказоустойчивость PostgreSQL¶
Репликация — создание копий данных на другие серверы. Нужна для двух целей:
- Отказоустойчивость (HA) — если primary упал, standby занимает его место за секунды
- Масштабирование чтения — 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¶
# postgresql.conf
wal_level = replica # или logical (перекрывает replica)
max_wal_senders = 5 # макс потоков репликации
wal_keep_size = 2GB # сколько WAL хранить для отставших реплик
-- Создать роль для репликации
CREATE ROLE replicator LOGIN REPLICATION PASSWORD 'strong_password';
Настройка Standby¶
# Создать базовую копию 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
# postgresql.auto.conf (создаётся автоматически с -R)
primary_conninfo = 'host=primary-host port=5432 user=replicator password=strong_password'
# postgresql.conf
hot_standby = on # разрешить read-запросы на standby
Проверка¶
-- На 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 до реплики.
# 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).
# postgresql.conf
archive_mode = on
archive_command = 'cp %p /archive/%f' # простой вариант
# archive_command = 'aws s3 cp %p s3://backup/%f' # в S3
Восстановление на момент времени¶
# 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)
Настройка¶
-- На publisher: создать публикацию
CREATE PUBLICATION sales_pub FOR TABLE orders, order_items, customers;
-- Или все таблицы:
CREATE PUBLICATION all_pub FOR ALL TABLES;
-- На 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 между реплицированными и локальными таблицами
- Большие таблицы: начальная синхронизация может быть медленной
-- Мониторинг подписки
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).
Архитектура¶
┌─── etcd cluster ───┐
│ (consensus store) │
└────────┬───────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌───▼───┐ ┌────▼────┐ ┌───▼───┐
│ node1 │ │ node2 │ │ node3 │
│ PG + │ │ PG + │ │ PG + │
│Patroni│ │ Patroni │ │Patroni│
│(leader)│ │(replica)│ │(replica)
└────────┘ └─────────┘ └───────┘
Как работает failover¶
- Leader Patroni получает lease в etcd (обычно 30 секунд)
- Leader обновляет lease каждые 10 секунд
- Если leader не обновил lease → etcd сообщает остальным
- Реплики проводят выборы (побеждает с наименьшим lag)
- Победитель делает
pg_ctl promote→ становится новым primary - Остальные переключаются на нового primary
# 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:
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 |
Проверь себя¶
Источники¶
- PostgreSQL: High Availability — streaming replication и standby
- PostgreSQL: Logical Replication — publication/subscription
- PostgreSQL: WAL Configuration — параметры WAL
- PostgreSQL: Continuous Archiving and PITR — архивирование и восстановление
- Patroni Documentation — автоматический failover