· 17 мин чтения

Patroni + PostgreSQL: отказоустойчивый кластер, который пережил отключение электричества

Семёнов Евгений Сергеевич, директор АйТи Фреш. 15+ лет в инфраструктуре. PostgreSQL в 2025 году — основная БД для многих корпоративных приложений: 1С, Битрикс24, самописные CRM, ЭДО. Но одиночный PostgreSQL — это single point of failure: упал диск или сервер — и бизнес встал на 2–6 часов восстановления из бэкапа. Patroni закрывает этот вопрос. В статье — рабочая схема, которую мы выкатили трём клиентам за последний год, с разбором реальных проблем и того, что помогло их решить.

Зачем Patroni

Сам PostgreSQL поддерживает потоковую репликацию и базовый hot standby — реплика готова принять трафик при падении мастера. Но переключение вручную: обнаружить падение, промоутить реплику, перевести клиентов. Это 10–20 минут при идеальных обстоятельствах и полное отсутствие автоматики ночью в выходные.

Patroni добавляет:

Архитектура минимального кластера

Минимальная production-конфигурация, которую я ставлю клиентам:

Характеристики узла БД для офиса до 50 пользователей 1С: 8 vCPU, 32 ГБ RAM, 500 ГБ NVMe. У нас на стенде — Dell Xeon Platinum 8280, 40G Mellanox между узлами, что даёт repl-lag ниже 5 мс при пиковой нагрузке.

Установка etcd-кластера

etcd — распределённое хранилище ключ-значение, используется Patroni для выбора лидера. Ставим на все 3 узла (Ubuntu 22.04):

# На всех узлах
apt install -y etcd-server etcd-client

# /etc/default/etcd на pg01 (аналогично на pg02/pg03, меняйте IP и имя)
ETCD_NAME="pg01"
ETCD_DATA_DIR="/var/lib/etcd/default"
ETCD_LISTEN_PEER_URLS="http://10.10.50.11:2380"
ETCD_LISTEN_CLIENT_URLS="http://10.10.50.11:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.10.50.11:2380"
ETCD_INITIAL_CLUSTER="pg01=http://10.10.50.11:2380,pg02=http://10.10.50.12:2380,pg03=http://10.10.50.13:2380"
ETCD_INITIAL_CLUSTER_TOKEN="pg-cluster-prod"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_ADVERTISE_CLIENT_URLS="http://10.10.50.11:2379"

systemctl enable --now etcd

# Проверка
etcdctl member list
etcdctl endpoint health --endpoints=http://pg01:2379,http://pg02:2379,http://pg03:2379

Установка PostgreSQL и Patroni

На всех узлах:

apt install -y postgresql-16 postgresql-server-dev-16 python3-pip
systemctl disable --now postgresql
rm -rf /var/lib/postgresql/16/main

pip3 install psycopg2-binary patroni[etcd]
# или через apt: apt install patroni

Конфиг Patroni для pg01 — /etc/patroni/patroni.yml:

scope: pg-cluster-prod
namespace: /service/
name: pg01

restapi:
  listen: 10.10.50.11:8008
  connect_address: 10.10.50.11:8008

etcd:
  hosts: 10.10.50.11:2379,10.10.50.12:2379,10.10.50.13:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    synchronous_mode: true
    postgresql:
      use_pg_rewind: true
      parameters:
        max_connections: 500
        shared_buffers: 8GB
        effective_cache_size: 24GB
        work_mem: 16MB
        maintenance_work_mem: 1GB
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        max_replication_slots: 10
        wal_log_hints: "on"
        archive_mode: "on"
        archive_command: "test ! -f /var/lib/postgresql/archive/%f && cp %p /var/lib/postgresql/archive/%f"

  initdb:
    - encoding: UTF8
    - locale: ru_RU.UTF-8
    - data-checksums

  pg_hba:
    - host replication replicator 10.10.50.0/24 scram-sha-256
    - host all all 10.10.0.0/16 scram-sha-256

  users:
    admin:
      password: ********
      options:
        - createrole
        - createdb
    replicator:
      password: ********
      options:
        - replication

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 10.10.50.11:5432
  data_dir: /var/lib/postgresql/16/main
  bin_dir: /usr/lib/postgresql/16/bin
  authentication:
    replication:
      username: replicator
      password: ********
    superuser:
      username: postgres
      password: ********

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false
  nosync: false

На pg02 и pg03 — аналогичный файл, меняется только name, listen и connect_address.

Первый запуск

