Все новости

Горизонтальное масштабирование Camunda 8: как мы решили проблему производительности Zeebe через мультикластерную архитектуру

1. Вводная часть: масштабирование как неизбежность

Проблематика масштабирования Zeebe

Если вы работаете с Camunda 8 в продакшене, рано или поздно столкнетесь с вопросом: “А что будет, когда нагрузка вырастет в разы?”

Zeebe, движок оркестрации в основе Camunda 8, спроектирован как высокопроизводительная система с горизонтальным масштабированием через партиционирование. Официальная документация и маркетинговые материалы обещают практически неограниченную масштабируемость, противопоставляя новую, облачно-нативную архитектуру монолитному подходу Camunda 7, производительность которого была ограничена возможностями центральной реляционной базы данных.

На бумаге всё выглядит отлично: добавляем партиции – растет пропускная способность, добавляем брокеры – увеличиваем надежность. Но реальность, как обычно, сложнее теории.

В компании Камунда.РФ мы развиваем отечественную платформу оркестрации бизнес-процессов на основе собственного форка Zeebe, полностью совместимого с Camunda 8 API. Практически первый вопрос, который мы слышим от потенциальных заказчиков, касается производительности: “У нас сейчас 100 процессов в секунду, через год будет 500, а через два может, и тысяча. Ваше решение справится?”

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

Но обо всем по порядку.

Почему это актуально именно сейчас

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

Типичный сценарий роста нагрузки в крупной компании выглядит так:

  • Год 1: Запускаем пилотный проект по автоматизации ключевого процесса (например, выдачи кредитов) в одном регионе. Нагрузка: 10–20 процессов в секунду.
  • Год 2: Успешный пилот масштабируется на всю страну, подключаются все филиалы и онлайн-каналы. Нагрузка: 100+ процессов в секунду.
  • Год 3: Процесс выходит на новые рынки, подключается крупная партнёрская сеть (маркетплейсы, ритейлеры). Нагрузка: 500+ процессов в секунду.
  • Год 4: Крупная маркетинговая кампания или сезонный пик спроса создают взрывную нагрузку на всю инфраструктуру. →???

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

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

О чём эта статья

В этой статье я расскажу:

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

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

Поехали!

2. Контекст и боль: когда железо перестаёт помогать

Ограничения Camunda Zeebe при росте нагрузки

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

  • Этап 1: “Всё отлично”

    • Развернули кластер из 3 брокеров.
    • Настроили 3 партиции с replication factor = 3.
    • Обрабатываем 50 процессов в секунду.
    • Latency стабильная, метрики зеленые.
  • Этап 2: “Нужно больше мощности”

    • Нагрузка выросла до 100 процессов в секунду.
    • Добавили партиций до 6, брокеров до 6.
    • Помогло! Система справляется.
  • Этап 3: “Еще больше железа”

    • Целевая нагрузка 200+ процессов в секунду.
    • Увеличили до 12 партиций, 12 брокеров.
    • Вроде работает, но что-то не то…

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

  • Accepts: 200 process/sec
  • Completes: 50 process/sec
  • Queue depth: растет линейно
  • Job latency: от 100 ms до 5+ секунд

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

Почему вертикальное масштабирование не решает проблему

Первым и наиболее очевидным решением при столкновении с пределом производительности кажется вертикальное масштабирование – “просто добавить железа”. Мы проверили эту гипотезу, удвоив ресурсы кластера из 6 брокеров.

Результаты теста: больше ресурсов – больше разочарований

Мы сравнили две конфигурации: базовую (4 vCPU, 12 ГБ RAM на брокер) и удвоенную (8 vCPU, 24 ГБ RAM). Графики наглядно показывают, что, хотя определённый прирост и есть, он абсолютно нелинейный и не решает фундаментальную проблему.

  1. Увеличение вычислительных ресурсов кластера (в 2 раза больше CPU и RAM) дало лишь незначительное и временное улучшение производительности. Такой подход экономически неэффективен.

  2. Эффект от быстрых дисков. Наши тесты проводились на сетевых нереплицируемых SSD (заявленная производительность 28000 / 5600 IOPS, 110 / 82 МБ/с). Анализ показывает, что даже использование дисков с более высокими показателями IOPS даёт схожий результат: заметный прирост на старте, но по мере роста базы данных и интенсивности фоновых операций в RocksDB производительность снова упирается в предел.

Вывод: борьба с симптомами

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

