Bei der Analyse kam zum Vorschein, dass sich alte, nicht mehr gebrauchte Messages aus dieser Orchestration in der BizTalkMsgBoxDb ansammelten. Die BizTalk Engine erkannte offenbar nicht, dass die Messages nicht mehr benötigt wurden und gab diese nicht frei. Die immer grössere Message-Menge in der Message Box führte zur Verlangsamung, der Transaction Log wuchs stetig und sprengte irgendwann die Speicherplatzgrenzen.
Dieses Verhalten lässt sich durch folgende einfache Orchestration reproduzieren.
Der Ablauf:
1. Trigger wird empfangen.
2. Loop Count wird entnommen.
3. Loop
- Eine Message wird erzeugt.
- Eine Hilfs-Orchestration wird aufgerufen. Die Message wird by ref übergeben.
- Die Message wird versendet.
- Nächster Durchlauf wird vorbereitet.
Der entscheidende Punkt ist 3b.
Die aufgerufene CalledHelper Orchestration ist im Beispielfall absolut leer („Just Empty“) und tut nichts.
Durch die Übergabe des Message Orchestration Parameters by ref (Message geht rein, darf im CalledHelper neu zugeordnet werden und geht wieder raus) erkennen der BizTalk Compiler und die BizTalk Engine nicht, dass die Message nach dem Versenden nicht mehr benötigt wird. Sie bleibt bis zum Lebensende der langlaufenden Orchestration in der Messagebox.
Im Fortschritt kann das durch Abfrage der Spool Tabelle in der BizTalkMsgBoxDb beobachtet werden.
Im Bild zu sehen ist die Situation nach einigen Minuten Laufzeit. Die allererste Message ist die Trigger-Message, die weiteren aus den Loops. Auch aus den allerersten Loops sind die Messages immer noch da, hier sind es insgesamt schon 2233 Stück. Die Messages gehen nicht weg, solange die Orchestration läuft!
Wie kann dieses unerwünschte Verhalten verbessert werden? Eine gute Möglichkeit ist das Ersetzen der Übergabe by ref durch in.
Da die Message nur hereingegeben wird, erkennt der Compiler, wann sie nicht mehr benötigt wird. Die Messages werden nach dem Versenden aus der Messagebox entfernt. Ein positiver Nebeneffekt ist die höhere Performance, da Persistenzpunkte gespart werden. Wenn die Rückgabe einer geänderten (also neuen) Message notwendig ist, kann ein zusätzlicher out Parameter für die neue Message verwendet werden.
Als zweite Möglichkeit, für den Fall, dass der ref Parameter bleiben soll, geht Folgendes.
Das komplette Innere des Loops wird in einen Scope geschoben. Alle Messages, die nur im Loop benötigt werden, werden nicht ausserhalb, sondern im Loop Scope deklariert. Der Loop Scope muss zusätzlich den Transaction Type Long Running haben (die Orchestration selbst auch). Damit wird die Freigabe der unnötigen Messages erreicht, allerdings auf Kosten der Performance wegen zusätzlicher Persistenzpunkte.
Viel Spass beim Coding!
Plamen Petrow, Solution Architect