Что означает FATAL: too many clients и почему это не «просто лимит»
Сообщение FATAL: too many clients already появляется, когда PostgreSQL отказывает в новом подключении из‑за достижения max_connections. Важно: это почти всегда симптом, а не корень проблемы. Либо приложение открывает слишком много соединений, либо соединения «висят» в idle/idle in transaction, либо сам сервер по памяти не рассчитан на текущий уровень параллелизма.
Механика PostgreSQL классическая: «один клиент — один backend‑процесс». Даже если соединение простаивает, оно всё равно занимает ресурсы (процесс, память, дескрипторы) и увеличивает накладные расходы на планирование и переключения контекста.
Поэтому простое увеличение max_connections часто даёт краткий эффект, а затем приводит к деградации: растёт расход памяти, падает эффективность кешей, увеличиваются ожидания и в пике можно поймать OOM.
Цель — не «убрать ошибку», а сделать количество соединений управляемым: ограничить, переиспользовать (pooling) и не позволять всплескам трафика напрямую «пробивать» базу.
Быстрая проверка: разовый всплеск или хроническая история
Сначала уточните контекст: ошибка возникла во время деплоя, рестарта приложения, миграций или при пиковом трафике? Разовый всплеск можно пережить, но постоянный упор в лимит почти всегда означает необходимость внешнего пула (например, pgbouncer) и ревизии настроек в приложении.
Проверьте текущее значение max_connections и фактическое количество подключений:
psql -U postgres -d postgres -c "SHOW max_connections;"
psql -U postgres -d postgres -c "SELECT count(*) AS total, count(*) FILTER (WHERE state = 'active') AS active FROM pg_stat_activity;"
Если total близко к max_connections, дальше нужен разбор: кто держит соединения и почему они не освобождаются.

Диагностика через pg_stat_activity: ищем «пожирателей» подключений
Ваш основной инструмент — pg_stat_activity. Начните с разреза по базам и пользователям:
psql -U postgres -d postgres -c "SELECT datname, usename, count(*) AS conns FROM pg_stat_activity GROUP BY 1,2 ORDER BY conns DESC;"
Дальше — по приложениям. Поле application_name обычно помогает отделить веб, воркеры, админку, миграции:
psql -U postgres -d postgres -c "SELECT application_name, count(*) AS conns FROM pg_stat_activity GROUP BY 1 ORDER BY conns DESC;"
Посмотрите распределение по состояниям:
psql -U postgres -d postgres -c "SELECT state, count(*) FROM pg_stat_activity GROUP BY 1 ORDER BY 2 DESC;"
Сигналы, которые чаще всего приводят к too many clients:
- много
idle— обычно это «слишком большой пул» на стороне приложения или пул на каждом воркере; - много
idle in transaction— опасно: транзакции зависли без активности (часто забытыйCOMMIT/ROLLBACKили долгие операции между запросами); - десятки/сотни соединений с одинаковым
application_name— почти всегда проблема лимитов на стороне приложения/воркеров.
Чтобы быстро найти самые старые транзакции и «залипшие» сессии, выведите возраст транзакции и текущего состояния:
psql -U postgres -d postgres -c "SELECT pid, usename, datname, application_name, state, now() - xact_start AS xact_age, now() - state_change AS state_age, left(query, 120) AS query FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start ASC LIMIT 20;"
Если хочется глубже именно про pooling и режимы работы, держите шпаргалку по внедрению и настройкам: практическое руководство по pgbouncer и пулу подключений.
Reserved connections: почему «админ не может зайти»
PostgreSQL оставляет «форточку» для администратора: параметр superuser_reserved_connections. Когда занято max_connections - superuser_reserved_connections, обычные пользователи начинают получать отказ, но суперпользователь ещё может подключиться и провести диагностику.
Проверьте значение:
psql -U postgres -d postgres -c "SHOW superuser_reserved_connections;"
Практический совет: не ставьте superuser_reserved_connections в 0 на продакшене, иначе в момент инцидента вы рискуете потерять возможность зайти «штатно» и будете лечить проблему в разы дольше.
Что делать прямо сейчас, если база уже упёрлась и сервис горит
Правильный порядок: сначала восстановить работоспособность, потом разбирать первопричину и устранять её системно. Если у вас есть доступ под суперпользователем, можно аккуратно завершить самые вредные сессии.
Например, завершить idle in transaction старше 10 минут:
psql -U postgres -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction' AND now() - state_change > interval '10 minutes';"
Или закрыть «idle» соединения конкретного приложения (делайте только если уверены, что оно переподключится и это не оборвёт важные сессии админки/батчей):
psql -U postgres -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE application_name = 'myapp' AND state = 'idle' AND now() - state_change > interval '30 minutes';"
Параллельно снизьте давление на базу: временно уменьшите количество воркеров, выключите фоновые джобы, ограничьте параллелизм в очередях. Постоянные рестарты PostgreSQL обычно только усугубляют ситуацию (кэш прогревается заново, а приложение снова штормит подключениями).
Почему повышение max_connections — не серебряная пуля
Увеличивать max_connections имеет смысл только после оценки ресурсов. Любое «+N соединений» — это дополнительные процессы, дополнительная память и больше конкуренции за CPU и I/O. На загруженных системах рост max_connections часто превращает проблему «не пускаем новые коннекты» в проблему «всё стало медленно».
На практике рабочая стратегия такая:
- держать
max_connectionsв разумных пределах для конкретного сервера и профиля нагрузки; - в веб‑сценариях гасить лавину подключений внешним пулером (pgbouncer), а не расширять лимит бесконечно;
- ограничивать реальное число коннектов к PostgreSQL со стороны пула и давать очереди образовываться «перед базой», а не внутри неё.
Если вы крутите PostgreSQL на отдельном сервере, чаще всего удобнее и безопаснее делать это на VDS, где вы контролируете лимиты процессов, память и сетевой профиль, а не делите их с соседями.
pgbouncer: как pooling убирает too many clients
pgbouncer принимает много клиентских подключений и мультиплексирует их на меньшее число реальных соединений к PostgreSQL. Это особенно эффективно для коротких запросов (типичный веб), где цена открытия/содержания большого числа backend‑процессов слишком высока.
Что обычно улучшается после внедрения:
- резко снижается число backend‑процессов PostgreSQL;
- нагрузка становится предсказуемее: в пике растёт очередь в pgbouncer, а не хаос в базе;
- пропадают лавинообразные отказы
too many clientsпри всплесках трафика; - проще контролировать конкуренцию: лимиты выставляются «на входе».
Выбор режима пула: session, transaction, statement
Ключевой параметр pgbouncer — pool_mode:
- session — серверное соединение закрепляется за клиентом до закрытия (проще, но экономия меньше);
- transaction — соединение выдаётся на время транзакции и возвращается в пул (самый популярный режим для веба);
- statement — на время одного запроса (редко нужно и может ломать сценарии).
Для «веб + ORM» чаще всего подходит transaction. Но проверьте нюансы: временные таблицы, сессионные настройки через SET, работа с prepared statements — всё это нужно протестировать на стейджинге.
Если выбираете между пулерами или планируете миграцию, может пригодиться сравнение подходов: Odyssey vs pgbouncer: что выбрать и почему.
Базовый план внедрения pgbouncer без боли
- Зафиксируйте «до»: пики соединений, топ приложений по коннектам, долю
idleиidle in transaction. - Поставьте pgbouncer ближе к PostgreSQL или ближе к приложению (в зависимости от топологии и количества app‑нод).
- Поднимите pgbouncer на отдельном порту, протестируйте, затем переключите прод конфигом приложения.
- Ограничьте реальные подключения к PostgreSQL со стороны пула (например, через лимиты на БД/пользователя в pgbouncer) и только потом пересматривайте
max_connectionsв PostgreSQL.
Идея простая: лучше получить очередь в пулере, чем падение базы с отказами в подключениях.

