ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

PostgreSQL: как победить FATAL: too many clients — max_connections, reserved connections и pgbouncer

FATAL: too many clients в PostgreSQL приводит к отказам и таймаутам. Разбираем, кто съедает подключения в pg_stat_activity, почему опасно просто поднимать max_connections, как работают reserved connections и как внедрить pgbouncer.
PostgreSQL: как победить FATAL: too many clients — max_connections, reserved connections и pgbouncer

Что означает 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, дальше нужен разбор: кто держит соединения и почему они не освобождаются.

Диагностика подключений PostgreSQL через pg_stat_activity

Диагностика через 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 на продакшене, иначе в момент инцидента вы рискуете потерять возможность зайти «штатно» и будете лечить проблему в разы дольше.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Что делать прямо сейчас, если база уже упёрлась и сервис горит

Правильный порядок: сначала восстановить работоспособность, потом разбирать первопричину и устранять её системно. Если у вас есть доступ под суперпользователем, можно аккуратно завершить самые вредные сессии.

Например, завершить 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 без боли

  1. Зафиксируйте «до»: пики соединений, топ приложений по коннектам, долю idle и idle in transaction.
  2. Поставьте pgbouncer ближе к PostgreSQL или ближе к приложению (в зависимости от топологии и количества app‑нод).
  3. Поднимите pgbouncer на отдельном порту, протестируйте, затем переключите прод конфигом приложения.
  4. Ограничьте реальные подключения к PostgreSQL со стороны пула (например, через лимиты на БД/пользователя в pgbouncer) и только потом пересматривайте max_connections в PostgreSQL.

Идея простая: лучше получить очередь в пулере, чем падение базы с отказами в подключениях.

Схема работы pgbouncer: много клиентских соединений к меньшему числу соединений 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();"

Если у вас есть легальные длинные транзакции (например, миграции), лучше организационно отделить их (отдельный пользователь/окно работ), чем делать таймауты слишком агрессивными для всех.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Контроль: как убедиться, что проблема ушла

Проверяйте не только исчезновение ошибки, но и поведение системы под нагрузкой. Хороший признак после внедрения пула: число соединений к 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 под контролем.

Шпаргалка: безопасная стратегия действий

  1. Снимите картину через pg_stat_activity: кто и сколько соединений держит.
  2. Разберите долю idle и idle in transaction; найдите самые долгие транзакции.
  3. Если пожар: точечно завершите самые вредные сессии и временно ограничьте воркеры/фоновые джобы.
  4. Не лечите проблему бесконечным ростом max_connections без пересчёта ресурсов.
  5. Внедрите pgbouncer и выберите режим пула (обычно transaction), протестируйте на стейджинге.
  6. Добавьте таймауты и мониторинг, чтобы инцидент не повторился.
Поделиться статьей

Вам будет интересно

Grafana Tempo + Loki + Prometheus: корреляция traceID и быстрый triage инцидентов OpenAI Статья написана AI (GPT 5)

Grafana Tempo + Loki + Prometheus: корреляция traceID и быстрый triage инцидентов

Пошагово связываем Grafana Tempo, Loki и Prometheus в единую observability-схему: OpenTelemetry-трейсы, логи с traceID и метрики. ...
Kubernetes DNS: таймауты, MTU и conntrack — диагностика через CoreDNS, tcpdump и PMTUD OpenAI Статья написана AI (GPT 5)

Kubernetes DNS: таймауты, MTU и conntrack — диагностика через CoreDNS, tcpdump и PMTUD

Если в Kubernetes периодически не резолвится DNS или внешние API отвечают timeout, причина часто в MTU/PMTUD, conntrack и настройк ...
Kubernetes CrashLoopBackOff: события, пробы, exit codes и backoff — практический разбор OpenAI Статья написана AI (GPT 5)

Kubernetes CrashLoopBackOff: события, пробы, exit codes и backoff — практический разбор

CrashLoopBackOff в Kubernetes — не «ошибка», а симптом: контейнер быстро завершается, kubelet перезапускает его и увеличивает пауз ...