Настройка HAProxy для автоматического определения ролей экземпляров PostgreSQL и защиты от split brain

HAProxy должен выполнять проверки, чтобы определить роль экземпляров PostgreSQL: мастер или реплика, чтобы направлять соединения на подходящие сервера.
Описываются способы настройки HAProxy для обнаружения переключения ролей экземпляров:
1) через Patroni REST API
2) через параметр in_hot_standby, передаваемый сервером сразу после аутентификации клиента
3) запросом select pg_is_in_recovery();
Два последних способа используются, если Patroniпоставлен на паузу или не используется.

Проверка через Patroni REST API
Для проверок HAProxy может обращаться на порт Patroni REST API по протоколу HTTP или на порты экземпляров PostgreSQL. Проверки инициирует HAProxy, экземпляры не уведомляют HAProxy о смене своих ролей. Эти проверки должны повторяться через короткие интервалы времени, чтобы HAProxy мог быстро реагировать на смену роли.
В конфигурации HAProxy создаётся две секции listen для мастера и реплик:
listen primary
bind *:8001
option httpchk OPTIONS /primary
http-check expect status 200
default-server inter 2s fastinter 100ms downinter 2s fall 3 rise 2 observe layer4 error-limit 1 on-error fastinter init-state down on-marked-down shutdown-sessions
server wtantor1 127.0.0.1:5432 maxconn 100 check addr 127.0.1.1 port 8008
server wtantor2 127.0.0.1:5433 maxconn 100 check addr 127.0.1.2 port 8008
server wtantor3 127.0.0.1:5434 maxconn 100 check addr 127.0.1.3 port 8008
listen replica
bind *:8002
option httpchk OPTIONS /replica
http-check expect status 200
default-server inter 2s fastinter 100ms downinter 2s fall 3 rise 2 observe layer4 error-limit 1 on-error fastinter init-state down on-marked-down shutdown-sessions
server rtantor1 127.0.0.1:5432 maxconn 100 check addr 127.0.1.1 port 8008
server rtantor2 127.0.0.1:5433 maxconn 100 check addr 127.0.1.2 port 8008
server rtantor3 127.0.0.1:5434 maxconn 100 check addr 127.0.1.3 port 8008IP-адрес Patroni REST API может отличаться от ip-адреса экземпляра PostgreSQL.
Для корректной работы нужно, чтобы были установлены параметры init-state=down и, как минимум, у реплик on-marked-down=shutdown-session. Директиву можно указать в секции defaults.
Аналогичная конфигурация haproxy.cfg рекомендована 1С ИТС.
Проверка через Patroni REST API может использоваться только, если используется Patroni и он не поставлен на паузу (переведён в mainenance mode). Режим паузы используется для обновления программного обеспечения и переконфигурирования.
Проверка роли экземпляра по параметру in_hot_standby, автоматически посылаемому сервером
Проверки на мастера и реплику напрямую (без Patroni) полезны, если Patroni переводится в режим паузы или вообще не используется. Конфигурация HAProxy может быть изменена и "бесшовно" перечитана: старые сессии доработают на старой конфигурации, новые - на новой конфигурации.
Роль экземпляра можно проверять без обращения к REST API Patroni, путём проверки значения параметра in_hot_standby, который возвращает экземпляр PostgreSQL сразу после аутентификации.
Пример конфигурации:
listen master1
mode tcp
bind *:8001
option tcp-check
tcp-check connect linger
tcp-check send-binary 00000041000300007573657200706f7374677265730064617461626173650074656d706c61746531006c6f675f646973636f6e6e656374696f6e73006f66660000
tcp-check expect binary 696e5f686f745f7374616e646279006f666600
tcp-check send-binary 5800000004 # X - завершение сессии
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 check
listen replica1
mode tcp
balance leastconn
bind *:8002
option tcp-check
tcp-check connect linger
tcp-check send-binary 00000041000300007573657200706f7374677265730064617461626173650074656d706c61746531006c6f675f646973636f6e6e656374696f6e73006f66660000
tcp-check expect binary 696e5f686f745f7374616e646279006f6e00
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 checkРассмотрим директивы tcp-check. Сначала клиент передаёт пакет установки соединения (Startup):

