Patroni Proxy
patroni_proxy — TCP-балансировщик для кластеров PostgreSQL под управлением Patroni. Слушает один или несколько портов, спрашивает у Patroni REST API кто сейчас leader / sync / async, и направляет новые соединения на нужную роль по стратегии least-connections. Не выполняет пулинг соединений, не парсит wire-протокол, не знает что за SQL внутри — это работа pg_doorman, развёрнутого ниже по цепочке.
Что он делает
- Открытие членов кластера через опрос
/clusterс интерваломcluster_update_interval(по умолчанию 3 с) и по запросуGET /update_clusters. - Маршрутизация по ролям. Каждый listen-порт привязан к одной или нескольким ролям (
leader,sync,async,any). Соединения с этого порта попадают на члена, у которого совпадает одна из указанных ролей. - Least-connections. Для портов с несколькими допустимыми членами прокси держит счётчик соединений на каждого члена и отправляет новое соединение туда, где их меньше. Обновление кластера не сбрасывает эти счётчики.
- Отбрасывает реплики со старыми данными.
max_lag_in_bytesper-port исключает членов, у которыхreplication_lag(из/cluster) выше порога. Leader по лагу никогда не исключается. - Пропускает не-running. Допускаются только члены со
state: "running";starting,stopped,crashedи узлы с тегомnoloadbalanceфильтруются.
Операционно важно следующее: при изменении топологии patroni_proxy обновляет таблицу маршрутизации только для новых соединений. Существующие TCP-соединения к ещё живому backend не трогаются. По сравнению с HAProxy + confd, где reload рвёт все соединения через затронутую backend-секцию, это значит, что cluster_update_interval не воюет с долгоживущими транзакциями.
Роли
| Роль | Описание |
|---|---|
leader | Primary / master |
sync | Синхронные standby-реплики |
async | Асинхронные реплики |
any | Любой running-член кластера |
Рекомендуемое развёртывание
graph TD
App1[Приложение A] --> PP(patroni_proxy<br/>TCP-балансировка)
App2[Приложение B] --> PP
App3[Приложение C] --> PP
PP --> D1(pg_doorman<br/>пулинг)
PP --> D2(pg_doorman<br/>пулинг)
PP --> D3(pg_doorman<br/>пулинг)
D1 --> PG1[(PostgreSQL<br/>leader)]
D2 --> PG2[(PostgreSQL<br/>sync-реплика)]
D3 --> PG3[(PostgreSQL<br/>async-реплика)]
- pg_doorman живёт на хостах PostgreSQL. Делает пулинг, ведёт кеш prepared statements и парсит протокол — работа, которой выгодна низкая задержка до локального сокета.
- patroni_proxy живёт рядом с приложением. Маршрутизирует TCP, владеет role-aware failover-решением и не лезет в дела пулера.
Если поток приложения небольшой и одного pg_doorman на кластер достаточно, схема сжимается до одного pg_doorman с включённым fallback через Patroni, а patroni_proxy можно вообще не разворачивать.
Конфигурация
Пример patroni_proxy.yaml:
# Интервал обновления кластера в секундах (по умолчанию: 3)
cluster_update_interval: 3
# Адрес HTTP API для health checks и ручных обновлений (по умолчанию: 127.0.0.1:8009)
listen_address: "127.0.0.1:8009"
clusters:
my_cluster:
# Endpoint'ы Patroni API (несколько — для отказоустойчивости)
hosts:
- "http://192.168.1.1:8008"
- "http://192.168.1.2:8008"
- "http://192.168.1.3:8008"
# Опционально: TLS-конфигурация для Patroni API
# tls:
# ca_cert: "/path/to/ca.crt"
# client_cert: "/path/to/client.crt"
# client_key: "/path/to/client.key"
# skip_verify: false
ports:
# Соединения к primary/master
master:
listen: "0.0.0.0:6432"
roles: ["leader"]
host_port: 5432
# Read-only соединения к репликам
replicas:
listen: "0.0.0.0:6433"
roles: ["sync", "async"]
host_port: 5432
max_lag_in_bytes: 16777216 # 16MB
Параметры конфигурации
| Параметр | По умолчанию | Описание |
|---|---|---|
cluster_update_interval | 3 | Интервал в секундах между опросами Patroni API |
listen_address | 127.0.0.1:8009 | Адрес для HTTP API |
clusters.<name>.hosts | — | Список endpoint'ов Patroni API |
clusters.<name>.tls | — | Опциональная TLS-конфигурация для Patroni API |
clusters.<name>.ports.<name>.listen | — | Адрес для listener этого порта |
clusters.<name>.ports.<name>.roles | — | Список разрешённых ролей |
clusters.<name>.ports.<name>.host_port | — | Порт PostgreSQL на бэкенд-хостах |
clusters.<name>.ports.<name>.max_lag_in_bytes | — | Максимальный лаг репликации (опционально) |
Использование
Запуск patroni_proxy
# Запуск с файлом конфигурации
patroni_proxy /path/to/patroni_proxy.yaml
# С debug-логированием
RUST_LOG=debug patroni_proxy /path/to/patroni_proxy.yaml
Перечитывание конфигурации
Перечитать конфигурацию без перезапуска (добавить или удалить порты, обновить hosts):
kill -HUP $(pidof patroni_proxy)
Ручное обновление кластера
Запустить немедленное обновление всех членов кластера через HTTP API:
curl http://127.0.0.1:8009/update_clusters
HTTP API
| Endpoint | Метод | Описание |
|---|---|---|
/update_clusters | GET | Запустить немедленное обновление всех членов кластера |
/ | GET | Health check (возвращает "OK") |
Сравнение с HAProxy + confd
| Возможность | patroni_proxy | HAProxy + confd |
|---|---|---|
| Сохранение соединений при обновлении | Да | Нет (reload разрывает соединения) |
| Hot-обновления upstream | Нативные | Требуется confd + reload |
| Учёт лага репликации | Встроен | Требуются кастомные проверки |
| Сложность конфигурации | Один YAML | Несколько конфигов |
| Потребление ресурсов | Лёгкий | Процессы HAProxy + confd |
| Маршрутизация по ролям | Нативная | Требуются кастомные шаблоны |
Сборка
# Сборка release-бинарника
cargo build --release --bin patroni_proxy
# Запуск тестов
cargo test --test patroni_proxy_bdd
Диагностика
No backends available
Если видите предупреждения вроде no backends available, проверьте:
- Patroni API доступен с хоста patroni_proxy.
- У членов кластера
state: "running". - Роли в конфигурации совпадают с реальными ролями members.
- Если используется
max_lag_in_bytes-- проверьте текущий лаг реплик.
Соединения разрываются после обновления
С patroni_proxy этого происходить не должно. Если соединения всё-таки разрываются:
- Проверьте, действительно ли бэкенд-хост был удалён из кластера.
- Убедитесь, что порог
max_lag_in_bytesне превышается. - Включите debug-логирование, чтобы увидеть детальный жизненный цикл соединений.