# Systemd-юнит
cat > /etc/systemd/system/patroni.service << 'EOF'
[Unit]
Description=Patroni
After=syslog.target network.target etcd.service
[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml
KillMode=process
TimeoutSec=30
Restart=no
[Install]
WantedBy=multi-user.target
EOF

chown -R postgres:postgres /etc/patroni /var/lib/postgresql/16
systemctl daemon-reload
systemctl enable --now patroni

# Проверка состояния
patronictl -c /etc/patroni/patroni.yml list

Запускаем сначала pg01 — он становится лидером. Потом pg02 и pg03 — они подтягивают базу как реплики.

HAProxy для маршрутизации

HAProxy на двух отдельных виртуалках (hap01, hap02) с общим VIP через keepalived. Конфиг /etc/haproxy/haproxy.cfg:

global
  maxconn 500

defaults
  mode tcp
  timeout connect 10s
  timeout client 1d
  timeout server 1d

listen pg_write
  bind *:5432
  option httpchk GET /leader
  http-check expect status 200
  default-server check port 8008 inter 3s rise 2 fall 3 on-marked-down shutdown-sessions
  server pg01 10.10.50.11:5432
  server pg02 10.10.50.12:5432
  server pg03 10.10.50.13:5432

listen pg_read
  bind *:5433
  balance roundrobin
  option httpchk GET /replica
  http-check expect status 200
  default-server check port 8008 inter 3s rise 2 fall 3
  server pg01 10.10.50.11:5432
  server pg02 10.10.50.12:5432
  server pg03 10.10.50.13:5432

Порт 5432 на VIP всегда ведёт на текущий мастер, 5433 — балансирует между репликами для read-only запросов. Приложения подключаются к VIP:5432 для записи и VIP:5433 для read-only отчётов.

Тестирование failover

Всегда перед продакшеном нужно погонять failover-сценарии:

СценарийОжидаемое поведение
Ручной switchoverМастер переходит на другой узел за 5–10 секунд, старый становится репликой
Kill -9 PostgreSQL на мастереPatroni перезапускает процесс, мастер не меняется
Systemctl stop patroni на мастереНовый мастер выбирается за 15–30 секунд, HAProxy переключает трафик
Жёсткое выключение хоста мастераТо же, что выше, но без graceful shutdown
Network partition (1 узел)Изолированный узел становится репликой в read-only
Network partition (2 узла)Одиночный узел останавливает PostgreSQL — нет кворума
# Тесты
patronictl -c /etc/patroni/patroni.yml switchover --master pg01 --candidate pg02
patronictl -c /etc/patroni/patroni.yml failover --candidate pg03
patronictl -c /etc/patroni/patroni.yml list
patronictl -c /etc/patroni/patroni.yml history

Мини-кейс: HA для 1С в оптовой торговле

В марте 2026 года клиент — оптовый поставщик электроники, 95 пользователей 1С ERP, база 680 ГБ. Требование — минимальный простой при сбоях, SLA 99,9% в часы работы (8–20). База на одном PostgreSQL 14 падала раз в квартал из-за проблем с дисками, каждый простой — 2–3 часа работы ста пользователей.

Решение: трёхузловой кластер Patroni на Dell R650 (dual Xeon Gold, 128 ГБ, 1.5 ТБ NVMe) в дата-центре МТС, между узлами — 40G Mellanox. Миграция:

Итог за 3 месяца: один незапланированный failover (упал диск на мастере в среду в 14:30), переключение заняло 22 секунды, пользователи заметили только повторную попытку проведения документа. Стоимость проекта — 420 000 руб. за внедрение + 28 000 руб./мес. сопровождение.

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

По умолчанию Patroni работает в асинхронном режиме — failover может потерять несколько последних транзакций. Для финансовых систем это недопустимо. Включаем sync:

bootstrap:
  dcs:
    synchronous_mode: true
    synchronous_mode_strict: true
    synchronous_node_count: 1

В синхронном режиме мастер ждёт подтверждения от указанного числа реплик перед коммитом. В обмен на гарантию сохранности — небольшое снижение latency, обычно на 2–5 мс в LAN. synchronous_mode_strict запрещает коммит вовсе, если нет синхронной реплики.

Мониторинг кластера

Я всегда ставлю комбо prometheus + postgres_exporter + patroni_exporter + grafana:

# Prometheus scrape configs
- job_name: 'patroni'
  static_configs:
    - targets: ['10.10.50.11:8008','10.10.50.12:8008','10.10.50.13:8008']
  metrics_path: /metrics

- job_name: 'postgres'
  static_configs:
    - targets: ['10.10.50.11:9187','10.10.50.12:9187','10.10.50.13:9187']

Ключевые метрики для мониторинга: pg_up, pg_replication_lag, patroni_cluster_unlocked, pg_locks_count, pg_database_size_bytes, pg_stat_activity_count.

Частые ошибки при внедрении Patroni

Построим PostgreSQL HA-кластер под ключ

Внедрение Patroni-кластеров PostgreSQL для 1С, Битрикс24, самописных систем. Проектирование, железо, настройка, нагрузочное тестирование, миграция данных, мониторинг, сопровождение. 15+ лет опыта с БД, SLA 99.9%.

Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш

FAQ — частые вопросы про Patroni

Что такое Patroni?
Patroni — менеджер для PostgreSQL, написанный Zalando на Python. Автоматизирует управление репликацией, выбирает лидера через распределённый консенсус (etcd/Consul/ZooKeeper), выполняет автоматический failover и восстановление реплик.
Сколько узлов нужно для кластера?
Минимум 3 узла для etcd (для кворума) и 2+ узла PostgreSQL для репликации. Типовая конфигурация — 3 хоста, на каждом свой etcd и свой PostgreSQL. Для production лучше 3 PostgreSQL + 3 etcd на разных хостах.
Какая задержка failover?
При правильной настройке Patroni переключает мастер за 10–30 секунд от момента падения. Timeout можно уменьшить, но ниже 5 секунд — риск ложных срабатываний из-за сетевых задержек.
Нужен ли HAProxy для Patroni?
Необязательно, но рекомендуется. HAProxy проверяет роль каждого узла через HTTP API Patroni и направляет трафик на текущего лидера. Альтернативы — Keepalived с VIP, pgbouncer с настройкой, PgCat.
Можно ли использовать Patroni без etcd?
Да, Patroni поддерживает Consul, ZooKeeper, Kubernetes API и raw storage. Но etcd — самый простой и популярный вариант для bare-metal и VM-инсталляций, имеет минимальный overhead.

Подпишитесь на рассылку ITfresh

Раз в неделю — практические гайды для руководителя IT и сисадмина: безопасность, 1С, миграции, резервные копии, лайфхаки из реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.