Это как пытаться решить проблему пробки на дороге, постоянно расширяя полосы. Какое-то время это помогает, но потом машин становится ещё больше, и всё возвращается на круги своя. Решение не в расширении дороги, а в строительстве развязок и альтернативных маршрутов.

Реальная проблема: архитектурные ограничения

После анализа метрик мы поняли корневую проблему: в single-cluster архитектуре есть несколько фундаментальных узких мест:

  1. Накопление нагрузки в большом кластере. В кластере из 12 брокеров с 12 партициями (фактор репликации 3) каждый брокер становится лидером для одной партиции и фолловером для двух других. Это создаёт две ключевые проблемы:

    • Концентрация нагрузки. Каждый брокер испытывает суммарную нагрузку на диск и сеть сразу от трёх партиций (своей и двух “чужих”). Это узкое место, которое проявляется задолго до того, как сетевой трафик достигнет теоретических пределов.
    • Торможение из-за медленного узла. Производительность всей партиции зависит от самого медленного брокера в её группе репликации. Если один узел перегружен, он тормозит коммит транзакций для всей партиции.
  2. RocksDB: перегрузка I/O и постоянное уплотнение. На каждом брокере (ВМ) работают 3 инстанса RocksDB, которые конкурируют за общие ресурсы CPU и диска. Под нагрузкой наблюдается следующая картина:

    • Постоянная фоновая работа: графики Running Flushes и Running Compactions показывают непрерывную активность. Система постоянно сбрасывает данные на диск и пытается их уплотнить, работая на пределе возможностей.
    • Полная утилизация кэша: метрика Block Cache Usage демонстрирует, что со временем кэш заполняется на 100%. Это вынуждает систему чаще обращаться к диску при чтении, поскольку кэш переполнен.

Результат: производительность упирается в возможности дисковой подсистемы – она перегружена одновременными операциями записи (от поступающих команд), фоновым compaction и “промахами” по кэшу, требующими чтения с медленного диска.

“Shared Nothing” превращается в “Shared Everything”. Zeebe построен на архитектуре shared nothing, где каждая партиция логически полностью изолирована со своим собственным состоянием. Однако на физическом уровне, в пределах одной машины, эта изоляция нарушается, так как несколько партиций вынуждены делить общие ресурсы:

  1. Page Cache ОС (за него конкурируют все инстансы RocksDB).
  2. Планировщик CPU (распределяет время между всеми потоками, включая потоки compaction от разных партиций).
  3. Сетевой стек (обрабатывает трафик всех партиций на одном брокере).
  4. Heap JVM, общий для всех компонентов внутри процесса брокера.

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

Момент истины: граница применимости

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

Механизм сбоя выглядел так:

  1. Задержка обработки команд растет.
  2. Клиенты получают таймауты и начинают повторные запросы (retry).
  3. Повторные запросы создают дополнительную нагрузку поверх основной.
  4. Задержка растёт ещё больше.
  5. Возврат к шагу 1.

На практике это приводит к катастрофическому разрыву между входящим и исходящим потоком. Система принимала до 120+ процессов/с, но успевала завершать лишь ~5. Остальные ~115+ процессов каждую секунду оседали во внутренних очередях в виде технического долга.

Чтобы оценить масштаб проблемы, посчитаем, сколько времени потребуется системе на восстановление. После всего одного часа работы под нагрузкой ~120 процессов/с система накапливает долг примерно в 414 000 незавершённых процессов. Даже если после снижения нагрузки система сможет разбирать очередь со скоростью 100 процессов/с, ей потребуется ~4140 секунд (около 1 часа 9 минут) на полную нормализацию. Фактически за каждый час пиковой нагрузки системе нужно ещё час на восстановление.

Неочевидные последствия для бизнеса

Что происходит, когда система оркестрации процессов начинает тормозить?

Для операционистов:

  • “Почему кредит одобряется 3 часа вместо 5 минут?”
  • “Клиенты жалуются на таймауты.”
  • “SLA нарушены по всем процессам.”

Для разработчиков:

  • “Мы ничего не меняли, почему всё сломалось?”
  • “Rollback не помогает.”
  • “Метрики показывают, что мы принимаем запросы…”

Для бизнеса:

  • Потеря клиентов из-за долгого обслуживания.
  • Штрафы за нарушение SLA.
  • Репутационные риски.

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

3. Архитектурное решение: разделяй и властвуй