Пакет Startup посылается клиентом на порт процесса postmaster и инициирует создание сессии. В этом пакете первыми идут 4 байта длины, затем версия протокола. В PostgreSQL 18 используется версия 3.0 и 3.2, и принимается значение с версией 3.1. Затем идут строки в кодировке клиента. Кодировка опциональна, она может передаваться в том же пакете параметром client_encoding00UTF8, в HEX-формате это 636c69656e745f656e636f64696e670055544638, в котором принимает данные директива HAProxy:
tcp-check send-binary <hexstring> [comment <msg>]
Если кодировка клиента не передаётся, то используется кодировка сервера.
Обязательным в Startup пакет является только параметры: user. Если его не передать, то будет возвращён пакет с ошибкой 28000:
FATAL: no PostgreSQL user name specified in startup packet
Ни имя базы, ни кодировка не являются обязательными. Если не указать имя базы, то пользователь будет подключён к базе с его именем. Пример минимального Startup пакета:
tcp-check send-binary 00000017000300007573657200706f7374677265730000
Серверный процесс (backend) возвращает в пакете TCP (TCPframe) последовательность пакетов сетевого протокола PostgreSQL:

Первый байт пакета сетевого протокола PostgreSQL (кроме пакета Startup) это символ (обычно, буква латинского алфавита в верхнем регистре) в кодировке ASCII, обозначающий тип пакета (метку, tag). Затем идут 4 байта с длиной пакета, включая первый байт.
Если аутентификация не требуется (trust), то сервер возвращает пакеты, первый из которых типа 'R' (AuthentificationRequest) с содержимым из 4 нулей, которые означают, что аутентификация успешна (AuthenticationOk): 520000000800000000.
Описание пакета, инициирующего соединение
Описание пакета нужно, чтобы знать. как редактировать имя пользователя и/или базы данных для подсоединения.

Описание пакета, инициирующего соединение (StartupMessage):
00000041 - длина пакета (сообщения протокола PostgreSQL) 65 байт в шестнадцатеричном виде (HEX). Длина пакета обязательна, начиная с версии протокола 3.0. Длина занимает 4 байта и следует за одним байтом типа пакета. Все пакеты имеют тип, кроме пакетов Startup, в которых нет байта типа.
00030000 - версия протокола 3.0, которая используется начиная с PostgreSQL 7.4 (2003г.). В 18 версии появилась версия протокола 3.2, разница с 3.0 только в том, что сервер может передавать ключ длиной от 4 до 256 байт (в 18 версии 32 байта), а протокол 3.0 использует ключ длиной 4 байт. Ключ используется для прерывания сессии. Короткий ключ позволяет организовывать атаки на отказ в обслуживании, прерывая сессии. Версии 3.1 не существовало, но PostgreSQL принимает версию 3.1 00030001. Для протокола версии 3.2 значение будет: 00030002. Клиент libpq версии 18 указывает версию 3.0. PostgreSQL 18 поддерживает протоколы от 3.0 до 3.2. Протокол 2.0 перестал поддерживаться в версии PostgreSQL 14. При указании неподдерживаемой версии протокола PostgreSQL возвращает пакет 45 = E (ErrorResponse) с ошибкой:
FATAL: unsupported frontend protocol 2.0: server supports 3.0 to 3.2
7573657200 - строка со словом user, завершающаяся нулевым байтом, обозначающим завершение строки. Это слово и имя пользователя предавать обязательно.
706f73746772657300 - слово postgres для аутентификации. Можно указать другое имя.
646174616261736500 - слово database. Это слово и имя базы передавать не обязательно.
74656d706c6174653100 - слово template1. Можно указать имя существующей базы данных, например, postgres. Если не указать, то используется имя пользователя. База данных template1 есть во всех кластерах баз данных PostgreSQL.
6c6f675f646973636f6e6e656374696f6e73006f666600 = log_disconnections00off00
Передача этого параметра отключит логирование в момент отсоединения, если оно было включено на экземпляре PostgreSQL. Логирование в момент соединения параметром log_disconnection не отключается и передавать этот параметр бесполезно. Утилита psql передаёт параметры application_name00psql и client_encoding00UTF8, их не обязательно передавать, кодировка будет установлена в кодировку сервера.
00 - пакет Startup завешается ещё одним нулевым байтом.
Описание пакета, возвращаемого сервером
Описание пакета, возвращаемого сервером нужно, чтобы понимать, почему проверяются приведённые последовательности байт и их достаточно:
tcp-check expect binary 696e5f686f745f7374616e646279006f666600
или
tcp-check expect binary 696e5f686f745f7374616e646279006f666600
Север возвращает одним TCP-пакетом сообщения:

