PostgreSQL
High Availability: Patroni, etcd, failover
Black Friday 2019. Shopify обрабатывает $2.9 миллиарда за 24 часа. Primary PostgreSQL сервер падает в 14:03. В 14:04:30 - Patroni завершил failover. 90 секунд, ноль потерянных транзакций. Без HA - часы ручного восстановления, миллионы потерянной выручки. Разница между катастрофой и инцидентом - Patroni + etcd + HAProxy, настроенные заранее.
- **Zalando** - авторы Patroni, используют для 500+ PostgreSQL кластеров: автоматический failover < 30 секунд, scheduled daily switchover для обновлений конфигурации без downtime
- **GitLab.com** - Patroni + Consul: 99.99% uptime, primary + 2 standby, все failover 2020-2024 без потери данных, тонкий PgBouncer слой перед каждым кластером
- **Timescale Cloud** - Patroni под капотом каждой БД: автоматический failover, transparent PgBouncer, HAProxy read/write split как managed service
Концепция High Availability
**High Availability (HA) для PostgreSQL - это автоматическое восстановление сервиса при отказе primary без ручного вмешательства.** Три компонента: обнаружение отказа (failover detection), выбор нового лидера (leader election), перенаправление трафика (traffic routing). PostgreSQL сам по себе не обеспечивает автоматический failover - нужны внешние инструменты.
**RTO и RPO** - ключевые метрики HA. RTO (Recovery Time Objective) - максимальное время восстановления. RPO (Recovery Point Objective) - максимальная допустимая потеря данных. Patroni с async replication: RTO ~30 сек, RPO > 0. С sync replication: RPO = 0, RTO ~30 сек, но выше latency.
Финансовое приложение требует RPO = 0 (нулевая потеря данных) и RTO < 1 минуты. Что нужно включить?
Patroni + etcd: лидер-электион
**Patroni** - Python-агент для управления PostgreSQL кластером. Запускается на каждом узле, общается с DCS (Distributed Configuration Store - etcd/Consul/ZooKeeper). DCS хранит состояние: кто leader, конфигурацию кластера, TTL блокировки лидера. Если лидер не обновляет TTL-блокировку - значит, он упал, и начинается выбор нового.
TTL в Patroni = 30 секунд. Primary завис (не отвечает, но процесс жив). Через сколько секунд начнётся failover?
Автоматический Failover
**Failover через Patroni:** истёк TTL в etcd → standby с наименьшим lag захватывает блокировку → новый primary → Patroni перенастраивает остальные standby → pg_rewind синхронизирует старый primary при возвращении. Весь процесс: 30-60 секунд.
**pg_rewind** (use_pg_rewind: true) синхронизирует только изменённые страницы - намного быстрее полного pg_basebackup. Предотвращает split-brain: узел без DCS-блокировки останавливает PostgreSQL (fencing).
Старый primary вернулся после failover. Patroni обнаруживает новый лидер в etcd. Что происходит?
PgBouncer: connection pooling в HA
**PgBouncer в HA-стеке** ограничивает число соединений к PostgreSQL и буферизует запросы во время failover. При failover PgBouncer паузирует клиентские запросы на несколько секунд пока переключается на новый primary - клиенты видят задержку, но не ошибку.
| Pool mode | Соединение с PG | Для чего |
|---|---|---|
| session | На всё время сессии клиента | SET, PREPARE, advisory locks |
| transaction | Только во время транзакции | Большинство приложений (рекомендуется) |
| statement | На одну SQL-команду | Только без multi-statement транзакций |
Приложение использует `SET search_path = myschema` в начале сессии. PgBouncer в transaction pool mode. Что произойдёт?
HAProxy: маршрутизация к primary/standby
**HAProxy маршрутизирует запросы к правильному PostgreSQL узлу.** Patroni предоставляет REST API (порт 8008): `/primary` возвращает 200 только для текущего primary, `/replica` - для standby. HAProxy опрашивает эти эндпоинты и автоматически обновляет routing при failover.
**Два порта для двух ролей** - стандартная схема: :5000 для write (primary), :5001 для read (любой standby). Приложение держит два connection string. При failover оба обновляются автоматически через HAProxy health checks.
HA для PostgreSQL требует только streaming replication
Streaming replication - только транспорт данных. Полный HA-стек: Patroni (cluster management + failover), etcd (distributed consensus без split-brain), PgBouncer (connection pooling), HAProxy (traffic routing)
Streaming replication не переключает трафик автоматически, не предотвращает split-brain, не управляет жизненным циклом соединений клиентов. При ручном failover без этих компонентов: 10-30 минут работы DBA, ошибки у всех клиентов, риск потери данных при неправильном promote
HAProxy опрашивает Patroni REST API каждые 3 секунды (inter=3s, fall=3). После падения primary - через сколько секунд максимум HAProxy перестанет посылать трафик на него?
Итоги
- **HA-стек**: Patroni (cluster manager) + etcd (consensus) + PgBouncer (pooling) + HAProxy (routing). Streaming replication - только транспорт
- **Failover за ~30-60 сек**: TTL истекает в etcd → election → promote → pg_rewind для старого primary → обновление routing через HAProxy
- **Fencing через DCS**: узел без блокировки в etcd останавливает PostgreSQL, предотвращая split-brain
- **PgBouncer transaction mode**: оптимален для большинства приложений, но ломает сессионные SET и prepared statements
- **Patroni REST API** `/primary` и `/replica` - механизм health check для HAProxy и любого load balancer
Связанные темы
HA строится поверх нескольких ключевых механизмов:
- Streaming Replication — Физический транспорт для HA - WAL-поток между primary и standby, которым управляет Patroni
- Connection Pooling (tuning) — Детальная настройка PgBouncer - режимы пула, max_client_conn, prepared statements в HA-контексте
- Backup и восстановление — HA защищает от падения сервера. Backup - от потери данных из-за ошибок данных. Оба обязательны в production
Вопросы для размышления
- Patroni TTL = 30 секунд, maximum_lag_on_failover = 1 МБ. Standby-1 отстаёт на 2 МБ, Standby-2 - на 0.5 МБ. Primary падает. Кто станет новым primary и почему?
- Приложение использует PREPARE statements через PgBouncer в transaction mode. После нескольких часов - ошибки 'prepared statement does not exist'. Что происходит и как исправить?
- Третий datacenter добавлен для etcd. DC1 отрезан от сети (network partition), но PostgreSQL primary там продолжает работать. Как Patroni реагирует? Есть ли риск split-brain?