Решение пришло из мира микросервисов – федерация через интеллектуальный proxy. Вместо одного большого кластера мы создаём несколько маленьких и объединяем их на уровне API. Ключевая идея: каждый маленький кластер работает в своей зоне комфорта (до ~40–45 процессов/с), а proxy распределяет нагрузку между ними. Proxy-слой стал той самой “развязкой”, которая направляет потоки на разные маршруты.

Наш proxy – это единственная точка входа, которая маршрутизирует запросы на шард-кластеры в зависимости от их типа. Такая гибридная схема позволяет сочетать stateless-балансировку для масштабирования входящего потока со stateful-маршрутизацией для сохранения консистентности при обработке уже запущенных процессов.

Как работает прокси-слой

Для реализации этой концепции мы разработали прокси, который выступает единой точкой входа и маршрутизирует запросы на несколько шард-кластеров. Логика маршрутизации разделена на три потока в зависимости от типа запроса. Деплоймент моделей процессов выполняется веерной рассылкой на все кластеры (fan-out), новые процессы запускаются по алгоритму балансировки P2C (Power of Two Choices), запросы от воркеров на выполнение задач направляются конкретным кластерам, где находятся соответствующие задачи. Таким образом, новые процессы распределяются равномерно, а обработка уже существующих идёт там, где хранится их состояние.

Обработка состояния и консистентность

Критический вопрос: как обеспечить консистентность, когда данные разбросаны по кластерам?

Экземпляры процессов:

  • Каждый экземпляр живёт в одном кластере от создания до завершения.
  • Нет cross-cluster коммуникации для отдельных процессов.
  • Полная изоляция состояния.

Процесс взаимодействия выглядит следующим образом:

  1. Воркер отправляет на прокси стандартный запрос ActivateJobs для нужного типа задач (например, credit-check).
  2. Прокси, получив запрос, параллельно проксирует его во все кластеры, которые могут содержать задачи этого типа.
  3. Прокси ожидает первый отклик. Как только первый же кластер возвращает доступные задачи, прокси немедленно делает две вещи:
    • Добавляет в заголовок ответа cluster-id, чтобы воркер знал, с каким шардом он работает.
    • Передаёт обогащённый ответ воркеру.
  4. Воркер получает задачи и начинает их обработку, оставаясь в неведении о существовании нескольких кластеров.

Graceful Degradation и Self-Healing

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

Сценарий 1: Внезапный сбой кластера

  1. Прокси обнаруживает недоступность одного из кластеров через неуспешные health checks.
  2. При выборе двух случайных кластеров для балансировки (P2C) отказавший узел не будет выбран, так как не отвечает.
  3. Новые процессы автоматически распределяются только между работоспособными кластерами.
  4. Влияние сбоя ограничивается только активными процессами, которые исполнялись на отказавшем кластере.
  5. После восстановления кластер снова начинает проходить health checks и автоматически возвращается в ротацию для P2C-балансировки.

Сценарий 2: Деградация производительности

  1. Алгоритм балансировки P2C для каждого нового процесса выбирает два случайных кластера из пула.
  2. Запрос направляется в тот из них, у которого в данный момент меньше активных соединений.
  3. Когда производительность одного из кластеров деградирует (например, из-за длительной сборки мусора), количество “зависших” соединений на нём естественным образом растёт.
  4. В результате этот кластер всё реже выигрывает в P2C-сравнении, и нагрузка автоматически перераспределяется на более здоровые узлы, давая ему время на восстановление.

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

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

В нашей мультикластерной системе сбой не стал катастрофой. При средней нагрузке оставшиеся кластеры без проблем приняли на себя дополнительный трафик, и отказ прошёл для новых задач абсолютно незаметно. При работе на пределе производительности система показала частичную деградацию: общая пропускная способность снизилась пропорционально потерянной мощности (примерно на 25-30%), но, что самое важное, обработка не остановилась.

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

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

4. Нагрузочное тестирование: цифры, которые заставили задуматься

Методология тестирования: максимально честно

Прежде чем перейти к цифрам, важно рассказать, как мы проводили тестирование. Ключевая цель – избежать “сферических коней в вакууме”, то есть синтетических тестов, результаты которых неприменимы к реальной продакшен-среде.

Наша позиция была принципиальной: мы не отключали персистентность, не использовали in-memory хранилища “для скорости” и не ограничивались простыми “hello-world” процессами. Вместо этого мы взяли за основу реальный бизнес-процесс “Кредитный конвейер” из нашего продакшена. Это сложный процесс, включающий 47 BPMN-элементов и 8 эксклюзивных шлюзов.