520000000800000000 - байт типа пакета 52 = 'R' (Authentification Request). Затем идёт 4 байта с длиной оставшейся части пакета - 8 байт. Четыре нуля - аутентификация успешна (AuthenticationOk).
530000001b496e74657276616c5374796c6500706f73746772657300 - затем сразу идёт пакет 53 = 'S' (ParameterStatus) и его длина, - примерно 27 байт. В пакете IntervalStyle00postgres.
Таких пакетов 15 - по числу параметров из списка PQparameterStatus.
4b0000000c00156c427fa84d15 - пакет типа 4b = 'K' (BackendKeyData). Для протокола версии 3.0 длина пакета 0c = 12 байт. Пакет содержит номер процесса PID = 00156c42 (4 байта) и ключ Cancel Key 7fa84d15 (4 байта для протокола версии 3.0) которые может использовать клиент, чтобы прервать сессию. Для протокола версии 3.2 длина ключа 32 байта и может варьироваться от 4 до 256 байт.
5a0000000549 - пакет типа 5a = 'Z' (ReadyForQuery), который означает готовность сервера принимать команды. Длина пакета 5 байт. 49 = I (Idle), что означает, что сессия простаивает и транзакция не открыта. Пакеты типа S не передаются в открытой транзакции.
Пример, директивы HAProxy, которой можно проверить, что аутентификация успешно пройдена:
tcp-check expect binary 520000000800000000
Директива проверяет в ответе сервера наличие последовательности байт, соответствующей пакету 52 = 'R' (Authentification Request) с payload об успешной аутентификации.
Однако, эта проверка излишняя, можно сразу проверять значение in_hot_standby, если нужно определить роль экземпляра PostgreSQL.
Для проверки наличия пакета 'S' с in_hot_standby=off (мастер):
tcp-check expect binary 5300000017696e5f686f745f7374616e646279006f666600
для проверки наличия пакета 'S' с in_hot_standby=on (реплика):
tcp-check expect binary 5300000016696e5f686f745f7374616e646279006f6e00
Префикс 5300000017 или 5300000016 можно не указывать, так как оставшаяся часть сообщения вряд ли встретится в каком-то другом пакете.
Корректное закрытие сессии проверки
Сразу после аутентификации серверный процесс возвращает параметры текущего статуса сервера https://docs.tantorlabs.ru/tdb/ru/18_4/se/libpq-status.html#LIBPQ-PQPARAMETERSTATUS . Также параметры передаются, если они меняются в течение сессии. Параметры default_transaction_read_only и in_hot_standby передаются, начиная с PostgreSQL версии 14. По значению параметра in_hot_standby можно отличить мастер от реплики.

