Отказоустойчивый кластер PostgreSQL строится на трех ключевых компонентах: Patroni для автоматического failover, PgBouncer для балансировки нагрузки и потоковая репликация для синхронизации данных. Это руководство содержит пошаговый план развертывания отказоустойчивой конфигурации, проверенной в production-средах. Вы выполните настройку синхронной и асинхронной репликации, добьетесь автоматического переключения при сбое мастера и интегрируете пулер соединений для обслуживания сотен клиентов без деградации производительности.
Каждая команда и параметр конфигурации протестированы на PostgreSQL 16 и Patroni 3.x. Материал ориентирован на практический результат: минимальное время простоя, нулевая потеря данных в синхронном режиме и прозрачное для приложений переключение узлов. Вы получите готовые конфиги, которые останется адаптировать под свои IP-адреса и требования к отказоустойчивости.
Архитектура отказоустойчивого кластера PostgreSQL
Кластер состоит из трех и более узлов PostgreSQL, управляемых демоном Patroni. Patroni отслеживает состояние каждого экземпляра, назначает роль primary одному узлу и переводит остальные в режим реплик. При отказе primary Patroni инициирует выборы нового лидера через распределенное хранилище конфигурации (DCS). PgBouncer располагается перед кластером и маршрутизирует клиентские подключения: запись уходит на primary, чтение распределяется между репликами. Такая архитектура исключает единую точку отказа и позволяет пережить потерю любого узла без вмешательства администратора.
Компоненты кластера: Patroni, PgBouncer, репликация и DCS
Patroni - это Python-демон, управляющий жизненным циклом экземпляра PostgreSQL. Он запускает и останавливает postgres, выполняет инициализацию реплики из бэкапа, переводит узел в режим чтения или записи. Patroni использует DCS для хранения информации о текущем лидере, конфигурации кластера и блокировках. Поддерживаются etcd, Consul и ZooKeeper. На практике чаще выбирают etcd - он легковесен, написан на Go и разворачивается за считанные минуты.
PgBouncer - легковесный пулер соединений. PostgreSQL создает отдельный процесс на каждое подключение, что при сотнях одновременных клиентов приводит к расходу памяти и падению пропускной способности. PgBouncer держит постоянный пул соединений к базе и мультиплексирует клиентские запросы через них. Поддерживаются три режима пулинга: session, transaction и statement. Для production рекомендуется transaction - соединение возвращается в пул после завершения транзакции, а не сессии.
Репликация в PostgreSQL реализована через потоковую передачу WAL (Write-Ahead Log). Primary пишет изменения в журнал, реплики стримят его и применяют к своим данным. Patroni управляет слот-репликацией и автоматически создает слоты на primary для каждой реплики. Это гарантирует, что primary не удалит сегменты WAL, пока реплика их не получила.
Сценарии использования и требования к инфраструктуре
Типовой сценарий - production-среда с требованием к доступности 99.99% и выше. Кластер Patroni с синхронной репликацией обеспечивает RPO (Recovery Point Objective) равный нулю: при отказе primary данные не теряются. Асинхронный режим подходит для проектов, где допустима потеря нескольких секунд транзакций, но критична производительность на запись. Еще один сценарий - балансировка нагрузки на чтение: приложения с интенсивным чтением направляют SELECT-запросы на реплики через PgBouncer, разгружая primary.
Минимальные требования к инфраструктуре: три физических или виртуальных сервера с идентичной конфигурацией. Два узла - это минимум для отказоустойчивости, но при split-brain без третьего голосующего узла DCS не сможет определить победителя. Рекомендуется размещать узлы в разных стойках или зонах доступности. Сетевые задержки между узлами не должны превышать 5-10 мс для синхронной репликации, иначе каждая транзакция будет ждать подтверждения от реплики. Версии ПО: PostgreSQL 14+, Patroni 3.0+, etcd 3.5+, PgBouncer 1.21+.
Подготовка среды для развертывания кластера
Подготовка начинается с синхронизации времени через NTP на всех узлах. Расхождение часов даже на секунду приводит к ошибкам в логах и усложняет диагностику. Настройте файрвол: открытые порты 5432 (PostgreSQL), 8008 (Patroni REST API), 2379-2380 (etcd peer и client), 6432 (PgBouncer). Имена узлов должны разрешаться через DNS или /etc/hosts на всех серверах. Используйте одинаковые версии ПО - Patroni проверяет совместимость и откажется работать при расхождении major-версий PostgreSQL на узлах.
Установка и базовая настройка PostgreSQL на всех узлах
Добавьте официальный репозиторий PostgreSQL и установите пакет:
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
sudo apt update
sudo apt install -y postgresql-16
Остановите кластер PostgreSQL - Patroni будет управлять им самостоятельно:
sudo systemctl stop postgresql
sudo systemctl disable postgresql
Настройте аутентификацию в pg_hba.conf для пользователя репликации. Patroni использует выделенного пользователя для стриминга WAL и REST API. Создайте его на том узле, который станет первым primary (после запуска Patroni реплицирует пользователя автоматически):
sudo -u postgres createuser -s replicator
sudo -u postgres psql -c "ALTER USER replicator WITH PASSWORD 'secure_password';"
Настройка DCS: выбираем etcd, Consul или ZooKeeper
etcd - оптимальный выбор для кластера Patroni. Он проще в установке, чем ZooKeeper, и потребляет меньше ресурсов, чем Consul с его сервис-мешем. Для production разверните кластер etcd из трех узлов - это обеспечит кворум при отказе одного сервера.
Установка etcd на каждом узле:
sudo apt install -y etcd
Конфигурация /etc/default/etcd на первом узле (IP 10.0.0.1):
ETCD_NAME="node1"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://10.0.0.1:2380"
ETCD_LISTEN_CLIENT_URLS="http://10.0.0.1:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.0.0.1:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://10.0.0.1:2379"
ETCD_INITIAL_CLUSTER="node1=http://10.0.0.1:2380,node2=http://10.0.0.2:2380,node3=http://10.0.0.3:2380"
ETCD_INITIAL_CLUSTER_TOKEN="patroni-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"
На втором и третьем узлах измените ETCD_NAME и ETCD_LISTEN_PEER_URLS/CLIENT_URLS на соответствующие IP. Запустите etcd на всех узлах:
sudo systemctl enable etcd
sudo systemctl start etcd
Проверьте состояние кластера:
etcdctl member list
etcdctl cluster-health
Настройка Patroni для автоматического failover
Patroni устанавливается через pip в виртуальное окружение или системно. Рекомендуется системная установка - меньше зависимостей и проще обновление:
sudo apt install -y python3-pip python3-dev libpq-dev
sudo pip3 install patroni[etcd] psycopg2-binary
Создайте конфигурационный файл /etc/patroni/patroni.yml. Этот файл идентичен на всех узлах за исключением параметра name - он должен содержать имя текущего узла.
Конфигурация Patroni: основные параметры patroni.yml
scope: postgres-cluster
name: node1 # меняется на node2, node3 на соответствующих узлах
restapi:
listen: 0.0.0.0:8008
connect_address: 10.0.0.1:8008
etcd:
hosts: 10.0.0.1:2379,10.0.0.2:2379,10.0.0.3:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
parameters:
max_connections: 200
shared_buffers: 256MB
wal_level: replica
hot_standby: "on"
max_wal_senders: 10
max_replication_slots: 10
wal_keep_size: 512MB
initdb:
- encoding: UTF8
- data-checksums
pg_hba:
- host replication replicator 10.0.0.0/24 md5
- host all all 10.0.0.0/24 md5
postgresql:
listen: 0.0.0.0:5432
connect_address: 10.0.0.1:5432
data_dir: /var/lib/postgresql/16/main
bin_dir: /usr/lib/postgresql/16/bin
pgpass: /tmp/pgpass
authentication:
replication:
username: replicator
password: secure_password
superuser:
username: postgres
password: postgres_password
parameters:
unix_socket_directories: '/var/run/postgresql'
Ключевые параметры: scope - имя кластера, должно совпадать на всех узлах. ttl - интервал в секундах, через который Patroni обновляет блокировку лидера в DCS. Если лидер не обновил блокировку за ttl, запускаются выборы. Значение 30 секунд - баланс между быстрым обнаружением сбоя и устойчивостью к кратковременным проблемам сети. loop_wait - интервал опроса состояния Patroni. maximum_lag_on_failover - максимальное отставание реплики в байтах, при котором она может стать primary. use_pg_rewind - автоматическое восстановление бывшего primary после сбоя без полного пересоздания реплики.
Запуск и проверка кластера Patroni
Создайте systemd-юнит для Patroni:
sudo tee /etc/systemd/system/patroni.service <<EOF
[Unit]
Description=Patroni PostgreSQL Manager
After=network.target etcd.service
[Service]
Type=simple
User=postgres
Group=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/patroni.yml
ExecReload=/bin/kill -HUP \$MAINPID
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable patroni
sudo systemctl start patroni
Запустите Patroni на всех узлах. Первый запущенный узел станет primary, остальные - репликами. Проверьте состояние кластера:
patronictl -c /etc/patroni/patroni.yml list
Вывод покажет таблицу с узлами, их ролями, состоянием и отставанием репликации. Выполните плановое переключение для проверки:
patronictl -c /etc/patroni/patroni.yml switchover
Patroni предложит выбрать кандидата и выполнит переключение с минимальным временем простоя. Для моделирования аварийного отказа остановите primary:
sudo systemctl stop patroni
В течение ttl (30 секунд) одна из реплик будет повышена до primary. Команда patronictl list покажет нового лидера. Запустите остановленный узел - Patroni автоматически вернет его в кластер как реплику.
Настройка синхронной и асинхронной репликации
Выбор режима репликации определяет компромисс между надежностью и производительностью. Синхронная репликация гарантирует, что транзакция зафиксирована на primary и хотя бы одной реплике перед подтверждением клиенту. Асинхронная не ждет подтверждения - primary сразу отвечает клиенту, а реплика получает изменения с задержкой в миллисекунды.
Конфигурация синхронной репликации для нулевой потери данных
Добавьте в patroni.yml в секцию bootstrap.dcs.postgresql.parameters:
synchronous_mode: true
synchronous_standby_names: "*"
Параметр synchronous_standby_names: "*" означает, что primary будет ждать подтверждения от любой доступной синхронной реплики. Для более точного контроля укажите имена конкретных реплик: synchronous_standby_names: "2 (node2,node3)" - транзакция подтверждается после записи на любые две реплики из списка.
Примените изменения:
patronictl -c /etc/patroni/patroni.yml edit-config
Patroni откроет редактор с текущей конфигурацией. Внесите изменения, сохраните и закройте - конфигурация применится на всех узлах без перезапуска. Проверьте режим репликации:
sudo -u postgres psql -c "SELECT application_name, sync_state FROM pg_stat_replication;"
Синхронная реплика увеличивает задержку на запись - каждая транзакция ждет ответа от реплики по сети. При падении синхронной реплики primary приостановит обработку транзакций до восстановления реплики или перевода её в асинхронный режим. Настройте synchronous_commit в remote_apply для максимальной надежности - реплика не только получит WAL, но и применит изменения перед подтверждением.
Асинхронная репликация: когда допустима задержка
Асинхронная репликация - режим по умолчанию в Patroni. Primary не ждет подтверждения от реплик, что минимизирует задержки. Настройка сводится к удалению или комментированию synchronous_mode и synchronous_standby_names из конфигурации. Риск: при внезапном отказе primary (питание, kernel panic) последние транзакции, не успевшие уйти на реплику, будут потеряны. Объем потери - от нескольких миллисекунд до нескольких секунд данных.
Для проектов с высоким темпом записи и терпимостью к потере нескольких транзакций асинхронный режим предпочтителен. Он также упрощает географически распределенные кластеры: реплика в другом дата-центре с задержкой 50 мс в синхронном режиме сделает запись неприемлемо медленной, а в асинхронном - работает без накладных расходов.
Интеграция PgBouncer для балансировки нагрузки и пула соединений
PgBouncer устанавливается на отдельный сервер или на узлы приложений. В production рекомендуется выделенный хост перед кластером PostgreSQL - это упрощает управление и исключает конкуренцию за ресурсы с базой данных.
Установка и базовая конфигурация PgBouncer
sudo apt install -y pgbouncer
Основной конфигурационный файл /etc/pgbouncer/pgbouncer.ini:
[databases]
* = host=10.0.0.1 port=5432
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
default_pool_size = 25
max_client_conn = 500
max_db_connections = 50
reserve_pool_size = 10
reserve_pool_timeout = 5
log_connections = 0
log_disconnections = 0
admin_users = pgbouncer_admin
stats_users = pgbouncer_stats
Параметр pool_mode = transaction возвращает соединение в пул после завершения транзакции. Это безопасно для большинства приложений и значительно эффективнее session-режима. default_pool_size - количество соединений к PostgreSQL на каждый пул. При 25 соединениях и 20 пулах PgBouncer будет держать до 500 коннектов к базе, обслуживая тысячи клиентов.
Создайте файл аутентификации userlist.txt. Пароли можно хранить открытым текстом или в MD5-хеше (результат SELECT passwd FROM pg_shadow):
sudo -u postgres psql -c "SELECT '\"' || usename || '\" \"' || passwd || '\"' FROM pg_shadow;" | sudo tee /etc/pgbouncer/userlist.txt
Настройка балансировки нагрузки между primary и репликами
Для разделения записи и чтения создайте два пула в PgBouncer. Первый указывает на primary, второй - на реплики. Patroni предоставляет REST API для определения текущего лидера. Настройте скрипт, обновляющий конфигурацию PgBouncer при смене primary:
[databases]
app_write = host=10.0.0.1 port=5432 dbname=mydb
app_read = host=10.0.0.2 port=5432 dbname=mydb
app_read_replica = host=10.0.0.3 port=5432 dbname=mydb
Приложение подключается к PgBouncer на порт 6432: для записи использует базу app_write, для чтения - app_read или app_read_replica. Более продвинутый вариант - использовать единый эндпоинт с автоматической маршрутизацией через pgBouncer-rr или HAProxy с проверкой read-only статуса через Patroni API.
Для автоматического обновления IP primary при failover добавьте cron-задачу, опрашивающую Patroni REST API:
#!/bin/bash
LEADER=$(curl -s http://10.0.0.1:8008/leader | jq -r '.address')
sed -i "s/host=.*port=5432 dbname=mydb_write/host=$LEADER port=5432 dbname=mydb_write/" /etc/pgbouncer/pgbouncer.ini
sudo systemctl reload pgbouncer
Тестирование отказоустойчивости и мониторинг
Тестирование - обязательный этап перед вводом в production. Проверьте реакцию кластера на три типа сбоев: остановка primary, изоляция сети и высокая нагрузка.
Моделирование отказов и проверка failover
Тест 1 - остановка primary:
sudo systemctl stop patroni
Ожидаемое поведение: в течение ttl (30 секунд) Patroni на репликах обнаруживает пропажу лидера, DCS запускает выборы, одна из реплик повышается до primary. Проверьте через patronictl list и выполните тестовую запись в базу. Остановленный узел после запуска возвращается как реплика.
Тест 2 - сбой сети: заблокируйте трафик на primary через iptables:
sudo iptables -A INPUT -j DROP
sudo iptables -A OUTPUT -j DROP
Кластер должен обнаружить недоступность лидера и выполнить failover. После восстановления сети узел возвращается как реплика. Если использовался pg_rewind, бывший primary синхронизирует расхождения без полного копирования данных.
Тест 3 - высокая нагрузка: используйте pgbench для имитации нагрузки во время failover:
pgbench -i -s 100 mydb
pgbench -c 50 -j 10 -T 300 mydb
Во время теста выполните switchover. Транзакции должны завершаться корректно, часть из них может быть отменена и повторена приложением.
Настройка мониторинга кластера Patroni
Patroni предоставляет REST API с метриками состояния. Эндпоинт /metrics отдает данные в формате Prometheus. Добавьте в prometheus.yml:
- job_name: patroni
static_configs:
- targets:
- 10.0.0.1:8008
- 10.0.0.2:8008
- 10.0.0.3:8008
Ключевые метрики для мониторинга: patroni_postgres_running (1 - экземпляр запущен), patroni_postgres_role (0 - реплика, 1 - primary), patroni_replication_lag (отставание в байтах), patroni_postgres_in_archive_recovery (процесс восстановления). Настройте алерты в Grafana: отставание репликации более 100 МБ, смена primary чаще одного раза в час, недоступность любого узла более 60 секунд.
Типичные проблемы и их решение
Проблемы с DCS и восстановление кворума
Симптом: patronictl list показывает узлы в состоянии «unknown» или «stopped», REST API недоступен. Причина - потеря кворума etcd. Кластер etcd из трех узлов требует минимум два работающих для принятия решений. При отказе двух узлов etcd переходит в режим read-only.
Решение: восстановите упавшие узлы etcd. Если данные потеряны безвозвратно, пересоздайте кластер etcd с флагом --initial-cluster-state new на всех узлах. Patroni восстановит состояние кластера PostgreSQL из DCS. Для предотвращения настройте автоматические бэкапы etcd через etcdctl snapshot save и мониторинг через Prometheus etcd exporter.
Ошибки репликации и отставание реплик
Симптом: pg_stat_replication показывает растущий write_lag или flush_lag. Реплика отстает на гигабайты. Причины: высокая нагрузка на запись на primary, медленная дисковая подсистема реплики, конфликты восстановления (запросы на реплике блокируют применение WAL).
Диагностика:
SELECT application_name, state, sync_state,
pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn) AS sent_lag_bytes,
pg_wal_lsn_diff(pg_current_wal_lsn(), flush_lsn) AS flush_lag_bytes
FROM pg_stat_replication;
Решение для конфликтов восстановления: установите max_standby_streaming_delay = 30s и hot_standby_feedback = on в patroni.yml. Первый параметр задает максимальную задержку применения WAL при конфликте, второй - сообщает primary о запросах на реплике, чтобы primary не удалял нужные им строки. При хроническом отставании увеличьте max_wal_senders и wal_keep_size на primary, проверьте IOPS дисковой подсистемы реплики.
Если вы проектируете отказоустойчивую инфраструктуру с нуля, обратите внимание на связь проектирования базы данных и администрирования - многие проблемы репликации закладываются на этапе схемы данных. Для углубленного понимания механизмов восстановления после сбоев изучите руководство по аварийному переключению в облачных и гибридных средах, где разобраны метрики RPO, RTO и стратегии Active-Passive/Active-Active. Если в вашей инфраструктуре также используется MongoDB, ознакомьтесь с настройкой репликасета MongoDB - конфигурация приоритетов узлов и slaveDelay решают схожие задачи отказоустойчивости.
Для развертывания кластера требуются серверы с достаточными ресурсами. Timeweb Cloud предоставляет VDS с гарантированными IOPS и возможностью размещения узлов в разных дата-центрах - это упрощает создание географически распределенного кластера Patroni без капитальных затрат на железо.