Масштабирование и надёжность

Обновлено 5 мин чтения

Bulkhead: почему одна перегруженная функция не должна съедать ресурсы всего продукта

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

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

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

Разделять функции в меню недостаточно

Bulkhead — изоляция ресурсов между видами работы. У выгрузки может быть собственная ограниченная группа обработчиков, у интерактивных запросов — другая. Для медленного внешнего сервиса можно выделить отдельный набор соединений, чтобы ожидание его ответа не занимало все подключения приложения.

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

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

Ограничение нагрузки и резерв мощности — разные вещи

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

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

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

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

Что происходит с работой, которая не поместилась

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

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

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

В этой точке Bulkhead связан с backpressure: система сообщает, что больше работы сейчас не принимает, либо просит отправителя замедлиться. Само разделение пулов не уменьшает входящий поток.

У большого клиента не должно быть безлимитной общей очереди

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

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

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

Цена, которая видна в спокойный день

При жёстком разделении часть мощности может простаивать, пока другой пул занят. Это не обязательно ошибка: свободный резерв и покупался ради отдельного сценария. Но его стоимость должна быть сопоставлена с последствиями простоя, а не скрыта за словом «надёжность».

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

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

Как доказать, что карточка заявки защищена

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

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