Для HAProxy обязательно использование linger, который позволяет HAProxy передать пакет, закрывающий соединение (send-binary 5800000004), без него в логе PostgreSQL будут ошибки:
LOG: could not receive data from client: Connection reset by peer
Проверка роли экземпляра SQL-запросом
Проверка запросомselectpg_is_in_recovery(); Недостаток - лишний сетевой roundtrip, преимущество - проверяется возможность выполнения SQL-запросов. В протоколе есть пакеты 'F' (FunctionCall), которые позволяют вызывать функции ядра напрямую. Они не практичны, так как для вызова нужно указать OID функции, а он не фиксирован. Использование пакетов с запросами 'Q' (Query) более универсально.
listen master1
mode tcp
bind *:8001
option tcp-check
tcp-check connect linger
tcp-check send-binary 00000041000300007573657200706f7374677265730064617461626173650074656d706c61746531006c6f675f646973636f6e6e656374696f6e73006f66660000
tcp-check expect binary 5a000000 # готов принимать команды 5a = Z
tcp-check send-binary 510000002073656c6563742070675f69735f696e5f7265636f7665727928293b00
# передаёт select pg_is_in_recovery();
tcp-check send-binary 5800000004
tcp-check expect binary 66430000000d # проверяет 66 = f = false
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 check
listen replica1
mode tcp
balance leastconn
bind *:8002
option tcp-check
tcp-check connect linger
tcp-check send-binary 00000041000300007573657200706f7374677265730064617461626173650074656d706c61746531006c6f675f646973636f6e6e656374696f6e73006f66660000
tcp-check expect binary 5a000000 # готов принимать команды
tcp-check send-binary 510000002073656c6563742070675f69735f696e5f7265636f7665727928293b00
tcp-check send-binary 5800000004
tcp-check expect binary 74430000000d # проверяет 74 = t = true
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 checkОписание пакетов проверки SQL запросом
Этот раздел статьи нужен, если вы захотите использовать более сложный запрос для проверки роли и работоспособности экземпляра.

Передаваемый пакет с SQL-запросом:
tcp-check send-binary 510000002073656c6563742070675f69735f696e5f7265636f7665727928293b00
5100000020 - вначале тип (tag) пакета 51 = 'Q' (Query); затем длина пакета 0x20 = 32 байта;
затем строка с запросом: select pg_is_in_recovery(); и нулевой байт 00.
TCP-пакет с ответом сервера содержит пакеты сетевого протокола PostgreSQL:
540000002a000170675f69735f696e5f7265636f7665727900000000000000000000100001
ffffffff0000440000000b00010000000166430000000d5345 4c4543542031005a0000000549
5400000020 - пакет типа 54 = 'T' (RowDescription). Длина 2a = 42 байта.
0001 - число столбцов в ответе.
70675f69735f696e5f7265636f7665727900 - имя столбца pg_is_in_recovery.
Затем идут: OID таблицы (4 нуля, так как результат функции, а не таблица); номер столбца в таблице (2 нуля); 00000010 - OID типа данных столбца = 16 = bool, логический тип данных;
0001 - длина типа данных, для boolean 1 байт; ffffffff = -1 (неизвестен), модификатор типа данных столбца (typmod); формат кодирования данных - 2 нуля (текстовый формат, Text mode). Затем идёт сообщение:
440000000b - пакет типа 44 = 'D' (DataRow). Длина 0b = 11 байт; 0001 - число значений; 00000001 - длина значения 1 байт; 66 - само значение f. Затем идёт сообщение:
430000000d - пакет типа 43 = 'C' (CommandComplete). Длина 0d = 13 байт; строка с тэгом команды. Для команд типа select выдаёт число возвращённых строк в виде строки (тэга) SELECT число. Так как строка одна, то число = 1. Завершается нулевым байтом (00).
Для команды insert тэг: INSERT 0 число. Раньше, вместо 0 мог выдаваться OID таблицы.
Затем идёт сообщение:
5a0000000549 - пакет типа 5a = 'Z' (ReadyForQuery). Длина 5 байт. 49 - статус транзакции I (Idle) транзакция не открыта.
Пакет завершения сессии, посылаемый клиентом:
5800000004 - пакет типа 58 = 'X' (Terminate) корректного закрытия сессии. Длина 4 байта. После этого пакета клиент может закрывать TCP-сокет.
В конфигурации HAProxy, команда отсылки пакета завершения сессии (tcp-checksend-binary 5800000004) должен идти до команды проверки (tcp-checkexpect), которая может завершиться неудачно, так как HAProxy не выполняет команды, которые идут после неудачной проверки.
Защита от split brain средствами HAProxy
Проверки на мастера и реплику напрямую (без Patroni) полезны, если Patroni переводится в режим паузы или вообще не используется. Конфигурация HAProxy может быть изменена и "бесшовно" перечитана: старые сессии доработают на старой конфигурации, новые - на новой конфигурации.

