08 февр. 2026 г.·5 мин чтения

Лимиты подключений к базе данных, которые тормозят рост задолго до роста CPU

Лимиты подключений к базе могут блокировать трафик задолго до роста CPU. Узнайте, как пула, серверные лимиты и фоновые задачи создают скрытые очереди.

Лимиты подключений к базе данных, которые тормозят рост задолго до роста CPU

Почему пользователи ждут, а база данных выглядит бездействующей

Пользователи могут получить таймаут, хотя CPU базы низкий, диск тихий, а графики запросов почти пустые. Это кажется нелогичным, пока вы не вспомните, что происходит до выполнения самого SQL: запросу сначала нужен доступ к соединению с базой.

Если свободного подключения нет, запрос ждёт. В базе ещё ничего тяжёлого не началось: не стартовала дорогая операция, CPU остаётся спокойным. Со стороны пользователя приложение уже застопорилось.

Вот почему лимиты подключений часто дают сбои ещё до того, как сервер начинает выглядеть загруженным. Узкое место не всегда в скорости выполнения запросов. Иногда реальное ограничение — число открытых слотов между приложением, его пулом и сервером базы.

Полный пул сам по себе может блокировать трафик. Если в приложении разрешено 40 подключений и все 40 заняты, 41‑й запрос ждёт. Если это ожидание длиннее таймаута приложения, пользователь получает ошибку, даже если база всё это время почти ничего не делала.

Спокойные графики могут скрывать несколько разных проблем. Запросы могут ждать в пуле приложения, а не внутри базы. Небольшое число медленных запросов может держать подключения дольше, чем ожидается. Фоновые воркеры могут занимать слоты и оставлять меньше места для живого трафика. В системах с несколькими инстансами приложения у каждого свой пул, поэтому суммарное число подключений растёт быстрее, чем кажется.

Низкий CPU лишь говорит, что база не тратит много ресурсов. Он не говорит, что запросы проходят гладко.

Обычно это проявляется постепенно. Трафик растёт, добавляются инстансы приложения, и каждый открывает свой пул. Маленькие задержки накапливаются. Сначала замедляются логины, оформления покупок или ответы API. Затем при нагрузке появляются случайные таймауты, которые пропадают при снижении трафика.

Задача — найти, где заканчиваются подключения. Проверьте пул приложения, серверный лимит и любые воркеры, которые пользуются той же базой. Пока не найдёте узкое место, спокойный график может сильно вводить в заблуждение.

Где накапливаются подключения

Подключения редко скапливаются в одном очевидном месте. Они распределяются по всем процессам, которые разговаривают с базой, поэтому итог часто удивляет команды.

Начните с перечисления всех источников подключений, а не только основного приложения. Многие команды считают только веб‑серверы и на этом останавливаются. Пропущенные слоты часто сидят где‑то тише:

  • инстансы веб‑приложения за балансиром нагрузки
  • воркеры и потребители очередей
  • cron‑задачи, импорты и генераторы отчётов
  • админ‑панели, инструменты поддержки и дашборды
  • сессии разработчиков, скрипты и задачи деплоя

Каждый из этих процессов может держать свой пул. Эта деталь важнее, чем большинство ожидают. Пул на 10 кажется маленьким, пока 10 отдельных процессов не держат по 10 подключений каждый. В итоге база уже имеет 100 открытых сессий ещё до начала часа пик.

Контейнерные настройки усугубляют ситуацию. Если вы масштабируете приложение с двух инстансов до шести, вы добавляете не только CPU. Возможно, вы утроили число потенциальных подключений, потому что каждый новый инстанс запустил пул с теми же настройками по умолчанию.

Математика быстро растёт. Четыре веб‑инстанса с пулом 20 — это 80 подключений. Шесть воркеров с пулом 10 добавляют ещё 60. Задача отчётности, миграция и пара админ‑сессий могут подтолкнуть сумму к 150, даже если многие процессы в основном простаивают.

Поэтому проблемы с лимитами подключений часто кажутся случайными. Трафик выглядит нормально, запросы в порядке, а база отказывается от новой работы, потому что слишком много тихих процессов уже заняли слоты. Считайте пулы в первую очередь. Затем считайте запросы.

Как пула и серверные лимиты сталкиваются

Пул подключений — это набор соединений, которые каждый процесс приложения держит готовыми. Когда приходит запрос, приложение берёт одно из этих соединений, вместо того чтобы открывать новое «с нуля».

У сервера базы есть свой жёсткий предел. В PostgreSQL max_connections задаёт, сколько открытых подключений сервер допускает одновременно. Когда приложения, воркеры, скрипты, дашборды и админ‑инструменты вместе запрашивают больше этого числа, новая работа ждёт или падает, даже если CPU остаётся низким.