Единственное упрощение, на которое мы пошли, это замена всех внешних системных вызовов на быстрые “заглушки”. Это позволило исключить влияние сторонних сервисов и измерять производительность именно движка оркестрации.

Для обоих тестов (стандартного Zeebe и нашего решения) мы использовали идентичные виртуальные машины для брокеров, чтобы обеспечить честное сравнение производительности движка. Конечной целью было не найти пиковый TPS, а определить точку деградации – предел, после которого система перестаёт справляться с нагрузкой и её производительность необратимо падает.

Ключевые характеристики тестового процесса:

  • Сложность: процесс включает 47 BPMN-элементов, в том числе 8 эксклюзивных шлюзов с ветвлением по бизнес-логике.
  • Нагрузка на воркеры: каждый экземпляр процесса создаёт 36 задач (jobs). schema.png

Профиль нагрузки: от прогрева до предела

Для поиска максимальной производительности мы использовали классический ступенчатый профиль нагрузки – это позволило плавно подвести систему к её пиковым возможностям и точно зафиксировать момент деградации. Тест состоял из двух фаз:

  1. Фаза прогрева (Warm-up). В течение 15 минут подавалась лёгкая нагрузка ~50 процессов/с. Цель этого этапа – “прогреть” JVM, заполнить кеши и дождаться выхода системы на стабильный режим, чтобы исключить эффект холодного старта в основном тесте.

  2. Фаза поиска (Search). На этом этапе мы постепенно повышали нагрузку до тех пор, пока система не переставала справляться:

    • Начальная нагрузка: 50 запусков процессов в секунду.
    • Длительность каждой ступени: 5 минут.
    • Шаг увеличения: +10% к текущей нагрузке на каждой новой ступени.
    • Время разгона между ступенями: 1 минута плавного роста.

Тест автоматически останавливался при выполнении одного из условий:

  • Количество завершённых процессов < 15% чем стартовавших
  • Уровень ошибок превышал 1%.
  • p99 задержки ответа старта процесса превышал 5 секунд.

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

Конфигурация стендов

Стенд 1: стандартный кластер Camunda Zeebe 8.5.24

  • Всего: 13 ВМ (12 брокеров + 1 гейтвей).
  • VM брокера: 4 vCPU, 12 ГБ RAM, 93 ГБ SSD.
  • VM гейтвея: 8 vCPU, 16 ГБ RAM.
  • Итого ресурсов: 56 vCPU, 160 ГБ RAM.

Стенд 2: наше решение с федеративными кластерами (Платформа 8)

  • Всего: 17 ВМ (12 брокеров + 4 гейтвея + 1 прокси).
  • VM брокера: 4 vCPU, 12 ГБ RAM, 93 ГБ SSD.
  • VM гейтвея: 2 vCPU, 4 ГБ RAM (x4).
  • VM прокси: 8 vCPU, 16 ГБ RAM.
  • Итого ресурсов: 64 vCPU, 176 ГБ RAM.

Да, наша архитектура требует немного больше ресурсов за счёт прокси-слоя и отдельных гейтвеев для каждого кластера. Прирост составил ~14% по vCPU и 10% по RAM. Как покажут результаты, это более чем оправданная плата за кратное увеличение стабильной производительности.

Анализ результатов

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

Стенд 1: стандартный кластер Zeebe. Монолитный кластер показал поведение, которое мы описывали ранее:

  • Пиковый приём запросов: до ~80 процессов/с.
  • Стабильное завершение процессов: не более ~80 процессов/с.

Как только нагрузка превышала ~80 процессов/с, система входила в режим лавинообразного отказа. Она продолжала принимать новые запросы, но скорость их обработки резко падала. Это отчётливо видно на графиках пропускной способности: кривая созданных процессов устремляется вверх, а кривая завершённых начинает падать, что приводит к экспоненциальному росту внутреннего долга. vanila-process-start.png vanila-process-queue.png vanila-duration.png Стенд 2: наше решение с федеративными кластерами. Архитектура с изолированными кластерами под управлением прокси показала совершенно иную картину:

  • Пиковый приём запросов: ~160 процессов/с.
  • Стабильное завершение процессов: ~160 процессов/с.

Система продемонстрировала абсолютно стабильное поведение. Число создаваемых и завершаемых процессов в секунду было практически одинаковым до превышения нагрузки в 160 PPS. Каждый кластер работает в своей зоне максимальной эффективности, а прокси-слой обеспечивает горизонтальное масштабирование, распределяя нагрузку. fz-process-start.png fz-process-queue.png fz-duration.png

