High Availability и failover: Patroni, etcd, read replicas

↑ Этап 7 · Эксплуатация и безопасность БД

По желанию~7 мин чтенияУровень: middle+

Зачем это на собесе

Отказоустойчивость PostgreSQL — частая тема senior/DevOps-собеседований: как работает автоматический failover и как избежать split-brain.

Цели

  • Доступность (SLA 99.9–99.99%), минимальное RTO и RPO ≈ 0 для критичных данных;
  • автоматический failover без ручных действий;
  • масштабирование чтения репликами.

Встроенной автоматической кластеризации в PostgreSQL нет: есть репликация (streaming), а управление переключением выполняют внешние инструменты.

Инструменты HA для PostgreSQL

ИнструментИдея
Patroniдемон на каждом узле, хранит лидерство в DCS (etcd/Consul/ZooKeeper/Kubernetes API)
repmgrуправление репликацией и failover (с witness)
pg_auto_failoverмонитор + узлы
Stolon, Crunchy PGO, CloudNativePG, Zalando Postgres Operatorоператоры Kubernetes (многие на Patroni)
Управляемые: RDS/Aurora, Cloud SQL, Azure Flexible ServerHA встроена

Patroni

Архитектура:

            ┌───────── etcd кластер (3 узла) — DCS, лидерский ключ с TTL ─────────┐
            │                                                                      │
   Patroni + PG (leader)        Patroni + PG (replica)        Patroni + PG (replica)
            │                                                                      │
       HAProxy / vip-manager / PgBouncer  ← клиенты видят «текущего лидера»
  • Patroni на каждом узле управляет процессом PostgreSQL и регулярно обновляет лидерский ключ в DCS с TTL;
  • при исчезновении лидера ключ истекает, реплики голосуют: побеждает наиболее актуальная реплика (по LSN, с ограничением maximum_lag_on_failover), её promote;
  • старый лидер при возврате демотируется и подстраивается под нового (pg_rewind);
  • конфигурация кластера (postgresql.parameters, synchronous_mode) хранится в DCS и применяется ко всем узлам;
  • REST API (порт 8008): /primary, /replica, /health: используется HAProxy для маршрутизации.
# patroni.yml (фрагмент)
scope: pg-main
name: pg1
etcd3: { hosts: etcd1:2379,etcd2:2379,etcd3:2379 }
restapi: { listen: 0.0.0.0:8008, connect_address: pg1:8008 }
bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    synchronous_mode: true
    postgresql:
      use_pg_rewind: true
      parameters: { wal_level: replica, hot_standby: "on", max_wal_senders: 10 }
postgresql:
  listen: 0.0.0.0:5432
  data_dir: /var/lib/postgresql/data
  authentication:
    replication: { username: replicator, password: "..." }

patronictl list, patronictl switchover, patronictl failover, patronictl reinit.

Маршрутизация:

# haproxy.cfg
listen postgres_primary
  bind *:5000
  option httpchk GET /primary
  http-check expect status 200
  default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
  server pg1 pg1:5432 check port 8008
  server pg2 pg2:5432 check port 8008
  server pg3 pg3:5432 check port 8008
listen postgres_replicas
  bind *:5001
  option httpchk GET /replica
  ...

Порт 5000 — запись (лидер), 5001 — чтение (реплики). Альтернатива: VIP (vip-manager), DNS, multi-host строки подключения Npgsql (Host=pg1,pg2,pg3;Target Session Attributes=primary).

etcd (DCS)

Распределённое хранилище ключ–значение на Raft: нужен кворум (majority) узлов. Кластер из 3 (или 5) узлов; потеря большинства = кластер только читает, Patroni останавливает запись на лидере (демотирует), чтобы избежать split-brain.

Требования: отдельные быстрые диски (низкая fsync-задержка), стабильная сеть, мониторинг (etcdctl endpoint health), бэкапы, не размещать на нагруженных узлах БД без ресурсов.

Синхронная репликация и потери данных

  • synchronous_mode: true — Patroni управляет synchronous_standby_names: гарантирует, что при failover промотируется реплика с полным набором подтверждённых транзакций (RPO = 0);
  • если синхронной реплики нет, запись блокируется (строгий режим) или разрешается с потерей гарантии (synchronous_mode_strict управляет).

Split-brain и fencing

Два узла считают себя лидерами и принимают запись, данные расходятся. Защита:

  • DCS с кворумом: лидерство только с действующим ключом;
  • watchdog (softdog/аппаратный): перезагрузка узла, если Patroni завис;
  • fencing: изоляция старого лидера (остановка PostgreSQL, отзыв VIP, STONITH);
  • синхронная репликация;
  • в приложении: соединения только через прокси, обрывать сессии при смене лидера (on-marked-down shutdown-sessions).

Поведение приложения при failover

  1. Разрыв соединений (30–60 секунд неработоспособности зависят от ttl, loop_wait).
  2. Пул соединений (PgBouncer/Npgsql) должен отбрасывать мёртвые соединения и переподключаться.
  3. Повторы идемпотентных операций с backoff; транзакции в процессе откатываются.
  4. Таймауты подключения и команд короткие.
  5. Учения: регулярно switchover и имитация сбоев (chaos).

Read replicas

  • реплики для чтения, отчётов, бэкапов; лаг мониторится; routing по приоритету (Target Session Attributes=prefer-standby);
  • каскадная репликация и реплики в другом регионе;
  • hot_standby_feedback, max_standby_streaming_delay настраиваются компромиссом между конфликтами и раздуванием;
  • тег nofailover / noloadbalance для специальных реплик (аналитика, бэкап).

Топология

ВариантУзлыКомментарий
Минимум2 PG + witness/etcdнужен третий голос для кворума
Стандарт3 PG (1 лидер + 2 реплики) + 3 etcd (можно на тех же узлах при малом масштабе)типовой кластер
Multi-AZ/DCузлы в 3 зонахустойчивость к отказу зоны; учёт задержки для синхронной репликации
DR-регионstandby clusterасинхронно, ручное/полуавтоматическое переключение

Мониторинг

patroni_* метрики (Prometheus), pg_stat_replication, лаг, состояние etcd, алерты: нет лидера, два лидера, лаг > N, слот без потребителя, timeline изменился.

Kubernetes

Операторы: CloudNativePG (без Patroni, свой менеджер), Zalando, Crunchy. StatefulSet + PVC, PodDisruptionBudget, anti-affinity по зонам, сервисы -rw/-ro, бэкапы в S3 (Barman Cloud/pgBackRest), rolling-обновления с switchover.

Вопросы с ответами