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

Почему всплески очереди мешают запросам клиентов
Многие приложения отправляют запросы клиентов и фоновых воркеров в одну и ту же базу Postgres. Это обычная схема. Но у неё есть тихий риск: когда очередь внезапно заполняется, воркеры могут занять большую часть открытых соединений ещё до того, как клиенты нажмут на следующую страницу.
Это происходит очень быстро. Пул воркеров, который в простое выглядит безобидно, может проснуться и одновременно взять десятки задач. Каждый воркер открывает соединение, начинает читать или писать данные и держит этот слот достаточно долго, чтобы обычный трафик приложения начал ждать.
Пользователи чувствуют проблему ещё до того, как кто-то скажет: «база лежит». Страницы открываются медленно. Оформление заказа зависает. Кнопка сохранения крутится и потом выдаёт ошибку. В поддержку приходят обращения не с фразой «вы использовали все соединения», а с «сайт ведёт себя странно».
Вот почему лимиты подключений Postgres так важны для систем на очередях. База может технически оставаться живой, а продукт — ощущаться сломанным. CPU может выглядеть нормально. Память тоже. Приложение может даже проходить health check. Но если запрос клиента не может быстро получить соединение, сервис всё равно ломается там, где это важно.
Простой пример делает это очевидным. Представьте приложение, у которого после админ-инструментов и задач обслуживания остаётся 40 подключений для обычного трафика. Затем в очередь AI-воркеров падает backlog, и 30 воркеров одновременно просыпаются. Воркеры делают полезную работу, но оставляют слишком мало места для реальных пользователей. Через несколько секунд запросы клиентов начинают выстраиваться за ними.
Первые признаки обычно легко пропустить:
- время ответа растёт на страницах для вошедших пользователей
- действия, которые пишут в базу, начинают зависать по таймауту
- логика повторных попыток делает всплеск хуже, добавляя ещё больше нагрузки
Именно поэтому команды часто удивляются. Они смотрят, «живой» ли Postgres, хотя нужно смотреть, могут ли клиенты продолжать получать обслуживание во время всплеска. Здоровая система — это не та, где фоновые задачи быстро завершаются. Это та, где фоновые задачи укладываются в рамки, пока клиенты продолжают работать.
Где начинается давление на соединения
Обычно давление на соединения начинается не в самом Postgres, а в настройках приложения. Команда задаёт размер пула, который на одном процессе кажется небольшим, а потом забывает, что у базы есть только один жёсткий предел. Лимиты подключений Postgres не заботятся о том, какой сервис первым открыл сессию.
Обычная ловушка проста: у каждого процесса воркера свой пул. Это касается веб-серверов, потребителей очередей, cron-задач, обработчиков ретраев и одноразовых скриптов. Пул на 5 соединений выглядит безобидно, пока 30 процессов одновременно не решат, что им тоже нужно по 5.
С AI-очередями это становится ещё хуже, потому что они часто масштабируются по размеру backlog. Приходит всплеск, очередь растёт, autoscaling добавляет новых воркеров, и каждый новый воркер открывает свой небольшой пул. Потом подключаются ретраи, и спрос на соединения растёт гораздо быстрее, чем число задач.
Короткий пример показывает, почему команды удивляются:
- 12 веб-процессов с пулом 10 = 120 соединений
- 10 AI-воркеров с пулом 5 = 50 соединений
- 8 воркеров для ретраев или пакетной обработки с пулом 5 = 40 соединений
- 4 админских или отчётных задачи с пулом 3 = 12 соединений
Итого 222 возможных соединения ещё до того, как Postgres оставит запас на обслуживание, миграции или мониторинг.
Самая болезненная часть — не только общее число. Важно, кто доберётся до соединений первым. Низкоприоритетные фоновые задачи могут занять соединения раньше, чем вход в систему, оформление заказа или API-вызовы клиентов успеют запросить своё. Пользователи чувствуют сбой первыми, даже когда база всё ещё жива и фоновые задачи продолжают работать.
Ретрайи добавляют ещё один слой ущерба. Когда задача падает потому, что не смогла быстро получить соединение, многие системы ставят её в очередь снова. Воркер возвращается, снова просит соединение и усиливает давление на то же самое узкое место.
Вот почему даже «маленькие» пулы приводят к большим инцидентам. Проблема не в одном типе воркеров по отдельности. Проблема — в суммарном бюджете по типам воркеров, умноженном на autoscaling и ретраи, без приоритета для трафика клиентов.
Сначала определите бюджет соединений
Начните с числа, которое Postgres реально может держать: max_connections. Это жёсткий потолок для каждого app server, воркера, скрипта и админской сессии, которые подключаются к базе. Если сервер разрешает 200 соединений, воркеры не получают все 200. Это общий лимит для всех.
Именно здесь лимиты подключений Postgres перестают быть абстрактными. Если вы считаете только для загруженной AI-очереди, запросам клиентов придётся бороться за последние свободные слоты во время пика. Итог неприятный: запросы на вход зависают, страницы не открываются, а команда не может даже открыть консоль базы, когда что-то идёт не так.
Сначала зарезервируйте место для обычного продуктового трафика. После этого оставьте несколько соединений для админ-доступа, миграций и работ по обслуживанию. Вам также нужен запас на всплески и медленные запросы, потому что соединение, которое держится 30 секунд, мешает сильнее, чем то, которое завершается за 50 миллисекунд.
Запишите цифры до того, как менять настройки воркеров. Простая таблица делает обсуждение честным и убирает догадки.
| Группа | Соединения | Примечание |
|---|---|---|
| Веб-приложение | 80 | Трафик клиентов в загруженные периоды |
| Админ-доступ | 5 | Консоль, отладка, экстренный доступ |
| Обслуживание | 10 | Миграции, cron-задачи, vacuum, релизы |
| AI-воркеры | 60 | Общий пул для потребителей очередей |
| Запас | 45 | Всплески, ретраи, медленные запросы |
| Итого | 200 | Соответствует max_connections |
Даже если вы используете pooler, считайте по реальному потолку базы, а не по большему клиентскому пулу. У базы всё равно есть фиксированное число серверных соединений, и именно оно решает, останется система спокойной или начнёт задыхаться.
Записанный бюджет также делает компромиссы очевидными. Если вам нужно ещё 20 соединений для воркеров, значит, их нужно взять где-то ещё или безопасно увеличить мощность. Это гораздо лучше, чем позволить фоновым задачам забирать всё, что можно, и надеяться, что трафик клиентов выживет.
Задайте лимиты для каждого типа воркеров
Лимиты подключений Postgres работают только тогда, когда у каждого типа воркеров есть свой бюджет. Если все задачи берут соединения из одного общего пула, побеждает самая шумная очередь, а запросы клиентов ждут.
Сначала перечислите каждый тип воркера и его обычную параллельность. Используйте реальные цифры из обычного часа, а не оценки на память.
- асинхронные задачи для пользователей
- воркеры обработки AI
- импорт и экспорт
- ретраи и повторные запуски
- backfill, отчёты и очистка
Потом расставьте эти группы по влиянию на пользователя. Задача, которая завершает действие клиента в ближайшие несколько секунд, должна быть ближе к началу списка. Отчёт, reindex или backfill — ближе к концу, даже если команда считает их очень важными.
Для каждой группы задайте два жёстких ограничения. Первое — лимит пула базы. Второе — лимит очереди или параллельности воркеров. Нужны оба.
Если воркер может одновременно выполнять 30 задач, а лимит пула у него 6, эти задачи будут просто ждать соединений. Это всё равно расходует CPU, память и время очереди. Сопоставляйте параллельность воркера с бюджетом пула, чтобы приложение ломалось спокойно, а не накапливало скрытые ожидания.
Хорошо работает простое правило: если одной задаче обычно нужно одно соединение к базе, ограничьте параллельность на уровне или ниже размера пула для этой группы воркеров. Если одной задаче иногда нужны два соединения, учитывайте это честно и снижайте лимит.
После этого запустите тест всплеска. Дайте очереди нагрузку, которая в несколько раз выше обычной, и посмотрите, что происходит с задержкой запросов, временем ожидания соединений и отставанием задач. Если трафик клиентов замедляется, сначала уменьшайте лимиты низкоприоритетных задач. Не повышайте все лимиты только потому, что задачи выглядят медленными.
Низкоприоритетные задачи тоже должны притормаживать, когда очередь растёт. Поставьте их на паузу на минуту, уменьшите параллельность или перестаньте забирать новую работу, пока база не успокоится. Одно это правило часто спасает трафик клиентов во время тяжёлых AI-очередей, особенно когда пакетные задачи и живые запросы используют один и тот же сервер Postgres.
Простой пример с тремя группами воркеров
Представим SaaS-продукт, где одна и та же база Postgres обслуживает три вида работы. Днём клиенты пользуются веб-приложением. В фоне воркеры для email отправляют квитанции и напоминания. Ещё одна очередь создаёт embeddings после загрузки документов. Все три задачи важны, но при росте нагрузки они не заслуживают одинакового доступа.
Команда решает, что база безопасно выдерживает 80 соединений. Это число они считают жёстким пределом. Как только система его превышает, ожидание растёт, появляются таймауты, и веб-приложение быстро начинает казаться сломанным.
Один бюджет на три группы
Практичное распределение может выглядеть так:
- Веб-запросы: 50
- Email-задачи: 15
- Embedding-задачи: 10
- Резерв для админских задач и коротких всплесков: 5
Такая схема отдаёт наибольшую долю трафику клиентов, потому что задержку здесь замечают сразу. Email может подождать немного. Embedding-задачи могут подождать ещё дольше, особенно после большой загрузки или импорта.
Email-задачи находятся посередине. Сбросы пароля и квитанции всё равно должны уходить вовремя, но большой пакет рассылки не должен конкурировать с живыми действиями клиентов. Если трафик растёт, команда может в первую очередь замедлить массовую почту и оставить только срочные задачи.
Теперь представьте, что клиент днём импортирует 20 000 записей. Этот импорт создаёт поток embedding-задач. Без лимита эти воркеры могли бы открыть десятки соединений и вытеснить обычный трафик. Страницы начинают зависать. Сохранения не проходят. Обращения в поддержку появляются уже через несколько минут.
С бюджетом пулов по типам воркеров импорт не перехватывает всю базу. Embedding-воркеры остаются в пределах своих 10 соединений. Если очередь растёт, задачи ждут своей очереди или количество воркеров уменьшается. Импорт завершается позже, но продукт продолжает работать для всех остальных.
В этом и смысл лимитов подключений Postgres в загруженной системе на очередях. Вы сами выбираете, какая работа подождёт. Клиенты всё ещё могут входить, искать и обновлять данные, пока фоновые задачи завершаются с контролируемой скоростью.
Держите фоновые задачи в своей зоне
Когда фоновые задачи делят один пул с запросами клиентов, база не может отличить срочную работу от необязательной. Во время большого импорта, повторной обработки или всплеска AI-очереди массовые воркеры могут забрать большую часть открытых сессий и заставить обычные запросы ждать.
Не стоит решать проблему лимитов подключений Postgres просто повышением потолка каждый раз. Лучше разделять нагрузки. Дайте веб-запросам свой пул, срочным задачам — меньший пул, а массовым задачам — самый маленький пул из всех.
Такой бюджет по типам воркеров ставит ограждение вокруг каждого класса работы. Если массовые задачи резко активизируются, они ждут своих слотов вместо того, чтобы забирать ёмкость у входа в систему, оформления заказа, поиска или API-вызовов.
Медленным пакетным задачам тоже нужна отдельная очередь. Backfill, пересборка отчётов, крупные синхронизации и обновления embeddings часто работают минуты и затрагивают много строк. Их не стоит ставить рядом с короткими задачами вроде отправки квитанции или обновления одного заказа.
Параллельность должна соответствовать «весу» запроса. Воркер, который читает одну строку и пишет одну строку, обычно может запускать много копий одновременно. Воркер, который сканирует большую таблицу, сортирует много данных или держит соединение 20 секунд, нуждается в гораздо более низком лимите.
Часто хорошо работает такая схема:
- Отдайте самый большой пул веб-трафику.
- Дайте срочным фоновым задачам фиксированный, меньший пул.
- Поместите массовые задачи в отдельную очередь с жёстким лимитом.
- Сначала снижайте параллельность у задач, которые читают много строк.
- Разносите ретраи по времени с помощью backoff и случайной задержки.
Ретраи требуют особого внимания. Если 40 упавших задач повторят попытку в одну и ту же секунду, они создадут второй всплеск сразу после первого. Backoff с jitter распределяет нагрузку по времени и даёт базе шанс восстановиться.
Полезно также ставить на паузу несрочные запуски в часы пик. Большинство команд спокойно могут подождать до вечера с backfill или полным reindex. Клиенты сразу замечают медленные страницы, но редко замечают, что пакетная задача завершилась на три часа позже.
Если оставить одно правило, пусть оно будет таким: трафик клиентов получает резервную ёмкость, а массовая работа — только то, что осталось.
Ошибки, которые приводят к неожиданным сбоям
Неожиданные сбои редко начинаются с поломанной базы. Обычно всё начинается с маленьких решений, которые кажутся безобидными, пока всплеск очереди не будит десятки воркеров одновременно. Тогда запросы клиентов ждут, завершаются по таймауту и скапливаются за фоновыми задачами.
Один общий пул для всех процессов — частая ошибка. Если API, batch-задачи, embeddings, импорты и воркеры ретраев используют один и тот же пул с одинаковыми лимитами, побеждает самая шумная группа. Во время тяжёлого запуска трафик клиентов может потерять доступ к базе, хотя само приложение всё ещё выглядит здоровым.
Ещё одна плохая привычка — ориентироваться только на число CPU. Команды часто выставляют число воркеров по количеству ядер, а потом увеличивают consumers до тех пор, пока машины не станут загружены. Но лимиты подключений Postgres не заботятся о том, что у CPU ещё есть запас. Если каждый воркер может открыть несколько соединений, «безопасная» настройка по вычислительным ресурсам всё равно может задушить базу.
Ещё несколько ошибок повторяются снова и снова:
- Команды используют все доступные соединения в обычной работе и не оставляют места для миграций, ручной shell-сессии или аварийных действий.
- Они воспринимают таймауты как сигнал немедленно повторить попытку, поэтому упавшие задачи возвращаются и снова просят ещё больше соединений.
- Они добавляют новый парк воркеров для свежей AI-задачи и не меняют старый бюджет, как будто база сама по себе стала больше.
- Они масштабируют consumers очереди во время запуска или backfill, не проверив, совпадают ли лимиты read replicas, primary и pooler.
- Они смотрят только на CPU и память, а число соединений, ожидание блокировок и задержка очереди ускользают из поля зрения.
Проблема с ретраями особенно неприятна. Таймаут должен замедлять систему, а не ускорять её. Бесконечные ретраи превращают короткую паузу базы в поток. Пять воркеров могут стать пятьюдесятью активными запросами за минуту, если каждая неудачная задача сразу возвращается.
Я часто вижу это в стартапах после быстрого выпуска функции. Кто-то добавляет больше AI-очередей, чтобы быстрее убрать backlog, но никто не обновляет бюджет пулов по типам воркеров. В тестировании приложение работает нормально, а в продакшене всплеск блокирует вход, биллинг или поиск, пока фоновые задачи продолжают забирать слоты.
Оставляйте запас специально. Если ваша команда не может открыть сессию миграции или shell к базе во время инцидента, значит, бюджет был слишком жёстким ещё до начала сбоя.
Быстрые проверки перед тяжёлым запуском
Перед запуском большой очереди пересчитайте каждый пул соединений, который может проснуться одновременно. Команды часто считают только основное приложение и забывают про поды воркеров, cron-задачи, админские инструменты, миграции и одноразовые скрипты. Лимиты подключений Postgres легко нарушить именно так, потому что каждый сервис сам по себе выглядит безобидно.
Первое число, на которое стоит ориентироваться, — это общий возможный пик, а не среднее значение. Если веб-приложение может открыть 40 соединений, AI-воркеры — 60, а пакетные задачи — 30, ваш реальный потолок уже 130. Если база нормально живёт только до 100, вы уже знаете, что тяжёлый запуск может выйти за предел.
Веб-трафик должен получать самую большую долю. Реальные пользователи чувствуют боль первыми и не волнуются о том, что фоновая задача уже почти завершилась. Дайте клиентским запросам пространство даже тогда, когда очередь шумит.
Короткий список перед запуском помогает:
- Сложите лимиты пулов для каждого сервиса, группы воркеров и запланированной задачи.
- Проверьте, что веб- и API-трафик по-прежнему владеют самой большой частью бюджета.
- Запустите один тест всплеска с объёмом очереди, который соответствует реальной тяжёлой нагрузке, а не маленькой выборке.
- Смотрите одновременно на время ожидания соединений, ошибки базы и возраст очереди.
- Оставьте ручной переключатель паузы для массовых задач, чтобы кто-то мог быстро остановить поток.
Третий пункт важнее, чем многие думают. Фальшивый тест с десятью задачами почти ничего не говорит, если в продакшене приходят тысячи. Используйте реальную форму очереди, если можете: то же число воркеров, тот же набор задач, те же правила ретраев.
Смотрите на три сигнала вместе, потому что каждый из них показывает только часть картины. Время ожидания показывает давление ещё до полного отказа. Ошибки показывают, когда пулы или база начинают отказывать в работе. Возраст очереди показывает, не отстают ли задачи, даже если приложение снаружи выглядит нормально.
Переключатель паузы должен быть простым и очевидным. Подойдёт флаг в конфиге, отключённый деплой или число consumers очереди, выставленное в ноль. Когда трафик клиентов замедляется, вам нужен способ остановить массовые задачи за секунды, а не после долгого спора в чате.
Что делать после того, как бюджет задан
Бюджет работает только тогда, когда числа живут в коде и конфиге. Если ваши лимиты подключений Postgres существуют только в документе или в чьей-то памяти, они начнут расходиться при первом же изменении воркера, параллельности или очереди.
Сначала назовите бюджет простыми словами. Держите отдельные настройки для веб-запросов, быстрых фоновых задач и тяжёлых AI-очередей. Положите эти настройки рядом с конфигом воркеров, закоммитьте их в репозиторий и используйте те же названия в дашбордах и алертах.
Помогает небольшой чек-лист:
- Храните лимиты пулов и параллельность воркеров в конфиг-файлах или переменных окружения
- Назначьте одного владельца изменений бюджета, чтобы две команды не повысили лимиты одновременно
- Добавьте проверку на старте, которая не даст запуститься, если общий итог может превысить лимит базы
- Запишите причину для каждого числа в коротком комментарии
Последний пункт экономит время позже. Через шесть месяцев никто не вспомнит, почему одной очереди дали 12 соединений, а другой 4, если не оставить заметку.
Пересматривайте распределение каждый раз, когда добавляете новый тип воркера. Новые очереди сначала часто выглядят безобидно, а потом накапливаются и съедают запас, который вы хотели оставить для трафика клиентов. Если новая задача может подождать, жёстко ограничьте её с самого начала.
Нужно также повторно тестировать систему после любого изменения, которое меняет характер нагрузки. Autoscaling может умножить спрос на соединения быстрее, чем вы ожидаете. Изменение модели может сделать каждую задачу длиннее. Новая очередь может превратить короткие всплески в постоянное давление.
Запустите один тяжёлый тест, похожий на реальный плохой день, а не на аккуратный лабораторный пример. Подайте вместе трафик клиентов и фоновые задачи, а затем проверьте три числа: задержку запросов, время разгрузки очереди и общее число открытых соединений. Если запросы клиентов замедляются первыми, распределение всё ещё нужно дорабатывать.
Это также хороший момент, чтобы решить, кто вообще может менять бюджет в продакшене. Чем меньше людей — тем лучше.
Если хотите второй взгляд со стороны, Олег может проверить вашу схему AI-воркеров и бюджет пулов в формате практичной CTO-консультации. Такой разбор обычно находит одну-две рискованные настройки ещё до того, как они превратятся в инцидент.