Connection pooling: как работают пулы соединений в PostgreSQL и MySQL
Почему соединение с базой — это дорого?
Каждый раз, когда приложение открывает новое соединение с базой данных, происходит целый набор операций:
- TCP handshake
- (часто) TLS handshake
- аутентификация пользователя
- выделение памяти под backend-процесс (в PostgreSQL)
- создание служебных структур
Если приложение открывает и закрывает соединения на каждый запрос — производительность падает, а база начинает "задыхаться" от накладных расходов.
Особенно это заметно:
- при high-load API
- в микросервисной архитектуре
- при burst-нагрузке
- при использовании serverless
Именно для этого используется connection pooling.
Что такое connection pooling и как он работает
Connection pool — это промежуточный слой, который:
- Открывает ограниченное число соединений к базе
- Переиспользует их между запросами
- Управляет конкуренцией
- Ограничивает максимальную нагрузку на БД
Приложение берёт соединение из пула → выполняет запрос → возвращает его обратно.
Без постоянного создания новых 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-запрос.
- максимальная плотность
- серьёзные ограничения
Что происходит внутри
- Приложение подключается к PgBouncer
- PgBouncer аутентифицирует клиента
- При необходимости выдаёт backend-соединение
- После завершения транзакции соединение возвращается в пул
Количество 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.
Если база — сердце системы, то пул — это её регулятор давления.
Настроить мониторинг за 30 секунд
Надежные оповещения о даунтаймах. Без ложных срабатываний