Connection pooling: как работают пулы соединений в PostgreSQL и MySQL

8 минут чтения
Средний рейтинг статьи — 4.9

Почему соединение с базой — это дорого?

Каждый раз, когда приложение открывает новое соединение с базой данных, происходит целый набор операций:

  • TCP handshake
  • (часто) TLS handshake
  • аутентификация пользователя
  • выделение памяти под backend-процесс (в PostgreSQL)
  • создание служебных структур

Если приложение открывает и закрывает соединения на каждый запрос — производительность падает, а база начинает "задыхаться" от накладных расходов.

Особенно это заметно:

  • при high-load API
  • в микросервисной архитектуре
  • при burst-нагрузке
  • при использовании serverless

Именно для этого используется connection pooling.

Что такое connection pooling и как он работает

Connection pool — это промежуточный слой, который:

  1. Открывает ограниченное число соединений к базе
  2. Переиспользует их между запросами
  3. Управляет конкуренцией
  4. Ограничивает максимальную нагрузку на БД

Приложение берёт соединение из пула → выполняет запрос → возвращает его обратно.

Без постоянного создания новых TCP-сессий.

Без пула:

Client → DB (connect → auth → query → disconnect)

С пулом:

Client → Pool → DB

Pool держит N постоянных соединений.
Если все заняты — новые запросы ждут в очереди.

PostgreSQL: почему без пула тяжело

PostgreSQL использует процессную модель:

Один клиент = один backend-процесс.

Каждое соединение потребляет:

  • память
  • file descriptors
  • shared buffers
  • системные ресурсы

1000 соединений = 1000 процессов.

Это может легко "убить" сервер.

Поэтому пулер — практически обязательный компонент продакшена.

PgBouncer — самый популярный пулер

PgBouncer работает как прокси между приложением и PostgreSQL.

Основные режимы работы:

1) Session pooling

Соединение закрепляется за клиентом на всю сессию.

  • безопасно
  • хуже масштабируется

2) Transaction pooling (самый популярный)

Соединение выделяется только на время транзакции.

  • высокая эффективность
  • позволяет обслуживать тысячи клиентов
  • нельзя использовать session-level state

3) Statement pooling

Соединение выделяется на один SQL-запрос.

  • максимальная плотность
  • серьёзные ограничения

Что происходит внутри

  1. Приложение подключается к PgBouncer
  2. PgBouncer аутентифицирует клиента
  3. При необходимости выдаёт backend-соединение
  4. После завершения транзакции соединение возвращается в пул

Количество backend-соединений фиксировано (например, 100).
Количество клиентских подключений может быть 5000+.

Это резко снижает нагрузку на PostgreSQL.

MySQL: какие варианты pooling

MySQL также создаёт отдельный поток (thread) на каждое соединение.

Но он легче по ресурсам, чем процесс PostgreSQL.

Тем не менее, тысячи соединений:

  • потребляют память
  • увеличивают context switching
  • влияют на latency

В MySQL Enterprise есть thread pool.

В community-версии чаще используют:

  • ProxySQL
  • HAProxy
  • пул на уровне приложения

ProxySQL

Работает аналогично PgBouncer:

App → ProxySQL → MySQL

Позволяет:

  • ограничивать backend connections
  • балансировать read/write
  • кэшировать запросы
  • маршрутизировать по правилам

Пул на уровне приложения

Многие драйверы имеют встроенные пулы:

  • HikariCP (Java)
  • SQLAlchemy pool (Python)
  • Go database/sql
  • Node.js pg/mysql pools

Важно понимать:

Если у вас 10 сервисов, и в каждом pool_size = 50,

реально к базе может прийти 500 соединений.

И база всё равно будет перегружена.

Поэтому часто используют:

App-level pool + PgBouncer/ProxySQL.

Настройка пула: ключевые параметры

pool size

Сколько backend-соединений держать.

Правило грубой оценки:

CPU cores × 2–4

Но лучше измерять.

max connections

Ограничение клиентских подключений.

Защищает БД от перегруза.

idle timeout

Закрытие неиспользуемых соединений.

queue timeout

Сколько клиент будет ждать свободное соединение.

Типичные проблемы

❌ Слишком большой pool
Кажется логичным: больше соединений → выше throughput.
На практике растёт contention, увеличивается блокировка и падает производительность.

❌ Пул меньше, чем нужно
Растёт latency, а запросы стоят в очереди.

❌ Transaction pooling + session state
Если приложение использует temporary tables, SET variables и prepared statements без reset, можно получить неожиданные баги.

Когда пул может не понадобиться

  • аналитическая БД
  • batch-задачи
  • низкая конкуренция

Но в 95% продакшен-нагрузок — нужен.

Практический пример

Без пула:

2000 RPS
→ 2000 соединений
→ CPU 100%
→ latency скачет

С PgBouncer (transaction mode):

2000 RPS
→ 100 backend connections
→ стабильный latency
→ CPU ниже

Вывод

Connection pooling — это не просто оптимизация.

Это механизм защиты базы данных.

Он:

  • снижает накладные расходы
  • стабилизирует latency
  • ограничивает конкуренцию
  • делает систему предсказуемой

В PostgreSQL — практически обязательный.
В MySQL — сильно рекомендуется при high-load.

Если база — сердце системы, то пул — это её регулятор давления.

8 минут чтения
Средний рейтинг статьи — 4.9

Настроить мониторинг за 30 секунд

Надежные оповещения о даунтаймах. Без ложных срабатываний