Базы данных
Connection Pooling
GitHub в 2019 году столкнулся с "too many clients" ошибкой во время инцидента: автоматическое масштабирование приложения создало 3000 соединений при лимите в 500. Решение заняло час. После: ProxySQL как обязательный слой перед каждой MySQL БД. Connection pooling - не оптимизация, а обязательный элемент архитектуры.
- **Supabase**: PgBouncer встроен для всех проектов - serverless функции создают тысячи соединений, пул ограничивает реальное количество к PostgreSQL
- **GitLab**: PgBouncer перед каждым PostgreSQL кластером - 10,000+ соединений от CI runners к 50 реальным backend соединениям
- **Shopify**: ProxySQL для MySQL - 10,000 app серверов к 200 реальным MySQL соединениям
Стоимость создания соединения
Создание нового соединения с PostgreSQL - дорогостоящая операция: fork нового процесса (postgres backend), TLS handshake, аутентификация, загрузка pg_hba.conf, инициализация session state. В среднем 5-15ms на соединение. При 1000 req/sec это 5-15ms overhead на каждый запрос - катастрофа.
PostgreSQL максимум по умолчанию: 100 соединений (max_connections). У Shopify 10,000+ application серверов. Без пула: 10,000 прямых соединений = невозможно. PgBouncer решает: 10,000 клиентов -> 50 реальных соединений к PostgreSQL.
PostgreSQL ограничен 200 соединениями. 500 application серверов x 5 минимальных соединений = 2500 нужно. Как решить без увеличения max_connections?
Режимы PgBouncer
PgBouncer - легковесный connection pooler для PostgreSQL. Три режима: Session pooling - соединение с PostgreSQL удерживается всё время сессии клиента. Transaction pooling - соединение с PostgreSQL возвращается в пул после каждой транзакции (наиболее эффективный). Statement pooling - после каждого запроса (не работает с транзакциями).
Transaction pooling несовместим с: session-level settings (SET search_path), prepared statements (per-session), advisory locks, LISTEN/NOTIFY. Supabase (hosted PostgreSQL) предоставляет PgBouncer по умолчанию для всех проектов.
Приложение использует SET search_path = myschema после подключения. Какой режим PgBouncer подходит?
ProxySQL для MySQL
ProxySQL - умный proxy для MySQL с connection pooling, query routing, query rewriting, and failover. В отличие от PgBouncer, ProxySQL может маршрутизировать SELECT на replicas, INSERT/UPDATE на master - автоматически, без изменений в приложении.
ProxySQL поддерживает query multiplexing: несколько легковесных connections от приложения мультиплексируются в меньшее количество соединений к MySQL. При этом видны session-level переменные.
ProxySQL обнаруживает что текущий master MySQL недоступен. Что происходит с pending транзакциями?
Лимиты соединений и мониторинг
Превышение max_connections в PostgreSQL - критическая ошибка: "FATAL: sorry, too many clients already". Приложение не может подключиться. Мониторинг активных соединений и их состояния - обязательная метрика для продакшн PostgreSQL.
pg_stat_activity показывает 50 соединений в состоянии "idle in transaction" по 5 минут каждое. Что это означает?
Trade-offs и выбор стратегии
Нет универсального решения для connection pooling. PgBouncer transaction mode + маленький pool_size - наиболее эффективно, но несовместимо с session-level features. Supabase предоставляет два endpoints: direct (полный PostgreSQL протокол) и pooler (PgBouncer transaction mode).
| Сценарий | Рекомендация | Причина |
|---|---|---|
| Serverless функции (AWS Lambda, Vercel) | PgBouncer transaction mode или Supabase Pooler | Тысячи холодных соединений без pooler убьют PostgreSQL |
| Web приложение с постоянными серверами | Встроенный pool (SQLAlchemy, pgx) + PgBouncer | Двухуровневый пул: app + pgbouncer |
| MySQL с read replicas | ProxySQL | Автоматический routing reads/writes без изменений в коде |
| Prepared statements критичны | PgBouncer session mode или pgx built-in pool | Transaction mode несовместим с server-side prepared statements |
PgBouncer vs pgpool-II: pgpool-II дополнительно поддерживает load balancing, connection caching, query cache. Но он тяжелее и сложнее в настройке. Большинство продакшн систем используют PgBouncer (simpler, faster) + HAProxy для load balancing.
Больше соединений = лучше производительность
Избыточные соединения к PostgreSQL снижают производительность: каждый backend - отдельный процесс с RAM overhead, context switching между процессами дорог. Оптимальный pool_size ≈ (num_cores * 2) + num_disks.
Пул из 10-50 соединений часто даёт лучшую throughput чем 500. Соединения конкурируют за CPU, а не параллельно работают. PgBouncer с маленьким pool_size + очередь - правильная модель.
Vercel serverless функция подключается к PostgreSQL напрямую. Каждый invocation - новое соединение. 10,000 req/sec. Что произойдёт?
Ключевые идеи
- **Стоимость соединения**: 5-15ms fork + TLS + auth; pooler создаёт их один раз
- **PgBouncer transaction mode**: самый эффективный для PostgreSQL, несовместим с SET/session vars/server-side prepared statements
- **Мониторинг**: idle in transaction > 30s = проблема; connections > 80% max = нужен pooler
Связанные темы
Connection pooling - часть оптимизации всего стека:
- Кеширование — Redis как cache снижает количество запросов к БД - меньше нагрузка на connections
- Мониторинг БД — pg_stat_activity - основной инструмент мониторинга соединений
- Репликация — ProxySQL маршрутизирует reads на replicas - connection pooling + load balancing вместе
Вопросы для размышления
- Serverless архитектура (AWS Lambda) с PostgreSQL без pooler: какую проблему ожидать при 1000 concurrent invocations?
- Когда pool_size=10 эффективнее чем pool_size=100 для PostgreSQL?
- Как connection pooler влияет на транзакции и нужно ли что-то менять в коде приложения при добавлении PgBouncer?