PostgreSQL: too many connections после увеличения числа воркеров
После добавления серверных воркеров приложение стало чаще отвечать ошибкой подключения к PostgreSQL. В каждом процессе pool_size всего пять — вроде немного. Но пул принадлежит конкретному Engine, а Engine обычно создаётся отдельно в каждом процессе. Складывать нужно все процессы, реплики и фоновые обработчики.
Предположим, у приложения три реплики по четыре воркера. В каждой сессии используется Engine с pool_size=5 и max_overflow=5. Потенциальный предел этой части системы — 3 × 4 × (5 + 5), то есть 120 соединений.
Считаем предел, а затем проверяем реальность
Это верхняя граница для такого QueuePool, не обещание, что сразу после старта откроются все 120 соединений. Но при пике нагрузки она объясняет, почему база с небольшим лимитом перестаёт принимать новых клиентов. К бюджету добавьте миграции, администрирование, другие приложения и запас на обновление, когда старые и новые реплики работают одновременно.
На PostgreSQL посмотрите настройки и активность подключённой роли:
SHOW max_connections;
SELECT application_name, usename, state, count(*)
FROM pg_stat_activity
GROUP BY application_name, usename, state
ORDER BY count(*) DESC;
Полнота видимых деталей зависит от прав роли. Запрос читает состояние и ничего не завершает. Полезно задавать application_name для разных компонентов: так API, воркер и админский клиент проще различить.
Большой пул не обязательно быстрее
Если база уже загружена тяжёлыми запросами, дополнительные соединения увеличат конкуренцию за её ресурсы. Поэтому первым исправлением не должно автоматически становиться увеличение max_connections. Сопоставьте время ожидания пула, время SQL-запросов и нагрузку базы.
У SQLAlchemy pool_timeout ограничивает ожидание свободного соединения из пула. Это другой таймаут, чем время выполнения SQL. max_overflow разрешает временно выйти за обычный размер пула; соединения сверх pool_size не сохраняются в нём после возврата.
Ищем удерживаемые соединения
Проверьте жизненный цикл Session: контекстный менеджер должен завершаться и при исключении. Не держите открытую транзакцию, пока приложение ждёт медленный внешний HTTP-сервис. Такой код может занимать соединение базы почти всё время ожидания сети.
Состояние idle in transaction заслуживает отдельного внимания: клиент ничего не выполняет, но транзакция остаётся открытой. Не завершайте массово все такие соединения наугад; сначала найдите владельца и причину.
После изменения лимитов повторите тот же нагрузочный сценарий. Хороший результат — предсказуемое ожидание, отсутствие отказов подключения и сохранение задержки в вашем целевом диапазоне. Если вы добавили параллелизм через gather, проверьте и правило отдельных AsyncSession.
Собрать следующий этап проекта
Чтобы запуск проекта не держался на наборе команд в вашей памяти, соберите его в Docker Compose. В курсе по Docker вы упакуете HTTP-сервис, подключите PostgreSQL и разберёте сети, тома, обновление приложения и восстановление данных.
Посмотреть программу: Docker и Docker Compose: контейнеризация приложений
Проверить детали: SQLAlchemy: настройки пула, PostgreSQL: pg_stat_activity.
Продолжить на практике
- Docker и Docker Compose: контейнеризация приложений ↗
Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.
- FastAPI для начинающих: API с базой данных и тестами ↗
Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.
