EJB Transactie Management
Goed transactie management is niet alleen een kwestie van juiste configuratie van je EJB deployment descriptors. Er zijn veel meer factoren die het juiste gedrag van je transacties kunnen beïnvloeden.
Het begrijpen van de fout afhandeling van de EJB container is een belangrijk deel van het bouwen van robuuste transacties. Als een transactie overgaat in een fout conditie is het belangrijk om te weten hoe de container om gaat met deze conditie. Bijvoorbeeld, in het geval van een niet afgevangen runtime exception in de onMessage methode van een Message-Driven Bean, zal de EJB container de transactie terug draaien, wat juist wel of niet het gewenste gedrag is.
Time-out gedrag is ook een belangrijke factor om succesvol transacties te beheersen. Er zijn veel verschillende niveau’s in een totale transactie waar time-outs zijn geconfigureerd en/of kunnen gebeuren; in elk van de betrokken (back-end) systemen, de EJB container, op het niveau van connection resources en op het niveau van de EJB zelf. Elk van deze settings en/of gebeurtenissen kunnen een impact hebben op iedere proces stap binnen een transactie. Dit zal niet altijd duidelijk zijn voor de casual observer (lees: developer). In een juiste configuratie zal de tijdsduur van de time-out waarde korter worden voor elke daaropvolgende proces stap. Dit garandeert dat het aanroepende systeem altijd langer wacht dan de maximum tijd die kan verstrijken voordat de proces stap beëindigd (of teruggedraaid) wordt, voordat de transactie opgegeven wordt. Het niet toepassen van dit basis principe kan (of beter gezegd: zal) een foutieve uitvoering van je proces veroorzaken.
Lees hier verder over een praktijk voorbeeld.
Hoe je project kan veranderen in een Whodunit als je transactie verkeerd gaat.
De laatste paar maanden ben ik gedwongen geweest om diep te graven in het onderwerp van EJB transactie management door de problemen waar we tegen aan liepen in ons huidige project. Er zijn genoeg artikelen over dit onderwerp te vinden, maar niets kan je helemaal voorbereiden op de problemen die in de praktijk kunnen opduiken. Dit artikel behandelt een aantal van deze praktische issues en laat zien dat de alledaagse baan van een software developer soms meer lijkt op die van een detective.
Ons project behelst transactie requests op een niet-transactioneel back-end systeem (naast andere back-end systemen). Het probleem dat zich telkens weer voor deed was het versturen van dubbele transactie requests naar dit back-end systeem.
Het behandelen van een gemiddelde transactie request in de applicatie bestaat uit het uitvoeren van een (beperkt) aantal proces stappen. Feitelijk wordt iedere proces stap uitgevoerd door een aparte Enterprise Java Bean (EJB). In dit geval werd de aanvraag eerst in een JMS queue geplaatst en later opgehaald door een Message-Driven Bean (MDB) om vervolgens doorgezet te worden naar diverse EJB’s voor verdere verwerking.
Zaak 1: De EJB container heeft het gedaan
De eerste keer dat we geconfronteerd werden met dubbele requests maakte een onderzoek van de log file snel duidelijk dat onze applicatie tegen een niet afgevangen runtime exception aan liep tijdens het uitvoeren van de onMessage methode van deze MDB. Het standaard gedrag van de EJB container bij zo’n gebeurtenis is om de transactie terug te draaien. De developer van deze MDB was zich niet bewust van dit fout afhandeling gedrag van de container. En hij had ook niet geanticipeerd op de mogelijkheid van de (ongecontroleerde) runtime exception die deze eerste bug onder onze aandacht bracht.
De bug was eenvoudig gerepareerd door de inhoud van de onMessage methode te omringen met een try-catch blok met bijbehorende fout afhandeling. Na het testen van de bugfix werd de applicatie opgeleverd en alles was in orde, althans dat dachten we. Een paar dagen later vroeg een vertegenwoordiger van het bedrijf of we de bugfix ook daadwerkelijk in productie hadden genomen, omdat het erop leek dat dezelfde bug op één of andere manier weer opdook.
Zaak 2: Het was de connectie pool manager
Deze keer was het niet zo eenvoudig als de eerste keer en het koste aanmerkelijk meer moeite om uit te vinden wat er nou daadwerkelijk fout ging. De eerste indruk was dat de transactie een extreem lange tijd nodig had om verwerkt te worden in het back-end systeem. Zou het kunnen zijn dat het de schuld was van een slechte performance van het back-end systeem?! Meer uitgebreide logging werd in de code opgenomen en dat haalde deze aanname onderuit. Pas na het inzetten van nog meer debug logging maakte een uitgebreide analyse van de applicatie log-file duidelijk dat het probleem in feite een klassiek voorbeeld was van een schaarse resource veroorzaakt door, in dit geval, ongeschikt (onnodig) gebruik van synchronized methodes.
Om met het back-end systeem te verbinden wordt een connectie pool gebruikt. Helaas bleek dat de connectie pool manager slecht was ontworpen. Het werd niet alleen gebruikt voor het openen van een connectie op een gesynchroniseerde manier, maar het was ook geïmplementeerd om een connectie af te sluiten door middel van een synchronized methode. Daarbij was de connectie pool manager ook nog ontworpen om af en toe een clean-up van zijn pool uit te voeren. Dit proces lockt de pool manager gedurende de iteratie over de pool tijdens het testen van ieder vrije connectie en het verwijderen van de dode connecties. Dit kan er de oorzaak van zijn dat een (anderszins succesvolle) transactie wordt geblokkeerd voordat het zijn connectie kan sluiten en vrijgeven naar de pool. En er daardoor uiteindelijk een time-out wordt veroorzaakt waardoor de transactie wordt terug gerold.
Deze situatie deed zich alleen maar voor indien de applicatie een aanzienlijke proces belasting ontving waardoor de connectie pool manager een schaarse resource werd. Dit probleem is opgelost door de code te refactoren en het sluiten van de connectie direct uit te voeren op de connectie, in plaats van via de connectie pool manager.
Zaak 3: Het bleek toch uiteindelijk de configuratie te zijn
Een andere oorzaak van dubbele transacties bleek uiteindelijk de onjuiste configuratie van time-out waarden te zijn. Deze settings kunnen op veel verschillende niveau’s bestaan; in ieder deelnemend (back-end) systeem, de EJB container, op het niveau van de connection resources en op het niveau van de EJB zelf. Elk van deze settings kan een impact hebben op iedere proces stap binnen een transactie wat niet altijd even duidelijk is voor de gemiddelde ontwikkelaar en/of systeembeheerder. Echter, het kan een groot effect hebben op hoe vloeiend het proces verloopt. In een juiste configuratie zal de tijdsduur van de time-out waarde steeds korter worden voor elke daaropvolgende proces stap. Dit garandeert dat het aanroepende systeem altijd langer wacht dan de maximum tijd die mag verstrijken voordat de proces stap beëindigd (of teruggedraaid) wordt, voordat de transactie opgegeven wordt.
Zaak 4: Indien dubbele aanvragen onvermijdelijk zijn
Na eliminatie van alle oorzaken binnen onze applicatie, kwamen we nog steeds condities tegen die we niet in de hand hadden (verstoring van netwerk communicatie, etc.) dat veroorzaakte dat de applicatie dubbele requests ontving van een extern systeem. Dit bewijst het belang van defensief programmeren en de enige manier om dit issue op te lossen was het controleren van de unieke identiteit van elke request voordat gestart werd met het verwerken.
Ik hoop met dit verhaal te hebben aangetoond dat transactie management niet alleen een kwestie is van juiste configuratie van je EJB deployment descriptors (al helpt het wel). Er zijn veel meer factoren bij betrokken die een transactie om zeep kunnen helpen. Een goed begrip van het transactie gedrag van alle deelnemende componenten is essentieel om een robuuste enterprise applicatie te ontwerpen en te bouwen.