Итоги в цифрах (Платформа 8 vs Zeebe 8.5.24):

  • Завершение процессов в секунду: стабильно ~160 (мультикластер завершает всё, что принимает) vs деградация до ~5 (монолит завершает лишь малую часть принятых процессов).
  • Создание процессов в секунду: ~160 vs ~200+.
  • Общая задержка обработки: (на 160 тпс большинство запросов < 50 мс) vs (на 80 тпс большинство запросов < 50 мс).

Ключевое отличие: мультикластер показывает честные цифры – он не примет больше, чем сможет обработать.

Выводы и наблюдения

  1. Явный backpressure лучше скрытого. Стандартный кластер Zeebe пытается обработать все входящие запросы, даже когда его внутренние компоненты уже перегружены. Это приводит к накоплению огромной очереди и деградации всей системы. Наше решение с прокси придерживается другой стратегии: если все нижележащие кластеры сигнализируют о высокой нагрузке, прокси явно отказывает в приёме нового запроса (explicit backpressure).

    Контринтуитивно, но второй подход гораздо здоровее:

    • Предсказуемость: клиент мгновенно узнаёт о временной перегрузке и может отреагировать, например, применить стратегию retry с экспоненциальной задержкой (backoff).
    • Отсутствие техдолга: система не накапливает гигантскую очередь, которую потом пришлось бы разбирать часами.
    • Соблюдение SLA: процессы, которые уже приняты в работу, выполняются с предсказуемой скоростью.
  2. Меньше партиций на узел – выше производительность. Наши тесты подтвердили основной тезис: чем больше инстансов RocksDB (партиций) конкурируют за ресурсы одного узла (CPU, I/O, Page Cache), тем быстрее наступает деградация. Архитектура с небольшими, изолированными кластерами, где каждый узел обслуживает минимально необходимое число партиций, показала себя гораздо более эффективной.

  3. Важность сетевых задержек. Все наши тесты проводились в пределах одного дата-центра с минимальной задержкой между узлами (< 1 мс). В географически распределённых системах, где latency выше, преимущество компактных изолированных кластеров будет ещё более выраженным, так как объём “дорогого” межсетевого трафика для координации значительно ниже.

5. Практические результаты: от теории к production

Совместимость и миграция: бесшовный переход

Один из главных вопросов при внедрении новой технологии: “Сколько кода придётся переписать и насколько сложным будет переход?” В нашем случае ответ – 0 строк клиентского кода и предсказуемый, управляемый процесс миграции.

Благодаря тому, что наш прокси полностью совместим с Zeebe API, для клиентских приложений и воркеров переход выглядит как простое изменение адреса подключения. Сама миграция спроектирована по принципу Blue-Green, когда новая система разворачивается параллельно со старой.

Наше решение может работать и без прокси как единый кластер (drop-in замена стандартному Zeebe) или в режиме федерации (несколько кластеров под управлением прокси). Это позволяет начать с простого одно-кластерного развертывания, а затем при росте нагрузки добавить прокси и подключить дополнительные кластеры, не меняя клиентский код.

Простота внедрения: три шага к масштабируемости

Один из ключевых принципов нашего решения – простота. Весь процесс сводится к трём логическим шагам.

  • Шаг 1: Развёртывание инфраструктуры. Вместо обязательного мультикластерного решения на старте вы можете развернуть один кластер без прокси (drop-in замена стандартному Zeebe) или сразу подготовить несколько малых кластеров с прокси, в зависимости от текущей и ожидаемой нагрузки.

  • Шаг 2: Настройка единой точки входа. Если выбран мультикластерный путь, настраиваем прокси и передаём ему адреса всех кластеров. С этого момента прокси станет единой точкой входа, представляющей нижележащую федерацию как один сервис, полностью совместимый со стандартным Zeebe API. (Если пока используется один кластер без прокси, этот шаг пропускается.)

  • Шаг 3: Перенаправление клиентов. При использовании прокси финальный шаг – переключить все клиентские приложения и воркеры на адрес прокси. Благодаря полной API-совместимости изменений в коде не требуется: достаточно обновить единственный endpoint в конфигурации. Если вы начали с одного кластера без прокси, клиенты подключаются к нему напрямую, при последующем переходе на мультикластер добавляется прокси, и меняется только endpoint.

Какую конфигурацию выбрать и когда?

