Перейти к содержанию

Пулеры соединений

Пулер в режиме транзакций — PgBouncer с pool_mode = transaction — выдаёт клиенту серверное соединение на одну транзакцию. Между двумя транзакциями (и между двумя командами вне транзакции) следующая команда может попасть на другое серверное соединение. Запросы и транзакции hare за таким пулером работают без изменений; то, что держит собственную сессию, требует настроек ниже.

hare поддерживает PgBouncer 1.22.1 и новее с подготовленными командами между транзакциями (max_prepared_statements > 0) — оба драйвера сохраняют за ним свой кэш подготовленных команд. PgBouncer 1.21.0–1.22.0 после COPY FROM STDIN (bulk_create(use_copy=True)) теряет учёт подготовленных команд серверного подключения, и следующая команда на нём падает с unnamed prepared statement does not exist; в PgBouncer 1.22.1 это исправлено. asyncpg отправляет команду длиннее своего max_cacheable_statement_size (по умолчанию 15 КБ) безымянной подготовленной командой: готовит её одним обращением к серверу, а выполняет следующим, — пулер транзакций может выдать для второго другое серверное подключение, и оно падает с unnamed prepared statement does not exist. За пулером увеличьте max_cacheable_statement_size подключения для своих длинных команд; собственные команды hare в него укладываются.

Настройка hare

Сообщите hare, что соединение идёт через такой пулер и где находится сам сервер:

postgresql://app:secret@pgbouncer:6432/app?transaction_pooling=true&direct_host=postgres&direct_port=5432
Параметр По умолчанию Что задаёт
transaction_pooling false Соединение идёт через пулер в режиме транзакций.
direct_host нет Адрес самого сервера в обход пулера — для работы, которой нужна своя сессия (ниже). Только вместе с transaction_pooling, иначе ConfigurationError.
direct_port port соединения Порт самого сервера. Требует direct_host.

Пользователь, пароль, база и настройки TLS прямого соединения — те же, что у соединения.

Что hare делает иначе

При transaction_pooling=true:

  • LISTEN — NotificationListener, будильник ListenNotifyWakeup очереди исходящих событий — открывает соединение на direct_host. LISTEN через пулер терял бы уведомления сразу после конца своей транзакции. Без direct_host — ConfigurationError.
  • Сессионный тайм-аут блокировки неатомарной миграции (lock_timeout) ставится на соединении с direct_host — он не остаётся на серверном соединении, которое пулер отдаст другому клиенту. Без direct_host — ConfigurationError.
  • Команда, подготовленный план которой устарел после изменения схемы. PgBouncer делит подготовленную команду между клиентами по её тексту, поэтому после миграции, которая меняет результат команды (ALTER COLUMN ... TYPE), сервер отвергает её с cached plan must not change result type на каждом серверном соединении, где она была подготовлена раньше. hare отправляет команду как есть и отдаёт ошибку сервера: после такой миграции переподключите серверные соединения PgBouncer — RECONNECT в его консоли администратора или перезапуск пула, — чтобы они подготовили команды заново.
  • DROP DATABASE удаляет базу WITH (FORCE) — пулер держит свои сессии к базе открытыми и после ухода клиентов.
  • Путь поиска опции schema и схемы на арендатора:
  • на PostgreSQL 18+ сервер сообщает пулеру каждое изменение пути поиска, и PgBouncer (track_extra_parameters = search_path) восстанавливает путь каждого клиента на серверном подключении, которое ему выдаёт. hare проверяет это один раз при открытии пула: пулер, который не передаёт путь серверу, выполнял бы команды всех арендаторов в одной схеме, поэтому hare вместо этого не стартует с ConfigurationError;
  • PostgreSQL 14–17 не сообщает пулеру путь поиска, и PgBouncer не может его восстановить — клиент получил бы путь того клиента, который пользовался серверным подключением до него. hare отказывает такому подключению с ConfigurationError при открытии пула: опции schema и схеме на арендатора за пулером в режиме транзакций нужен PostgreSQL 18+ или подключение напрямую к серверу.
  • Вход, который отверг пулер (неверный пароль), даёт ConfigurationError, как и отказ самого сервера, и не повторяется.

Что не требует ничего

Не держат сессию дольше своей транзакции и работают за пулером как есть: транзакции и точки сохранения, арендаторы защиты строк (ставятся на транзакцию), statement_timeout/lock_timeout у Transactions.atomic() (SET LOCAL), блокировка миграций (рекомендательная блокировка транзакции), двухфазная фиксация, stream() внутри транзакции, select_for_update(), COPY и relay очереди исходящих событий.

Настройка, изменённая для базы или роли (ALTER DATABASE ... SET), доходит только до серверных соединений, которые пулер откроет после неё, — открытые сохраняют старое значение, пока PgBouncer не переподключит их (RECONNECT в его админ-консоли).

Настройки PgBouncer

[pgbouncer]
pool_mode = transaction
; Подготовленные команды между транзакциями — обязательно для hare.
max_prepared_statements = 200
; Нужно с опцией schema или схемой на арендатора на PostgreSQL 18+.
track_extra_parameters = search_path

TimeZone и application_name PgBouncer передаёт сам — часовому поясу сессии hare (UTC) ничего не нужно.

Проверка здоровья

Пинг проверки здоровья идёт через пулер. HealthCheck(check_direct=True) пингует ещё и сам сервер — по direct_host/direct_port: так видно, когда пулер работает, а сервер нет (Метрики пула и здоровье). Пул этого прямого клиента в снимке и метриках пула имеет роль DIRECT.