Go goroutines vs OS threads: когда нужны каналы, когда sync.Mutex
1
# Connection pooling: pgbouncer vs встроенный пул в приложении
Connection pooling — это не просто оптимизация, это необходимость для любого приложения с PostgreSQL под нагрузкой. Каждое новое соединение стоит 1-3 мс и съедает память на сервере. Когда у вас 100 одновременных запросов, это уже заметно. Когда 1000 — база начинает задыхаться.
Я работал с обоими подходами: встроенный пул в приложении (pg, Sequelize) и внешний pgbouncer. Расскажу, когда что выбирать и как настроить.
## Transaction vs Session vs Statement pooling
Pgbouncer поддерживает три режима пулинга — ключевое отличие от встроенных пулов.
**Transaction pooling** (`pool_mode = transaction`) — соединение возвращается в пул после `COMMIT`/`ROLLBACK`. Самый эффективный: одно соединение обслуживает десятки клиентов. Ограничения: нельзя использовать prepared statements, временные таблицы, менять параметры сессии вне транзакции.
**Session pooling** (`pool_mode = session`) — соединение держится за клиентом до отключения. Аналог встроенного пула. Prepared statements работают, но эффективность ниже.
**Statement pooling** — соединение возвращается после каждого запроса. Практически бесполезен.
Встроенный пул в приложении всегда работает как session pooling и не может отдать соединение другому процессу.
## Когда pgbouncer необходим
**Микросервисы.** 10 сервисов × 5 реплик × 20 соединений = 1000 соединений к базе. PostgreSQL с `max_connections = 100` откажет. С pgbouncer он держит к базе 20-50 реальных соединений.
**Serverless.** AWS Lambda создаёт новый процесс на каждый запрос. Встроенный пул не работает. Pgbouncer в transaction mode переиспользует физические соединения.
**Автоскейлинг.** Когда подов растёт с 5 до 50 за минуту, база получает лавину соединений. С pgbouncer их количество стабильно.
Монолит в одном контейнере — встроенного пула достаточно.
## Настройка pool_size и max_connections: расчёты
Главная ошибка — ставить `max_connections` слишком большим. Каждое соединение съедает 5-10 МБ. Оптимум: 100-200.
Формула для pgbouncer:
```
max_connections >= default_pool_size × баз × пользователей
```
Пример: 3 базы, 2 пользователя, `default_pool_size = 25`:
```
max_connections >= 25 × 3 × 2 = 150
```
Ставим 200 с запасом.
Для клиентов:
```
max_client_conn >= инстансов × pool_size в приложении
```
10 инстансов × 20 = 200. Ставим `max_client_conn = 250`.
В transaction mode `default_pool_size = 10-20` достаточно для сотен клиентов.
## Prepared statements и pgbouncer: подводные камни
Prepared statements живут в рамках сессии. В transaction mode pgbouncer отправляет `DEALLOCATE ALL` при возврате соединения — кеш не работает.
Решение: отключить prepared statements в драйвере.
Node-postgres:
```javascript
const pool = new Pool({
host: 'pgbouncer-host',
port: 6432,
});
await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
```
Или использовать session mode для критичных запросов.
## Примеры конфигурации pgbouncer
Для микросервисов:
```ini
[databases]
myapp = host=postgres-primary.internal port=5432 dbname=myapp
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
default_pool_size = 20
min_pool_size = 5
max_client_conn = 1000
max_db_connections = 100
server_idle_timeout = 600
server_lifetime = 3600
```
Для serverless:
```ini
[pgbouncer]
pool_mode = transaction
default_pool_size = 10
max_client_conn = 500
server_idle_timeout = 60
server_reset_query = DISCARD ALL
```
Файл `/etc/pgbouncer/userlist.txt`:
```
"myapp_user" "md5d8f5a3b2c1e4d7a6b9c8e1f2a3b4c5d6"
```
Хеш: `echo -n "passwordusername" | md5sum`, добавляем префикс `md5`.
## Мониторинг
Pgbouncer предоставляет встроенную базу:
```sql
psql -h pgbouncer-host -p 6432 -U pgbouncer pgbouncer
SHOW POOLS; -- статистика пулов
SHOW CLIENTS; -- активные соединения
SHOW STATS; -- статистика по базам
```
Ключевые метрики:
- `cl_waiting` — клиенты в очереди. Если > 0 постоянно — увеличивайте `default_pool_size`.
- `sv_idle` — простаивающие соединения. Если всегда 0 — увеличивайте пул.
- `sv_active` — активные запросы. Если близко к `default_pool_size` — узкое место.
## Вывод
Встроенный пул работает для монолитов. Pgbouncer необходим для микросервисов, serverless и автоскейлинга.
Transaction mode даёт максимальную эффективность, но требует отказа от prepared statements. Session mode — компромисс.
Настройка: `max_connections` в PostgreSQL держите низким (100-200), `default_pool_size` в pgbouncer — 10-25 в transaction mode. Мониторьте `cl_waiting` и `sv_active`.
Не ставьте pgbouncer «на всякий случай». Если у вас один инстанс с 20 соединениями — встроенный пул справится. Pgbouncer нужен, когда клиентских соединений в разы больше оптимального количества серверных.