pgBouncer

Для чего нужен пулер соединений

Количество соединений, которое поддерживает сервер БД PostgreSQL, ограничено. Поэтому нормальная логика работы клиентского приложения с БД предполагает, что приложение:

  1. Устанавливает соединение с сервером БД.

  2. Отправляет SQL-запрос и получает результат.

  3. Закрывает соединение, освобождая его для следующих клиентов.

Проблема в том, что PostgreSQL выделяет отдельный процесс на каждое установленное соединение. Постоянное открытие и закрытие множества соединений означает запуск и остановку множества процессов, что создает большие накладные расходы ресурсов сервера и приводит к снижению производительности СУБД.

Чтобы решить эту проблему, в DBaaS Postgres для всех инстансов установлен менеджер соединений PgBouncer (пулер). Логика его работы следующая:

  1. Пулер устанавливает с сервером БД фиксированное количество соединений (пул), которые держит открытыми все время. В DBaaS Postgres размер пула по умолчанию (default_pool_size pgbouncer) составляет 200 соединений.

  2. Клиентское приложение подключается не напрямую к БД, а к пулеру. Максимальное количество поддерживаемых соединений между приложением и пулером составляет 6000.

  3. Пулер коммутирует клиентское соединение на сервер, используя открытое соединение из пула.

  4. Когда приложение закрывает соединение с пулером, соединение между пулером и сервером возвращается в пул и может быть переиспользовано следующим клиентским соединением.

Режимы работы PgBouncer

Менеджер соединений PgBouncer в DBaaS Postgres работает в двух режимах:

  • Сессионный — в этом режиме клиентское соединение устанавливается при первом запросе к базе данных и поддерживается до тех пор, пока клиент не разорвет сессию. Такой режим поддерживается всеми клиентами PostgreSQL, но является менее производительным, чем транзакционный режим. PgBouncer в сессионном режиме доступен на порту 5432.

  • Транзакционный — в этом режиме клиентское соединение устанавливается при первом запросе к базе данных и поддерживается до завершения транзакции. Транзакционный режим обеспечивает высокую производительность и позволяет максимально эффективно нагрузить СУБД. PgBouncer в транзакционном режиме доступен на порту 6432.

Однако транзакционный режим поддерживается не всеми клиентами PostgreSQL, и в нем недоступно использование некоторых функций PostgreSQL:

  • временных таблиц (temporary tables);

  • курсоров (cursors);

  • рекомендательных блокировок (advisory locks), которые существуют дольше одной транзакции;

  • подготовленных операторов (prepared statements).

Experiment

Preparation

Having following docker-compose file:

This creates the test tables (pgbench_branches, pgbench_accounts, etc.) and populates them with sample data.

Without pgBouncer

  • -c 50: Use 10 concurrent clients.

  • -j 4: Use 4 threads.

result may look like

With pgBouncer

-d maindb (this comes from pgbouncer.ini where I define an alias maindb which leads to actual postgres database in postgres container)

PgBouncer psql

Of course it is possible to connect to pgBouncer using psql client

where 6432 - port to connect to pgBouncer

maindb - db alias configured in pgbouncer.ini for particular backed database

Session vs Transaction modes

Scenario 1: pgBouncer in session Mode

📌 Behavior

  • A single PostgreSQL session is assigned to a client connection for its entire lifetime.

  • All transactions from the same client use the same backend PID.

  • Session-scoped features (like temporary tables, GUCs, SET LOCAL, etc.) persist across transactions.

✅ Use Cases

  • Applications that rely on:

    • Temporary tables

    • Session variables (SET LOCAL app.user_id = '123')

    • Listeners (LISTEN / NOTIFY)

    • Extensions like pg_trgm with session-local settings

🧠 Spring Boot + Hibernate Behavior

  • One EntityManager may reuse the same connection (session) across multiple @Transactional methods.

  • Session state (e.g., SET LOCAL) persists across transactions.

  • Temporary tables created in one transaction are visible in subsequent transactions.

⚠️ Drawbacks

  • Less scalable: Each client connection holds a backend connection.

  • Risk of connection leaks if clients don’t close connections.


🧪 Scenario 2: pgBouncer in transaction Mode

📌 Behavior

  • A backend connection is assigned only for the duration of a single transaction.

  • After COMMIT or ROLLBACK, the connection is returned to the pool and can be reused by another client.

  • Session-scoped features do not persist across transactions.

✅ Use Cases

  • Stateless applications

  • High concurrency with many short transactions

  • Applications that don’t rely on session state

🧠 Spring Boot + Hibernate Behavior

  • Each @Transactional method may get a different backend PID.

  • Session variables (SET LOCAL) are lost between transactions.

  • Temporary tables are not visible across transactions (since they’re session-scoped).

  • Hibernate and Spring can still work fine, as long as they don’t rely on session state.

⚠️ Drawbacks

  • Incompatible with:

    • Temporary tables

    • SET LOCAL variables

    • LISTEN / NOTIFY

    • Extensions that rely on session state

Last updated

Was this helpful?