Именно это столкновение и вызывает проблемы. Приложение думает, что ему достаточно 20–30 подключений. Сервер думает, что все слоты уже разданы. Оба значения по отдельности могут выглядеть разумно и всё же конфликтовать в продакшне.

Бездействующие подключения усугубляют ситуацию. Даже когда соединение ничего не делает, оно занимает слот на сервере до тех пор, пока приложение его не закроет или пул не сбросит.

Небольшое масштабирование может спровоцировать проблему. Один инстанс приложения с пулом 20 может нормально работать против сервера с max_connections = 100. Но когда трафик растёт и вы запускаете четыре инстанса приложения, два воркера и задачу отчёта, суммарное требование может превысить лимит:

  • 4 веб‑инстанса x 20 слотов пула = 80
  • 2 воркера x 15 слотов пула = 30
  • отчёты и админ = ещё несколько

Теперь система может попросить 110–115 подключений, и у базы уже не останется свободных мест. Если у Postgres разрешено только 100, некоторые запросы встают в очередь, хотя сами SQL‑запросы быстры.

Настройки по умолчанию часто провоцируют это. Фреймворки поставляются с размерами пулов, которые кажутся безопасными на одной машине. Управляемые базы могут резервировать часть подключений для внутренней работы, мониторинга или админ‑доступа. Команды затем добавляют автоскейлинг, воркеров и превью‑окружения, но никто не пересчитывает полный бюджет подключений.

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

Поэтому размер пулов заслуживает того же внимания, что и подбор серверов. Настройки пула — не локальная мелочь. Они умножаются на каждый запущенный процесс.

Простой пример для растущего продукта

Предположим небольшое SaaS‑приложение с тремя веб‑серверами и двумя фоновых воркерами. Ничто не кажется большим. Команда по‑прежнему считает, что база в запасе, потому что CPU держится ниже 25% в течение дня.

У каждого веб‑сервера пул на 20. Это даёт 60 возможных веб‑подключений.

У каждого воркера пул на 15. Это ещё 30.

Итого 90. Но база имеет max_connections = 80, и несколько слотов лучше держать для админов и внутренних нужд. В практике приложению остаётся около 75 полезных слотов.

Сначала трафик лёгкий — 20–25 активных подключений, и всё работает. Страницы загружаются быстро, задачи выполняются в фоне, а дашборд базы выглядит спокойно.

Потом продукт растёт. Утренний трафик увеличивается с 150 до 400 активных пользователей. Параллельно фоновые задания запускают экспорт счётов, эмаил‑кампании и синхронизации. Эти работы не сильно нагружают CPU, но держат подключения по 30–40 секунд.

Баланс меняется. Веб‑трафик требует 45–50 подключений. Воркеры занимают ещё 25. Пару медленных запросов держат слоты. Вдруг приложение приближается к реальному лимиту 75, хотя сервер всё ещё выглядит спокойным.

Пользователи это ощущают первыми. Запрос пришёл, но веб‑приложение не может получить свободное соединение из пула. Запрос ждёт в очереди. После длительного ожидания страницы замедляются или таймаутятся.

Снаружи это выглядит странно: CPU в порядке, память в порядке, диск тих. Но живой трафик всё равно стоит, потому что очередь сидит перед базой, а не внутри движка базы данных.

Вот почему нагрузка фоновых задач на базу так важна в растущем продукте. Столкнуться со стеной можно задолго до того, как сервер начнёт выглядеть загруженным.

Как пошагово найти узкое место

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

Пользователи могут упереться в лимит задолго до того, как PostgreSQL разогреется. Если приложение ждёт свободного подключения, CPU остаётся низким, а графики запросов нормальными. Для диагностики начните с лимитов и двигайтесь наружу.

  1. Выпишите реальный бюджет подключений. Начните с max_connections, затем вычтите место для админов, мониторинга, миграций и всего, что должно работать под нагрузкой.
  2. Посчитайте каждый процесс, способный открыть подключение: веб‑инстансы, воркеры, планировщики, cron‑задачи, дашборды, скрипты, деплои и временное наложение при релизах.
  3. Сравните возможную сумму с безопасным бюджетом базы. Не используйте средний трафик. Берите наибольшее число реплик и самый загруженный час, который вы ожидаете.
  4. Измерьте время ожидания до запуска SQL. Если запросы тратят 200 мс или 2 секунды на ожидание до начала SQL, проблема ещё не в самом запросе — приложение ждёт подключения.
  5. Посмотрите временные паттерны. Если всплески подключений совпадают с деплоями, ретраями или пакетными заданиями, вы нашли точку давления.