Одна из задач Patroni - не допускать работы двух мастеров (split brain). Если Patroni не используется или переведён в режим паузы, то нужно проверить, что мастер остановлен перед promote одной из реплик. HAProxy позволяет проверять, что среди серверов, на которые он направляет клиентов, только один сервер является мастером. Проверка может использоваться и с Patroni, как дополнительная защита от split brain.
Конфигурация HAProxy немного усложняется: вместо секции listen мастера нужно использовать связку из двух секций frontend и backend. Содержимое listen копируется в секцию backend, за исключением директивы bind, которая переходит в секцию frontend.
В секцию frontend мастера добавляется проверка на то, что мастер единственный из всех серверов, иначе отказывать клиентам в соединении с серверами. Выбирается "CP" - защита от расщепления (partition tolerance) вместо доступности (availability), как и в Patroni. Конфигурация мастера:
frontend masterf
mode tcp
bind *:8001
acl single_master nbsrv(master1) eq 1
tcp-request connection reject if !single_master
default_backend master1
backend master1
mode tcp
option tcp-check
... проверки на мастера
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 check
listen replica1
mode tcp
balance leastconn
bind *:8002
option tcp-check
... проверки на реплику
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 checkДля реплики можно не менять директиву listen, но можно и заменить на эквивалент из двух директив frontend и backend:
frontend replicaf
mode tcp
bind *:8002
use_backend replica1
backend replica1
mode tcp
balance leastconn
option tcp-check
... проверки на реплику
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 checkpgbouncer и параметр in_hot_standby
При изменении значения 15 параметров, PostgreSQL 18 версии, без запроса клиента, посылает клиенту пакет со значениями этих параметров (http://docs.tantorlabs.ru/tdb/ru/18_4/se/protocol-flow.html#PROTOCOL-ASYNC ). Также, такой пакет посылается при установке соединения после аутентификации, до сообщения ReadyForQuery ('Z' ). Пакет не посылается, если клиент открыл транзакцию. Пакет посылается только вне транзакции. В 14 версии PostgreSQL в список отслеживаемых параметров, были добавлены параметры in_hot_standby и default_transaction_read_only, в 16 версии был добавлен параметр scram_iterations, в 18 версии был добавлен параметр search_path. Параметр transaction_read_only не входит в список параметров, отслеживаемых PostgreSQL.
pgbouncer может отслеживать параметры из списка PQparameterStatus, но только те, которые могут быть изменены клиентом ( раздел, описывающий опцию track_extra_parameters) - 10 параметров из 15. В этот список pgbouncer не входит параметр in_hot_standby, он не может меняться клиентом. Параметр default_transaction_read_only не меняется при смене ролей мастер-реплика. Если перечислить параметры из этого списка (10 штук) в опции track_extra_parameters, то pgbouncer будет кэшировать в своей памяти значения параметров, перечисленных в опции track_extra_parameters, и pgbouncer будет восстанавливать их значения в сессиях для клиентов. По умолчанию, track_extra_parameters пуст и не кэширует ни один из параметров. Значения остальных 10 отслеживаемых параметров будут сбрасываться в значения по умолчанию, если используется опция server_reset_query_always=1.
Проблема с pgbouncer: не обновляет in_hot_standby,если он поменялся
Если реплика становится мастером, то in_hot_standby меняет значение с true на false, но проблема в том, что созданные pgbouncer сессии не разрываются, и pgbouncer выдаёт своим клиентам значение in_hot_standby=true, хотя в реальности in_hot_standby=false. Экземпляр PostgreSQL передаёт S пакеты с новым значением, но pgbouncer эти пакеты не отслеживает.
pgbouncer может устанавливаться перед HAProxy. Такие конфигурации используются , чтобы клиенты не замечали разрыва сессий, так как pgbouncer будет пересоздавать сессии в случае сбоя сессий в результате смены ролей экземплярами. В таких конфигурациях, можно ставить pgbouncer на паузу перед switchover.

Из-за этого, клиенты, которые подсоединяются к pgbouncer, получают в пакете PQparameterStatus ложные значения и могут разорвать соединение. Например, если клиент указывает target_session_attrs=primary или target_session_attrs=read-write. Пример:
psql "postgres://127.0.0.1:5432/postgres?target_session_attrs=primary"
psql: error: connection to server at "127.0.0.1", port 5434 failed: server is in hot standby mode
Такие клиенты могут использовать значение target_session_attrs=any или, при продвижении реплики, нужно разорвать сессии pgbouncer с PostgreSQL:
psql "postgres://127.0.0.1:6432/postgres?target_session_attrs=primary"
psql: error: connection to server at "127.0.0.1", port 5434 failed: server is in hot standby mode
psql -q "postgres://127.0.0.1:6432/postgres?target_session_attrs=any"
postgres=#
Проблема описана в: http://github.com/pgbouncer/pgbouncer/issues/859
Пример, как выглядит проблема с соединениями через pgbouncer, если клиент запрашивает targetServerType=primary.
Разорвать сессии pgbouncer с PostgreSQL можно командой RECONNECT, подсоединившись на порт pgbouncer к базе с названием pgbouncer. Для посылки этой команды, нужно сконфигурировать Patroni, чтобы при продвижении реплики до мастера, Patroni подсоединялся к pgbouncer и выполнял команду RECONNECT. Для этого нужно создать исполняемый файл /opt/tantor/etc/patroni/callback.sh:
#!/usr/bin/bash
ACTION=$1; ROLE=$2; CLUSTER_NAME=$3;
[ "$ACTION" = "on_role_change" ] && [ "$ROLE" = "primary" ] && psql -h localhost -p 6432 -d pgbouncer -c "RECONNECT"
Установить параметры вызова скрипта в /opt/tantor/etc/patroni/patroni.yml:
postgresql:
callbacks:
on_role_change: /opt/tantor/etc/patroni/callback.shЕсли pgbouncer работает между клиентами и HAProxy, то проблемы нет, так как HAProxy разрывает сессии pgbouncer с экземплярами, сменившими роли.
Скрытый текст
Полный пример конфигурационного файла haproxy.cfg, который можно использовать как шаблон:
global
log stderr format iso local0 err
defaults
mode tcp
balance leastconn # направлять на сервер, где меньше соединений. По умолчанию random
log global
option tcplog
option dontlognull
retries 2 # если сервер не ответил, повторить ещё 2 раза перед выдачей ошибки клиенту
option redispatch # направлять запрос на другой сервер после исчерпания retries
timeout queue 1m # длительность ожидания клиента, перед выдачей ему ошибки
timeout connect 1s # таймаут соединения HAProxy к серверам
timeout client 3h # время неактивности клиента, установлено исходя из значения net.ipv4.tcp_keepalive_time = 7200
timeout server 3h # время неактивности со стороны сервера
timeout check 2s # таймаут на проверку
maxconn 100 # для каждого фронтенда или бэкенда перед постановкой запросов в очередь
default-server inter 2s fastinter 100ms downinter 2s fall 3 rise 2 observe layer4 error-limit 1 on-error fastinter init-state down on-marked-down shutdown-sessions
listen stats
mode http
bind *:8000
stats enable
stats uri /
stats refresh 10s
stats show-legends
listen primary
bind *:8001
option httpchk OPTIONS /primary
http-check expect status 200
server wtantor1 127.0.0.1:5432 maxconn 100 check addr 127.0.1.1 port 8008
server wtantor2 127.0.0.1:5433 maxconn 100 check addr 127.0.1.2 port 8008
server wtantor3 127.0.0.1:5434 maxconn 100 check addr 127.0.1.3 port 8008
listen replica
bind *:8002
option httpchk OPTIONS /replica
http-check expect status 200
server rtantor1 127.0.0.1:5432 maxconn 100 check addr 127.0.1.1 port 8008
server rtantor2 127.0.0.1:5433 maxconn 100 check addr 127.0.1.2 port 8008
server rtantor3 127.0.0.1:5434 maxconn 100 check addr 127.0.1.3 port 8008
listen master1
mode tcp
bind *:8003
option tcp-check
tcp-check connect linger
tcp-check send-binary 00000041000300007573657200706f7374677265730064617461626173650074656d706c61746531006c6f675f646973636f6e6e656374696f6e73006f66660000 #передаются слова в кодировке ASCII, записанные в HEX-формате: user00postgres00database00template100log_disconnections00off00
tcp-check expect binary 696e5f686f745f7374616e646279006f666600 # in_hot_standby off=6f6666 on=6f6e
tcp-check send-binary 5800000004 # завершение сессии 'X' = 58, длина 4 байта
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 check
listen replica1
mode tcp
bind *:8004
option tcp-check
tcp-check connect linger
tcp-check send-binary 00000041000300007573657200706f7374677265730064617461626173650074656d706c61746531006c6f675f646973636f6e6e656374696f6e73006f66660000
tcp-check expect binary 696e5f686f745f7374616e646279006f6e00 # off=6f6666 on=6f6e
tcp-check send-binary 5800000004
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 check
frontend masterf
mode tcp
bind *:8005
acl single_master nbsrv(masterb) eq 1
tcp-request connection reject if !single_master
default_backend masterb
backend masterb
mode tcp
option tcp-check
tcp-check connect linger
tcp-check send-binary 00000041000300007573657200706f7374677265730064617461626173650074656d706c61746531006c6f675f646973636f6e6e656374696f6e73006f66660000 # пакет Startup
tcp-check expect binary 5a000000 # 'Z' - готов к приему команд
tcp-check send-binary 510000002073656c6563742070675f69735f696e5f7265636f7665727928293b00 # 'Q' запрос select pg_is_in_recovery();
tcp-check send-binary 5800000004 # 'X' - завершение сессии
tcp-check expect binary 66430000000d # fC - false Complete или 7443 = tC
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 check
listen replicaf
mode tcp
bind *:8006
option tcp-check
tcp-check connect linger
tcp-check send-binary 00000041000300007573657200706f7374677265730064617461626173650074656d706c61746531006c6f675f646973636f6e6e656374696f6e73006f66660000 # пакет Startup
tcp-check expect binary 5a000000 # Z = 5a
tcp-check send-binary 510000002073656c6563742070675f69735f696e5f7265636f7665727928293b00 # 'Q', запрос select pg_is_in_recovery();00
tcp-check send-binary 5800000004 # 'X' - завершение сессии
tcp-check expect binary 74430000000d # fC - false Complete или 7443 = tC
server r1 127.0.0.1:5432 check
server r2 127.0.0.1:5433 check
server r3 127.0.0.1:5434 checkВыбор таймаутов, конфигурирование Patroni, pgbouncer в статье не рассматривается. Это рассматривается в книжке Tantor DBA2-18, главы из которой выложены в свободном доступе на http://dba1.ru.

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.