Классическая связка HA для PostgreSQL — Patroni + etcd. Работает, проверена временем, но у неё есть свойство, которое раздражает всех, кто её эксплуатировал: вы добавили ещё один распределённый кластер, чтобы обслуживать свой распределённый кластер. etcd нужно разворачивать, мониторить, бэкапить, обновлять и чинить — а когда он деградирует, Patroni теряет способность принимать решения.
GFM (Gorgona Failover Manager) предлагает убрать это звено: узлы договариваются напрямую через P2P-меш, без etcd, Consul и ZooKeeper.
Факты ниже — из README проекта и репозитория psqlmaster/gorgona (BSD-3-Clause). Разбор мой. Проект молодой — выводы делайте с поправкой на это, см. раздел про ограничения.
Что это такое
Два слоя:
- Gorgona — децентрализованный P2P-движок для доставки сообщений и удалённого выполнения. Написан на чистом C11, из зависимостей — только OpenSSL, никаких баз данных. Есть режим smart mesh с автообнаружением топологии через gossip-протокол обмена пирами (PEX).
- GFM — плагин поверх него, который управляет отказоустойчивостью PostgreSQL, используя меш как control plane.
Шифрование двухслойное: команды защищены end-to-end через RSA-OAEP для обмена ключами и AES-256-GCM для содержимого, а служебный трафик управления — кластерным PSK. Сервер хранит только зашифрованные сообщения и не может прочитать содержимое. От повторов защищает фильтр по возрасту пакета (окно 120 секунд) и дедупликация по скользящему окну из 50 пакетов.
flowchart LR
subgraph P["Patroni + etcd"]
direction TB
N1["Postgres+Patroni"] --> E[("etcd<br/>внешний DCS")]
N2["Postgres+Patroni"] --> E
N3["Postgres+Patroni"] --> E
end
subgraph G["GFM: P2P-меш"]
direction TB
G1["Postgres+GFM"] <--> G2["Postgres+GFM"]
G2 <--> G3["Postgres+GFM"]
G1 <--> G3
end
Слева состояние живёт во внешнем кластере, справа — распределено между самими узлами.
Как выбирается лидер
Здесь принципиальное отличие от Patroni, где лидерство — это аренда ключа в DCS.
Критерий выбора — LSN. Узел с наибольшим Log Sequence Number становится кандидатом в лидеры: побеждает тот, кто дальше всех продвинулся по WAL, то есть потерял меньше всех данных. При равных LSN тай-брейк детерминированный — по алфавитному порядку имён хостов, чтобы результат был однозначным.
Порог на участие. Чтобы инициировать выборы или стать кандидатом, узлу нужна TCP-связность минимум с (N/2)+1 узлами из списка кворума.
Защита от split-brain: самофенсинг
Самая интересная часть. Лидер каждые 5 секунд проверяет доступность пиров. Как только он теряет кворумную связность — он немедленно останавливает свой PostgreSQL. В документации это прямо называется «suicide».
Логика жёсткая и правильная: изолированный мастер, который продолжает принимать запись, — это гарантированное расхождение данных. Проще убить себя, чем разгребать split-brain.
Обратная сторона очевидна: сетевая проблема приводит к остановке базы, даже если сам PostgreSQL полностью здоров. Это осознанный размен доступности на консистентность, и о нём нужно знать заранее.
Witness-узлы. Чтобы набрать кворум без разворачивания лишних баз, поддерживаются узлы-свидетели: они отличаются портами (кворумный порт задан, а PostgreSQL — нет), дают голос, но не хранят данные. Полезно для схемы «два ЦОДа + арбитр».
Failover и самовосстановление
Сценарий отказа лидера:
- Здоровые узлы фиксируют пропажу heartbeat после
max_missing_heartbeatsциклов. - Узлы, имеющие кворумную связность, запускают выборы.
- Узел с наибольшим LSN становится лидером, остальные — standby.
- Упавший узел при возвращении автоматически уходит в rebuild.
Восстановление делает скрипт gfm_rebuild.sh: сначала пробует pg_rewind (откатывает разошедшийся timeline с минимальным трафиком), а если локальная база пуста или повреждена — падает обратно на полный pg_basebackup.
Требования к PostgreSQL для этого:
wal_level = replica
wal_log_hints = onwal_log_hints обязателен для работы pg_rewind — без него восстановление всегда будет идти долгим путём через полную копию.
Конфигурация и запуск
Единственный источник правды — gfm.conf. Основные секции:
| Секция | Ключевые параметры |
|---|---|
[cluster] | my_pub_hash, admin_pub_hash, quorum_total_nodes, cluster_id, quorum_nodes (в формате IP:PORT) |
[postgresql] | service_name, pg_version, pg_instance_name, port |
[timings] | heartbeat_interval, max_missing_heartbeats, monitor_interval, TTL для heartbeat и событий |
[paths] | base_dir, gorgona_bin, psql_bin, pg_ctl_bin, rebuild_script |
Установка:
chmod +x install.sh
./install.sh ./gfm_prod_5432.confУдаление — с возможностью сохранить данные:
./uninstall.sh ./gfm.conf # снести всё
./uninstall.sh ./gfm.conf --exclude-db # оставить данные PostgreSQLНаблюдение за кластером:
journalctl -u gfm@CLUSTER_ID -f # логи демона
cat /etc/gorgona/status_CLUSTER_ID.json # состояние кластера
tail -f /var/log/gorgona/rebuild_CLUSTER_ID.log # ход восстановленияНа одной машине можно держать несколько независимых кластеров: они разделяются по cluster_id, шаблонам systemd и per-cluster блокировкам через fcntl.flock.
Сборка самого Gorgona:
sudo apt install -y libssl-dev git gcc make
git clone https://github.com/psqlmaster/gorgona.git
cd gorgona && make clean && makeПоддерживаются Linux (Debian, Fedora, CentOS, Red OS), macOS и даже OpenWrt (x86_64, aarch64).
Чем отличается от Patroni + etcd
| Patroni + etcd | GFM | |
|---|---|---|
| Хранилище состояния | Внешний DCS (etcd/Consul/ZK) | P2P-меш между узлами |
| Компонентов обслуживать | PostgreSQL + Patroni + кластер DCS | PostgreSQL + GFM |
| Выбор лидера | Аренда ключа в DCS | По наибольшему LSN |
| Защита от split-brain | Потеря аренды → демоут | Потеря кворума → остановка PostgreSQL |
| Реализация | Python | C (Gorgona) + демон GFM |
| Зрелость | Годы в проде, огромное сообщество | Молодой проект |
Главный выигрыш — минус целый распределённый кластер из эксплуатации: нечего бэкапить, обновлять и чинить отдельно. Выбор лидера по LSN тоже приятен своей прямотой: побеждает тот, у кого свежее данные, без прослойки в виде аренды ключа.
Честные ограничения
Прежде чем нести это в прод:
- Зрелость. Patroni отлаживался годами в тысячах инсталляций, вокруг него — экосистема, ответы на любой вопрос и предсказуемое поведение в экзотических сценариях. GFM такого багажа пока не имеет. Это не приговор, но это риск, который вы берёте на себя.
- Самофенсинг режет доступность. Остановка PostgreSQL при потере кворума — верное решение для консистентности, но сетевой флап превращается в простой базы. Сеть между узлами должна быть надёжной, а таймауты — подобранными под вашу инфраструктуру.
- Нужен арбитр для чётных схем. Два узла без witness — это не HA: любой сетевой разрыв убивает кворум с обеих сторон.
- Экосистема. Patroni умеет работать с pgBouncer, интегрируется с Kubernetes-операторами, имеет готовые дашборды и REST API, к которому привязаны чужие инструменты. Здесь всё это придётся проверять и, возможно, строить самому.
- Обязательный
wal_log_hints— если он выключен, каждое восстановление будет полнымpg_basebackup, что на больших базах болезненно.
Кому стоит посмотреть
Разумные сценарии:
- On-prem и bare-metal, где содержать отдельный кластер etcd ради трёх нод PostgreSQL — явный перебор.
- Edge и небольшие площадки с ограниченными ресурсами: зависимость только от OpenSSL и поддержка OpenWrt здесь выглядят уместно.
- Команды, которых утомила эксплуатация DCS и которые готовы на эксперимент вне критичного контура.
Где я бы пока остался на Patroni: критичный прод с жёстким SLA, инсталляции в Kubernetes (там свои операторы вроде CloudNativePG, закрывающие ту же задачу иначе) и команды, которым важна предсказуемость и большое сообщество.
Разумный путь — поднять GFM на тестовом стенде, устроить учения (убить лидера, разорвать сеть, проверить rebuild) и сравнить поведение со своим текущим решением. Идея убрать внешний DCS хороша; проверять её лучше на своём железе, а не по статье.
Ссылки: репозиторий · README плагина GFM · статья автора на Хабре