Небольшой пример упрощает понимание. Допустим, база разрешает 100 подключений, но приложению безопасно использовать только 80. Четыре инстанса приложения по 12 подключений — это 48. Три воркера по 10 — ещё 30. Уже 78. При деплое старые инстансы могут оставаться живыми минуту или две, пока новые стартуют. Даже при почти неизменном трафике суммарное число может прыгнуть выше лимита.

Здесь диагностика обычно идёт неверно: команды смотрят только на длительность запросов и CPU и обвиняют движок базы. Реальная проблема часто на шаг раньше.

Ведите заметки по времени, а не только по сервису. График количества подключений рядом с окнами деплоя, всплесками ретраев и запланированными заданиями часто полезнее ещё одного графика CPU.

Когда фоновые задачи крадут слоты у живого трафика

Аудит всех клиентов базы данных
Перечислите все процессы, открывающие подключения, и постройте реальный бюджет для продакшна.

База может выглядеть спокойной по CPU и всё равно отказывать пользователям, если воркеры занимают слишком много подключений. Это типично для растущих продуктов: воркеров легко добавить и легко забыть.

Обычные нарушители — не просмотры страниц, а фоновые задачи, которые просыпаются пачками и одновременно запрашивают подключение. Это импорты, синхронизации с третьими сервисами, повторы вебхуков, генерация отчётов, переиндексация, расчёт счётов и подготовка почтовых кампаний.

Один импорт кажется безобидным. Десять воркеров, запущенных параллельно, могут заполнить пул ещё до того, как реальные пользователи нажмут что‑то. Аналогично ретрай‑штормы: если внешний API падает 15 минут, воркеры могут продолжать пытаться выполнить старые задания, пока свежие запросы стоят в очереди.

Синхронизации создают постоянную нагрузку. Продукт, который подтягивает обновления из CRM, биллинга и склада каждые несколько минут, может поддерживать постоянное давление на подключения в течение дня. Каждый воркер держит соединение дольше обычной загрузки страницы, особенно если читает пакет, пишет строки и ждёт сетевых ответов.

Несколько правил сильно помогают. Держите конкурентность воркеров ниже, чем у веба. Дайте тяжёлым очередям отдельный, меньший пул. Обрабатывайте задания небольшими партиями, а не гигантскими пачками. Замедляйте или приостанавливайте ретраи при всплесках ошибок. Оставляйте часть бюджета подключений свободной намеренно, чтобы живой трафик имел запас.

Хорошее стартовое правило: резервировать около 20–30% доступных подключений для пользовательских запросов в часы пик и не позволять воркерам бороться за всё это место. Точная цифра зависит от продукта, но сама привычка важнее процента.

Размер партий тоже имеет значение. Партия из 100 строк часто ведёт себя лучше, чем 10 000, если каждая партия открывает транзакцию, пишет, коммитит и быстро освобождает подключение. Меньшие партии обычно делают очереди предсказуемее.

Если у вас несколько типов воркеров, обращайтесь с ними по‑разному. Импорты могут ждать. Checkout, логин и чтение API обычно не могут. База сама не отличит эти типы — приоритеты нужно задавать вручную через отдельные пулы, лимиты очередей и расписания.

Ошибки, которые ухудшают очередь

Самое распространённое неправильное решение — считать любой таймаут признаком того, что базе нужно больше слотов.

Поднятие max_connections в Postgres может помочь, но только если у сервера достаточно памяти и нагрузка уже под контролем. Каждое дополнительное подключение требует RAM. Большее число активных запросов может увеличить чтения, блокировки и промахи кеша. CPU может по‑прежнему выглядеть нормально, а время ответа — ухудшаться.

Ещё одна ошибка — масштабировать инстансы приложения, не меняя дефолтные размеры пулов. Один сервер с пулом 20 может быть в порядке. Десять серверов с тем же пулом требуют уже 200 подключений плюс воркеры и админ‑инструменты.

Длинные транзакции наносят иной ущерб. Если запрос открывает транзакцию, а затем ждёт медленного API, загрузки файла или генерации отчёта, это подключение занято всё это время. Иногда оно держит блокировки, так что другие запросы ждут за работой, которая даже не использует базу.

Ретрай‑штормы ещё хуже. Один таймаут вызывает ретрай из приложения, ещё один из воркера и иногда ещё один из клиента. База получает одно и то же неудачное задание снова и снова, и короткая заминка превращается в пробку.

