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

00bbcc88d09be8e8c3e74fc8667a51fc.jpg

Итак, вы смоделировали все процессы, написали бизнес-логику и задеплоили все на сервер. Запускаем наши процессы на проде! Поехали? — Но дальше разложено множество граблей, на которые обычно наступают все, кто только начинает эксплуатировать 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.
Рассказываем про бизнес процессы: новости, гайды,  полезная информация и юмор.

© Habrahabr.ru