Optimalizace jBPM enginu – uchovávání informací o workflow v databázi
V CzechIdM využíváme workflow pro vykonávání aplikační logiky systému. Všechna tato workflow běží na enginu jBPM. O každém běžícím workflow si jBPM v standardně uchovává spousty informací jako jsou proměnné, aktuální stav apod. Tyto informace si ukládá do databáze a zůstávají v ní i poté, co dané workflow skončí, což v určitých případech způsobuje nárůst velikosti databáze. Proto jsme hledali způsob optimalizace jBPM.
Dosud jsme v případě velkého nárůstu databáze řešili ručním promazáním příslušných jBPM tabulek. Nyní jsme ho ale vyřešili sofistikovanějším způsobem, o kterém se právě dozvíte v tomto článku.
Řešení – správce workflow
Vytvořili jsme správce, který eviduje všechna spuštěná workflow a průběžne maže ta nepotřebná.
Každé workflow beží pod uživatelem a každý uživatel má přiděleno svoje unikátní sessionId – to identifikuje relaci jeho připojení k CzechIdM. Když dojde ke spuštění nového workflow, tak se toto workflow zaregistruje u správce pod sessionId uživatele, který ho spustil. Jakmile workflow doběhne, předá zprávu správci a ten ho rovnou smaže.
V CzechIdM používáme ale i workflow, která sama od sebe nikdy neskončí. To jsou zpravidla workflow, která zobrazují nějakou stránku. Takováto workflow jsou mazána poté, co skončí session uživatele (odhlásí se sám, nebo je automaticky odhlášen). Správce probere všechna workflow, která byla pod daným uživatelem spuštěna, a všechny je smaže.
Jediná výjimka je v případě workflow, která generují schvalovací požadavek. V tomto případě chceme, aby se workflow uchovávala i po skončení session uživatele, který daný požadavek vytvořil.
Abychom rozlišili smazatelná workflow od těch, které smazat nemůžeme, rozšířili jsme jejich definici o nový atribut – killable. Při nastavení killable=false říkáme správci, že takové workflow je potřeba uchovat v databázi i po skončení session uživatele a on ho tím pádem ignoruje.
<!-- definice smazatelneho WF-->
<process-definition xmlns="urn:jbpm.org:bcv_jpdl-3.2" name="user.edit" killable="true">
Promazání starých workflow
Výše popsaný správce ale řeší pouze nově spouštěná workflow a ne ta, co již déle leží v databázi. Bylo tedy zapotřebí projít databázi a smazat z ní nepotřebná workflow.
Instance workflow je v jBPM reprezentována objektem ProcessInstance. Ten se ukládá do tabulky databáze JBPM_PROCESSINSTANCE. Co zpravidla zabírá nejvíc místa v databázi, jsou tabulky obsahující proměnné použité ve workflow. Ty jsou uloženy v tabulkách JBPM_BYTEARRAY, JBPM_BYTEBLOCK a JBPM_VARIABLEINSTANCE. JBPM_VARIABLEINSTANCE obsahuje seznam použitých proměnných pro každé workflow a jejich hodnoty. V případě nestandardních typů proměnných jsou jejich hodnoty uloženy až v tabulkách JBPM_BYTEARRAY či JBPM_BYTEBLOCK.
Vytvořili jsme pravidlo clearCompletedProcessInstancesFromRepository, které postupně prochází databázi a maže z ní stará workflow. Každé workflow se načte z databáze do aplikace, kde se z něho zrekonstruuje objekt ProcessInstance, ten se korektně ukončí metodou instance.end() a smaže se z databáze metodou context.getGraphSession().deleteProcessInstance(instance).
Takhle smažeme všechna workflow, která v sobě nenesou nedokončený schvalovací úkol.
// Strucny vytah pravidla mazajici stare WF
public Object execute(JbpmContext context) {
Session session = context.getSession();
List result = session.createSQLQuery("select ID_ as id from JBPM_PROCESSINSTANCE;")
.addScalar("id", new org.hibernate.type.LongType()).list();
for (int i = 0; i < result.size(); i++) {
ProcessInstance instance = context.loadProcessInstance(Long.valueOf(result.get(i)));
if (!canBeDeleted(instance)) continue;
instance.end();
context.save(processInstance);
context.getGraphSession().deleteProcessInstance(processInstance);
}
}
V případě, že se vůbec schvalovací workflow nepoužívají, je možné kompletně smazat všechny jBPM tabulky (TRUNCATE), což je ostatně mnohem rychlejší.
Na co jsme narazili
Během testování funkčnosti naší opravy jBPM jsme narazili na několik záludností, u nichž jsme dále zvážili, jaký dopad mohou mít na uživatele a následně se rozhodli pro způsob řešení.
- Workflow spuštěné samotným CzechIdM, např. naplánovaná úloha, má přiřazené umělé sessionId; neexistuje žádná session, na jejímž konci by se mohlo workflow smazat z databáze. Pokud nedoběhne, zůstane v databázi natrvalo.
Řešení: Plánované procesy by měly vždy doběhnout bez chyby. - Pokud uživateli expiruje session a klikne např. na záložku Uživatelé, spustí se workflow user.list, které neprojde přes kontrolu oprávnění. CzechIdM pak uživatele přesměruje na přihlašovací stránku. Nicméně ProcessInstance se ještě zapsalo do repository (START_=null) a už tam zůstane navždy.
Řešení: Nevytvářet v tomto případě ProcessInstance vůbec, zkontrolovat ještě před spuštěním workflow, že je uživatel přihlášen. - Smazáním rodičovského workflow se vyvolá smazání jeho potomků. Při mazání procesů je tedy vhodné kontrolovat, jestli dané workflow už náhodou nebylo smazáno, aby se zbytečně nevyhazovaly výjimky. Na to lze použít JbpmContext.getProcessInstance (JbpmContext.loadProcessInstance vyhodí rovnou výjimku, když proces neexistuje – snaží se ho načíst).
Řešení: Vyřešeno obecně. Při skončení session se ve správci projde nejprve celý seznam spuštěných workflow a odeberou se z něj všechny non-killable workflow a jejich předci. Zbylá workflow ze seznamu se mohou smazat z databáze. Přičemž u každého prvku seznamu se nejprve ověří, že pořád ještě v databázi existuje a může být tedy smazán. - Pokud rodičovský proces není killable, potomek je killable a nedoběhnul, pak po vypršení session správce vyvolá ukončení procesu potomka. Při ukončení potomka jBPM notifikuje rodiče, že může pokračovat ve výpočtu. Nicméně v té chvíli už neexistuje session, což může vést k výjimce “No application context active”. Potomek se z tedy repository nesmaže, jen mu zůstane příznak “ukončen”. Ovšem toto je velmi nepravděpodné a u nás v praxi nenastane.
Závěr
CzechIdM stále vylepšujeme, doplňujeme a rozšiřujeme jeho možnosti. Úkolů ko zlepšení máme stovky, klíčovým rozhodovacím faktorem je pro nás názor našich zákazníků. Ve stručnosti, pracujeme na těch vylepšení, které chtějí zákazníci.
Optimalizace jBPM byl poměrně hluboký zásah do klíčové části CzechIdM, proto jsme postupovali velmi obezřetně. Po týdnu vývoje jsme vytvořili hot-fix produktu a vylepšení nasadili i u prvního zákazníka. Samotné nasazení do produkčního provozu u zákazníka i s otestováním zabere kolem 1-2MD dle velikosti zákaznického řešení. Nejvíce je věnováno přetestování.
Tímto jsem zde probral všechna vylepšení, které jsme provedli nad enginem jBPM. Kdybyste řešili podobné záludnosti s jBPM nebo jen měli nějaké dotazy, neváhejte mě kontaktovat na info@bcvsolutions.eu.