Более безопасный ответ менее драматичен и эффективнее. Посчитайте суммарные возможные подключения веба, воркеров, cron‑задач и админ‑инструментов. Проверьте, сколько сессий простаивают в транзакциях или ждут блокировок. Снизьте размеры пулов перед добавлением инстансов. Добавьте backoff и жёсткие лимиты ретраев, чтобы ошибки не умножались.

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

Часто задаваемые вопросы

Почему пользователи таймаутятся, когда загрузка CPU базы данных низкая?

Потому что запрос может ждать свободного подключения к базе данных, а не выполнения SQL. Пока приложение не получит слот из пула, в базе почти ничего не происходит, поэтому CPU и диск остаются спокойными, но пользователи всё равно ждут.

Именно поэтому спокойные графики обманывают: очередь часто находится в приложении или пуле подключений, ещё до того, как запрос дойдёт до движка базы данных.

Как понять, ждёт ли приложение подключения или выполняется медленный запрос?

Измерьте время до начала выполнения SQL. Если запрос тратит много времени на ожидание, а сам запрос выполняется быстро после старта, скорее всего узким местом является подключение.

Проверьте логи приложения, трассировку или тайминги запросов вокруг выдачи соединения. Логи медленных запросов покажут только то, что происходит после того, как приложение уже получило подключение.

Что нужно учитывать при оценке общего числа подключений к базе данных?

Учтите каждый процесс, который одновременно может говорить с базой: веб‑инстансы, воркеры, cron‑задачи, отчёты, админ‑панели, деплой‑таски, скрипты и сессии разработчиков.

Используйте максимально возможное число реплик при пиковом трафике, а не среднее. Маленькие пулы быстро суммируются, когда у каждого процесса свой собственный пул.

Почему проблемы с подключениями ухудшаются после масштабирования приложения?

Потому что каждый новый инстанс приложения обычно запускает собственный пул. Масштаб от двух инстансов до шести — это не только больше CPU, но и умножение возможных подключений.

Это может протолкнуть вас за пределы лимита сервера, даже если трафик выглядит нормальным. Приложение растёт тихо, а база быстро теряет доступные слоты.

Могут ли простые (idle) подключения действительно быть проблемой?

Да. Даже бездействующее подключение занимает слот на сервере базы, пока приложение его не закроет или пока пул не сбросит соединение.

Слишком много простых сессий отнимают место, которое могли бы занять живые запросы. Если число простых подключений остаётся высоким после падения трафика, значит пулы слишком велики или процессы держат слоты дольше, чем нужно.

Как лучше распределять подключения между веб‑трафиком и фоновыми задачами?

Сначала зарезервируйте место для пользовательских запросов, а затем подгоняйте воркеры под оставшийся бюджет. Хорошая точка старта — оставить порядка 20–30% доступных подключений для пользователей в часы пик и не позволять воркерам занимать всё это пространство.

Отдельные пулы для тяжёлых задач помогают: импорты и отчёты могут ждать, а логины и платежи — нет. База сама не расставит приоритеты, это надо делать на уровне архитектуры.

Стоит ли просто поднять max_connections в PostgreSQL?

Не сразу. Повышение max_connections может помочь, но требует больше RAM и при слишком большом числе параллельных запросов может ухудшить время отклика.

Сначала сделайте математику: уменьшите завышенные пулы, ограничьте воркеры, укоротите длинные транзакции и остановите штормы ретраев. Увеличивайте серверный лимит только когда машина готова и нагрузка под контролем.

Какие фоновые задания чаще всего крадут слоты подключения у живого трафика?

Пакетные задания, импорты, ретраи, синхронизации и генерирование отчётов обычно крадут слоты. Они могут не жечь CPU, но держат подключения длительное время, особенно если ждут внешних API или обрабатывают большие куски данных.

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

Какие самые быстрые проверки перед изменением настроек?

Начните с арифметики: выпишите максимальное число из max_connections, оставив место для админов, мониторинга и задач деплоя. Сравните этот бюджет с суммой максимальных подключений всех приложений и воркеров.

Затем посмотрите на высокие значения idle, длинные транзакции и пики в окнах деплоя или плановых задач — они обычно показывают точку давления быстрее, чем очередной график CPU.

Что делать, если лимиты подключений продолжают вызывать случайные таймауты?

Настройте размеры пулов исходя из реальной конкурентности, а не дефолтов фреймворка. Протестируйте под реалистичным пикем, включая обычные страницы, всплеск логинов и фоновые задания, которые обычно запускаются одновременно.

Если после этого числа не сходятся, привлеките кого‑то, кто посмотрит пулы, лимиты воркеров и серверные лимиты вместе; это часто экономит время и деньги по сравнению с бессмысленным добавлением серверов.