Таймауты и «страховочные» настройки PostgreSQL
Pooling решает лавину соединений, но типовые аварии часто добиваются зависшими транзакциями и бесконечными запросами. Здесь помогают разумные таймауты:
idle_in_transaction_session_timeout— завершает сессии, которые зависли в транзакции без активности;statement_timeout— ограничивает максимальное время выполнения запроса (включайте осмысленно);log_connectionsиlog_disconnections— временно включить для расследования коннект‑шторма (потом отключить, чтобы не утонуть в логах).
Пример настройки таймаута для «idle in transaction» через ALTER SYSTEM:
psql -U postgres -d postgres -c "ALTER SYSTEM SET idle_in_transaction_session_timeout = '60s';"
psql -U postgres -d postgres -c "SELECT pg_reload_conf();"
Если у вас есть легальные длинные транзакции (например, миграции), лучше организационно отделить их (отдельный пользователь/окно работ), чем делать таймауты слишком агрессивными для всех.
Контроль: как убедиться, что проблема ушла
Проверяйте не только исчезновение ошибки, но и поведение системы под нагрузкой. Хороший признак после внедрения пула: число соединений к PostgreSQL перестаёт расти линейно с трафиком.
Быстрый срез по состояниям и ожиданиям:
psql -U postgres -d postgres -c "SELECT state, wait_event_type, wait_event, count(*) FROM pg_stat_activity GROUP BY 1,2,3 ORDER BY 4 DESC;"
Что хочется видеть в норме:
- соединений в целом мало и их число стабильно;
idle in transactionблизко к нулю (или строго контролируется);- в пики растут очереди «перед базой» (на стороне приложения/pgbouncer), а не число backend‑процессов.
Типовые причины too many clients в приложениях (и как чинить)
- Слишком большой пул в каждом процессе: 20 воркеров по 20 соединений = 400 соединений даже без пользы.
- Нет лимита параллелизма у воркеров: очереди/кроны стартуют одновременно и умножают подключения.
- Переподключения в цикле: ретраи без бэк‑оффа при таймаутах и сетевых сбоях.
- idle in transaction: ошибки в коде, незакрытые транзакции, долгие операции между запросами.
Базовый минимум в приложении: ограничить размер пула, настроить таймаут на получение соединения, и добавить экспоненциальный бэк‑офф на ретраи. На уровне инфраструктуры: поставить pgbouncer и держать число реальных соединений к PostgreSQL под контролем.
Шпаргалка: безопасная стратегия действий
- Снимите картину через
pg_stat_activity: кто и сколько соединений держит. - Разберите долю
idleиidle in transaction; найдите самые долгие транзакции. - Если пожар: точечно завершите самые вредные сессии и временно ограничьте воркеры/фоновые джобы.
- Не лечите проблему бесконечным ростом
max_connectionsбез пересчёта ресурсов. - Внедрите pgbouncer и выберите режим пула (обычно
transaction), протестируйте на стейджинге. - Добавьте таймауты и мониторинг, чтобы инцидент не повторился.


