Camunda на проде: восемь типичных ошибок

Итак, вы смоделировали все процессы, написали бизнес-логику и задеплоили все на сервер. Запускаем наши процессы на проде! Поехали? — Но дальше разложено множество граблей, на которые обычно наступают все, кто только начинает эксплуатировать BPM, в том числе и на движке Camunda 7. Эта статья сэкономит вам много времени и успокоит нервы — потому что ситуации, описанные ниже, могут изрядно их попортить, если вы будете не готовы.
1. Не помещайте слишком много данных в контекст
Для бизнес-аналитика данные не имеют вкуса, цвета и запаха какого-либо ощутимого размера. Это может быть просто число, показывающее среднюю температуру по больнице, фотография котика или целая библиотека Конгресса — для него это всего лишь некая абстракция. В принципе на стадии проектирования это разумно.
Однако, когда вы начинаете строить исполняемый процесс, размер имеет значение. BPM-движки по жизни не приспособлены, чтобы хранить в своем контексте какие-то ощутимые количества данных. И хотя формальных ограничений на размер контекста в Camunda нет, при его разрастании начинаются проблемы с производительностью. Если вы не подумали об том заранее, то эти проблемы настигнут вас внезапно и, казалось бы, на ровном месте.
Допустим, по вашему процессу гуляет документ, который хранится в процессной переменной. В нормальном режиме этот документ имеет размер в несколько килобайт — одна-две странички текста. Но иногда найдется вдруг чудак, который закинет в процесс документ объемом с «Войну и мир». И движок скажет «ой».
Чтобы такого не случилось, вообще не надо хранить в контексте процесса документы и другие сущности, которые хотя бы потенциально могут иметь весомый размер. Для документов есть ECM-системы или другие хранилища, специально для этого приспособленные. Храните в процессе ссылки на эти объекты, и будет вашему движку счастье.
Но и это не абсолютная гарантия. Одна ссылка весит мало, но их может быть миллион! И тогда движку снова будет тяжко. На этот случай какого-то однозначного рецепта нет, здесь можно только посоветовать смотреть внимательно на свои процессы, и где такой риск существует — то есть, где идет работа со списками сущностей, лучше поставить какие-то ограничители.
2. Позаботьтесь об актуальности данных
Бизнес-процессы, особенно с участием людей, могут быть очень длительными — недели, месяцы, а то и годы. Почему бы и нет? Вдруг вы делаете процесс годового планирования? И это нормально, нотация BPMN разрабатывалась с учетом таких кейсов. Но когда процесс длится долго, есть один нюанс, который надо принимать во внимание.
Допустим, на старте процесса вы получили какие-то данные и положили их в процессные переменные. Например, сумма на счете клиента или даже его возраст. И прошло месяца три от момента запуска процесса — кто-то долго согласовывал, ходил в отпуск — всякое бывает. Наконец, настал момент принятия решения. Можно ли полагаться на данные из процессных переменных? — Едва ли. Как говорится, за время пути собачка могла подрасти.
Надежнее будет заново прочитать данные из первоисточника, а не возить с собой по процессу их старые значения. Это еще один аргумент в пользу того, что лучше использовать ссылки вместо самих данных — это касается не только размера, еще и актуальности. Даже если данные очень маленькие, всего лишь один атрибут из какой-то сущности, они должны быть свежими.
3. Бизнес-ключ — ваш ключ к успеху
Обычно каждый процесс связан с каким-то бизнес-объектом, будь то заказ клиента, жалоба гражданина, тикет в поддержке и так далее. Такой документ всегда имеет некий идентификатор в понятном человеку виде, на который удобно ссылаться. Собственно, это и есть бизнес-ключ в терминологии Camunda.
Наличие или отсутствие бизнес-ключа непосредственно на выполнение процесса никак не влияет, поэтому некоторые игнорируют этот необязательный параметр. А зря! Представьте типичную ситуацию, когда из нескольких процессов вызывается один и тот же подпроцесс. Как вы поймете, к какому родительскому процессу относится данный экземпляр подпроцесса? Смотреть по parent_id
не очень удобно, проще использовать бизнес-ключ.
BPM часто работает в интеграции с внешними системами (ERP, CRM, 1С и др.) В таких случаях бизнес-ключ помогает ассоциировать процесс с конкретной сущностью во внешней системе. Конечно, можно взять UUID, но это если вы совсем не любите ваших пользователей. Лучше все-таки что-то более наглядное, например, номер договора или лицевого счета.
Если во время эксплуатации без этого ключа в принципе можно обойтись, то, когда вам понадобится делать аналитику, это будет почти нереально. Придется весьма сильно изощряться, чтобы выдать отчеты, приемлемые для бизнеса. Бизнес-ключи делают поиск и мониторинг процессов более интуитивно понятными, так как они дают связь с реальными бизнес-объектами.
В общем, позаботьтесь о бизнес-ключах заранее, еще на этапе моделирования процессов. Там, где их навскидку не наблюдается, придумайте что-нибудь, чтобы различать экземпляры процессов не только по process_instance_id
.
4. Остерегайтесь сигналов
Сигналы и сообщения в BPMN — это прекрасный механизм межпроцессного взаимодействия и в более широком смысле событийно-ориентированного подхода. Это очень нужные вещи, которые позволяют реализовать разные интересные сценарии. Например, клиент может инициировать отмену заказа в любой момент, на любом шаге процесса. Удобнее всего моделировать такие ситуации при помощи событийного подпроцесса, чтобы не ставить проверку не отменен ли заказ после каждой задачи.
С точки зрения моделирования между сообщениями и сигналами большой разницы нет, диаграмма будет читаться примерно одинаково. Однако надо помнить, что сообщения всегда адресные, а сигналы — широковещательные. Это значит, что сообщение получит только конкретный инстанс процесса, и это распространяется на все процессы, которые его слушают.
— Окей, ну и что? Сигналы же в разных процессах разные.
Дело в том, что движок Camunda различает сигналы просто по имени. Когда у вас в команде несколько аналитиков делают процессы, трудно гарантировать, что они не используют для них одинаковые названия. В результате запросто может получиться, что сигнал из какого-то вообще левого процесса активирует ваш процесс в момент, когда этого никто не ждет. Если моделей много, хотя бы несколько сотен, то отловить такую ситуацию может быть трудно. Поэтому лучше в нее не попадать.
Возможна еще одна ситуация, когда процесс ждет выполнения User Task и в это время в него прилетает сигнал. А пользователь, как ни в чем ни бывало, завершает задачу. Процесс пытается продолжиться, но он уже был изменен или вовсе прерван по сигналу.
— И что же делать?
По возможности сигналы совсем не использовать. Это самый надежный вариант. Но если вам все-таки они нужны, заведите реестр и делайте деплой нового процесса только когда убедитесь, что его сигналы уникальны и на другие процессы не повлияют. В общем, в любой непонятной ситуации создавай реестр!
В общем-то и для сообщений (message) это было бы полезно, хотя связанные с ними риски меньше.
5. Избегайте долгих сервис-тасков
Нормальный сервис-таск в Camunda исполняется миллисекунды. Ну, пару секунд максимум. Иначе придется расхлебывать проблемы большой ложкой.
По умолчанию все сервисные задачи в Camunda 7 выполняются синхронно. Когда задача выполняется синхронно, например, долгий запрос к внешнему сервису или какой-то сложный расчет, база данных блокируется на время выполнения этой задачи. То есть у нас долго висит открытая транзакция, а это уже само по себе нехорошо. Поэтому на уровне БД для транзакций часто устанавливают таймаут, чтобы они автоматически откатывались, если зависнут. Если у вас в процессе стоит подряд несколько сервис-тасков и последняя задача выполняется так долго, что вызывает откат транзакции по таймауту, все предыдущие синхронные задачи тоже будут откачены. В результате мы имеем Optimistic Locking Exception, и не факт, что после этого данные будут в согласованном состоянии.
Технически говоря, ошибка Optimistic Locking Exception
возникает, когда два потока или транзакции пытаются изменить один и тот же экземпляр процесса одновременно. Camunda использует оптимистичную блокировку (Optimistic Locking), которая предотвращает перезапись данных без учета изменений, сделанных другим потоком. Это происходит, если кто-то другой изменил процесс во время выполнения задачи. Например, в нем есть параллельные ветки, получено сообщение, сработал таймер и так далее. Особенно это критично в нагруженных системах.
Обычно в качестве лекарства предлагают перейти к асинхронным задачам, что немного облегчает жизнь вашему серверу, но не решает проблему в полной мере.
Когда у задачи стоит признак asyncBefore="true"
, Camunda фиксирует текущую транзакцию и отправляет ее в Job Executor. То есть, в отдельной таблице создает работу (Job), которую Job Executor подхватывает и выделяет для нее один из своих потоков, при этом каждая задача выполняется в отдельной транзакции. Но! Если задаче нужно слишком много времени для завершения, ее все равно может настичь таймаут.
Тогда остается только один вариант — переходить к внешним задачам (external task). Это механизм в Camunda, который позволяет выполнять задачи вне движка. Вместо того чтобы запускать код JavaDelegate (что может перегружать Job Executor), задача ставится в очередь, и внешний клиент (например, микросервис) запрашивает ее выполнение. С точки зрения разработки это более затратно, однако в целом овчинка выделки стоит.
В Camunda 8 вообще отказались от JavaDelegate, там вы можете использовать только external task. Это было сделано, чтобы повысить производительность и полностью исключить вышеупомянутые риски.
См. подробнее в статье Бернда Рюкера
Переход от встроенных к удалённым BPM-движкам
6. Будьте аккуратнее с параллельными сервис-тасками
Когда рисуешь модель, иногда рука так и тянется поставить параллельный шлюз, чтобы процесс исполнялся быстрее, если две задачи выглядят независимыми. Для юзер-тасков это справедливо, при параллельном согласовании процесс действительно ускорится. А вот для сервис-тасков все не так однозначно! Может оказаться даже наоборот — параллельность приведет к торможению или, как минимум, не даст никакого эффекта.
Давайте разберем несколько кейсов.
Если обе задачи выполняются почти мгновенно (например, просто установка переменной в контексте процесса), то разница между последовательным и параллельным выполнением будет незаметна, так что не стоит и огород городить.
Если обе задачи нагружают CPU и выполняются на одном ядре, процесс может не стать быстрее. Это уже надо смотреть с учетом имеющегося железа, на стадии проектирования такое трудно предвидеть. Пожалуй, лучше отложить эти тонкости на этап оптимизации процесса.
Если две задачи параллельно пишут в одну таблицу, они могут блокировать друг друга (например, из-за Row Locks в БД). В этом случае время выполнения может не улучшиться или даже ухудшиться из-за конфликтов.
Однако параллельные сервис-таски не только вредны, но и полезны! Есть ситуации, когда они реально ускоряют процесс:
Если выполняются разные операции (например, запрос в базу и вызов внешнего API или обращения по API к двум системам), то их параллельное выполнение ускорит процесс. Вместо последовательного выполнения, где вторая задача ждет завершения первой, обе выполняются одновременно.
Если используются разные серверные ресурсы, например, одна задача выполняет тяжелые вычисления, а другая запрос в БД. Эти операции могут выполняться на разных потоках, что разгрузит систему.
Кстати, все вышесказанное применимо к мульти-инстанс задачам, хотя это сразу может быть неочевидно. Например, вы получаете пакет заказов и хотите его поскорее обработать. Вроде кажется логичным создать параллельный мульти-инстанс, но это будет как раз ситуация работы с одним ресурсом, и в результате процесс может даже зависнуть.
Короче, прежде чем поставить сервис-таски в параллель, смотрите, что именно они делают и как реализованы.
7. Не полагайтесь на распределенные транзакции
Простое процессное приложение обычно работает со своей собственной базой данных и там вопрос о распределенных транзакциях не возникает. Поскольку любое широкое внедрение BPM в масштабах предприятия начинается с какого-то небольшого пилотного проекта типа заявок на пропуска или отпуска, то разработчики привыкают к тому, что не надо беспокоиться о распределенных транзакциях.
Но аппетит приходит во время еды — после первого успеха можно замахнуться и на большее. Например, взять и интегрировать процесс согласования заявок на отпуска с кадровой системой. А дальше подтянутся всякие микросервисы, брокеры сообщений и добро пожаловать в мир распределенных транзакций! Ваши сервис-таски теперь будут апдейтить данные в нескольких БД и вам придется отвечать за их согласованность. Здесь можно отбросить все ваши прежние подходы и просто поверить Бернду Рюккеру (Bernd Ruecker), соучредителю и главному технологу Camunda, что распределенные транзакции не работают. Вы не можете гарантировать согласованность данных, полагаясь только на привычные методы из мира БД.
Что делать? — Добиваться согласованности данных средствами BPMN. Для этого у вас есть механизм компенсаций и паттерн сага. Да, это сложнее, чем линейный процесс, где подразумевается, что за транзакции отвечает кто-то другой. Теперь это в вашей зоне ответственности, включая сценарии обработки сбоев.
Короче, запомните: если процесс изменяет данные в нескольких источниках, то вы имеете дело с распределенной транзакцией и это должно быть отражено в модели бизнес-процесса. В противном случае вы изрядно рискуете, полагаясь, что менеджер транзакций сам все разрулит.
См. подробнее в статье Бернда Рюкера
Достижение согласованности без менеджеров транзакций
8. Учитывайте особенности работы в кластере
Пока вы тестируете и отлаживаете процесс в своей песочнице, можно не принимать во внимание факт, что серверов Camunda бывает много. Однако на проде обычно используется кластер Camunda, и это вносит свою специфику.
Как правило, кластер Camunda развертывается в архитектуре с общей базой данных (Shared Database), когда несколько экземпляров BPM-движка подключаются к одному серверу БД. Это означает, что все экземпляры Camunda используют одну и ту же базу данных для хранения состояния процессов. При этом совершенно нормально, что задачи одного экземпляра процесса исполняются на разных узлах. С точки зрения повышения производительности это разумно — как только у какого-то узла освобождается ресурс, он берет себе следующую задачу все равно из какого процесса.
И снова здравствуй Optimistic Locking Exception! Кроме рассмотренных выше, в кластере возникают дополнительные сценарии, когда это возможно. Например, два узла могут взять в обработку один и тот же сервис-таск, но сделать в базе запись о его завершении может только один.
Или, когда задача выполняется слишком долго, затем прерывается по таймауту и уходит на fail retry. Пока первый узел отдыхает, ее берет второй и тоже виснет. В итоге может получиться, что весь кластер пытается выполнить одну и ту же задачу, которая никак не может завершиться. Такая же ситуация возможна и с таймером: два узла одновременно видят, что таймер истек, и оба пытаются обработать событие. Один узел успешно обновляет БД, второй получает Optimistic Locking Exception.
К счастью, это все не фатально. В Camunda есть настройки, которые позволяют успешно разрешить подобные ситуации. В конце концов, она ведь проектировалась с учетом работы в кластере. Но это все довольно сложно для новичков и часто оказывается, что кластер вместо ожидаемого повышения производительности приносит ворох новых проблем.
Заключение
Технология BPM только кажется простой благодаря наглядности нотации BPMN. Но под капотом все устроено довольно сложно. И если не понимать особенностей реализации конкретного BPM-движка, такого как Camunda, то очень просто наломать дров.
Конечно, можно свалить всю вину на продукт и пойти обратно кодить процессы вручную. Но лучше изучить опыт коллег и не повторять известные ошибки.
Надеемся, что эта статья вам поможет их избежать.
Подписывайтесь на Telegram канал BPM Developers.
Рассказываем про бизнес процессы: новости, гайды, полезная информация и юмор.