Что такое connection pooling и зачем он нужен при работе с базой данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Connection Pooling при работе с базой данных
Connection pooling - это техника управления соединениями с базой данных, при которой создается и поддерживается пул заранее открытых соединений, которые переиспользуются между запросами вместо создания нового соединения на каждый запрос.
Проблема без пула
Создание TCP-соединения с базой данных - дорогая операция:
- TCP handshake (3 пакета)
- SSL/TLS handshake (еще 2-4 пакета)
- Аутентификация в СУБД
- Выделение ресурсов на стороне сервера БД
Это занимает 20-100мс на каждое соединение. При 1000 запросов в секунду без пула:
// Плохо - новое соединение на каждый запрос
async function getUser(id: string) {
const client = new Client({ connectionString: DATABASE_URL });
await client.connect(); // 50мс каждый раз
const result = await client.query("SELECT * FROM users WHERE id = $1", [id]);
await client.end();
return result.rows[0];
}
Как работает пул
Пул поддерживает N открытых соединений. Когда приложению нужно сделать запрос:
- Берет свободное соединение из пула
- Выполняет запрос
- Возвращает соединение обратно в пул
import { Pool } from "pg";
const pool = new Pool({
connectionString: DATABASE_URL,
max: 20,
min: 5,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000
});
async function getUser(id: string) {
const result = await pool.query(
"SELECT * FROM users WHERE id = $1", [id]
);
return result.rows[0];
}
Параметры пула
max (размер пула): Формула для PostgreSQL:
max_connections = (число ядер CPU * 2) + количество дисков
Для типичного сервера с 4 ядрами: pool.max = 10-20. Больше - не значит лучше: при 100+ соединениях PostgreSQL деградирует из-за контекстного переключения.
min: Минимум idle-соединений. Предотвращает задержку при первых запросах после периода неактивности.
idleTimeoutMillis: Время жизни неиспользуемого соединения.
Connection Pool в разных библиотеках
node-postgres (pg):
const pool = new Pool({ max: 20 });
// Автоматическое управление
await pool.query("SELECT 1");
// Ручное управление (для транзакций)
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query("UPDATE accounts SET balance = balance - 100 WHERE id = $1", [fromId]);
await client.query("UPDATE accounts SET balance = balance + 100 WHERE id = $1", [toId]);
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release(); // ОБЯЗАТЕЛЬНО вернуть в пул!
}
Prisma:
// Prisma управляет пулом автоматически
// Настройка через connection string
// postgresql://user:pass@host/db?connection_limit=20&pool_timeout=10
Проблемы и антипаттерны
- Connection leak: забыли вызвать
client.release()- соединение "утекло" из пула. Решение: всегда используйтеtry/finally - Слишком большой пул: N инстансов x M соединений = N*M соединений к БД. 10 инстансов по 20 = 200 соединений
- Внешний пул (PgBouncer): при множестве инстансов лучше использовать внешний connection pooler
PgBouncer
Для масштабирования используют PgBouncer - внешний connection pooler:
App1 (pool=20) --+
App2 (pool=20) --+-- PgBouncer (max=50) -- PostgreSQL
App3 (pool=20) --+
Bouncer мультиплексирует 60 входящих соединений в 50 реальных к PostgreSQL, используя режимы: session, transaction или statement.