Важно понимать, что наш форк Zeebe – это не только решение для высоких нагрузок. В первую очередь, это полноценная, развивающаяся и поддерживаемая альтернатива стандартному Camunda Zeebe (что особенно актуально после ухода вендора с российского рынка). Наше решение может работать в двух режимах, закрывая потребности компаний на любом этапе роста:

  1. Режим монолитного кластера. Это drop-in замена стандартному Zeebe. Вы разворачиваете кластер из нескольких брокеров и одного гейтвея, получая полную совместимость с оригиналом, но с ключевым преимуществом – наличием поддержки, регулярных обновлений и устранением уязвимостей.

    Такой режим идеально подходит, если:

    • ваша нагрузка стабильна и не превышает ~40–45 завершённых процессов в секунду;
    • у вас нет текущих требований к горизонтальному масштабированию;
    • вы хотите получить поддерживаемый и развивающийся продукт взамен ушедшего с рынка.
  2. Режим федеративных кластеров с прокси. Когда ваш бизнес растёт и производительности монолитного кластера становится недостаточно, вы можете перейти на нашу масштабируемую архитектуру – ту самую конфигурацию, которую мы протестировали в статье. Переход на этот режим оправдан, когда наблюдаются симптомы перегрузки:

    • стабильная нагрузка на систему превышает ~40–45 процессов/с;
    • количество завершённых процессов падает и составляет < 50% от принятых;
    • метрики показывают частые write stalls, а p99 latency превышает 1 секунду;
    • глубина очереди растёт линейно без признаков стабилизации.

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

Практические рекомендации по итогам тестов

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

Для архитекторов:

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

  2. Мониторинг - не опция, а необходимость. Внедряйте детальный мониторинг ключевых показателей производительности с первого дня.

  3. Определите триггер для миграции, не ждите, пока система откажет. Хорошим упреждающим сигналом к началу планирования масштабирования станет ситуация, когда completion rate стабильно держится < 80% от creation rate.

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

Для DevOps-инженеров: вместо отслеживания отдельных метрик рекомендуем смотреть на визуальные паттерны деградации на дашбордах - они дают более целостное понимание состояния системы:

  1. Индикатор “Ножницы” на графиках пропускной способности.

    Панель Process Instance Events per second (создано vs завершено).

    Линии созданных и завершённых процессов начинают расходиться, образуя “ножницы” - главный индикатор того, что система перестала справляться и растёт бэклог. vanila-process-start.png

  2. Индикатор «Смещение вверх» на тепловых картах задержек

    Панели Overall Processing Latency и Record Processing Duration.

    На тепловой карте (heatmap) смещение основной массы запросов из диапазона миллисекунд в диапазон секунд однозначно указывает на системную деградацию производительности. heatmap-for-example.png

  3. Индикатор “Растущая очередь” на графике бэклога.

    Панель Processing Queue size или Number of records not processed.

    Если график очереди начинает неуклонно расти, это прямой показатель того, что система “захлёбывается” и накапливает долг. vanila-process-queue.png Для разработчиков: эффективность системы зависит не только от инфраструктуры, но и от того, как приложения взаимодействуют с движком.

  4. Используйте batch-операции. Отправить один запрос на активацию 100 задач (ActivateJobs) гораздо эффективнее, чем отправлять 100 запросов по одной задаче - за счёт снижения сетевых и транзакционных издержек.

  5. Правильно настраивайте retry-политику, всегда применяйте exponential backoff (увеличение паузы между повторными попытками). Бездумные повторные запросы без задержек могут окончательно “убить” систему, уже работающую на пределе.

  6. Не игнорируйте backpressure, если Zeebe возвращает ошибку RESOURCE_EXHAUSTED, это не сбой, а штатный механизм защиты. Приложение должно уважать этот сигнал и снизить интенсивность запросов, а не продолжать “долбить” систему.

Заключение

Наше тестирование показало: стандартный Zeebe при нагрузке свыше ~80 процессов/с начинает деградировать до 5–10 завершений процесса в секунду, тогда как мультикластерное решение стабильно обрабатывает ~160 процессов/с без деградации.

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

Решение полностью совместимо с Zeebe API и не требует изменения клиентского кода.

Если у вас есть вопросы или вы хотите обсудить внедрение – оставляйте заявку на сайте https://камунда.рф, пишите в комментариях или напрямую. Мы в Farzoom всегда рады поделиться опытом и помочь с масштабированием Camunda 8 под реалии бизнеса.