Wie der Hybrid Connection Manager den Datenbank Admin nervt UPDATE [02.01.2018]: siehe Abschnitt 'Andere Sprache' und 'Res...

Von hinten durch die Brust ins Auge

thunder

Wie der Hybrid Connection Manager den Datenbank Admin nervt

UPDATE [02.01.2018]: siehe Abschnitt 'Andere Sprache' und 'Resümee'

Seit ich mich mit den Azure Functions auseinandersetze höre ich von verschiedenen Leuten witzige aber auch unangenehme Bugs. Die meisten lassen sich auf die dahinterliegende Architektur rückschließen. Andere Fehler sind nicht wirklich nachvollziehbar oder haben ihre Ursache an völlig anderen Stellen. Nicht immer, wenn etwas nicht funktioniert, liegt der Grund in der Cloud. Einen speziellen Fall möchte ich euch hier vorstellen. Dazu muss ich aber einen kleinen Umweg gehen, denn wer sich bisher nicht all zu viel mit den Function Apps auseinandergesetzt hat könnte sonst den Faden verlieren.

Datenintegration mit Azure Functions

Azure Functions sind, als sie das erste mal publiziert wurden, unter anderem als Werkzeug für Data Pipelines beschrieben worden. Das ist im Grunde auch in Ordnung, wenn man kein Echtzeitverhalten erwartet.

Für diese Pipeline richtet man eine Function App ein, die mindestens einen Endpunkt hat, der entweder getriggert wird, um Daten aus einer Datenbank zu lesen oder direkt Daten empfängt. Das geschieht in jedem Fall mittels eines HTTP Triggers.

Daten aus einer Datenbank zu lesen ist eigentlich ziemlich einfach, wenn diese ebenfalls in der Azure Cloud eingerichtet ist. Dann kann man eine API Connection dahin aufbauen und mittels IN-Binding aus einer Tabelle/View die Daten lesen. Alles hübsch soweit, wäre da nicht die hässliche Realität. In dieser Realität sind die Datenbanken OnPrem im eigenen Data Center. Hierfür gibt es eine tolle Lösung, den Hybrid Connection Manager, quasi ein Reverse Proxy. Die Realität erlaubt sich einen weiteren Knüppel in das sauber geschmierte Rad der Azure Functions zu werfen, die Daten an sich. Wenn man ein IN-Binding auf eine Tabelle/View einrichtet, werden bei jedem Aufruf der Funktion alle Zeilen gelesen. Das mag bei kleinen Datenmenge okay sein, aber schon ab 500.000 Datensätzen fängt es an sehr langsam zu werden. Der Grund hierfür: das virtuelle Dateisystem hinter den Function Apps, das eine ziemlich schlechte IO Verwaltung hat. Noch ist das so, es wird aber sicher besser.

Wenn man also viele Daten laden möchte, muss man sich des Paging oder den Continuations bemächtigen. Sprich seitenweises Laden der Daten. Dann steht man mit dem IN-Binding auf verlorenem Posten. Was nun?

Ein bisschen Old School

Man kann also das beeindruckend praktische IN-Binding nicht nutzen und auch eine direkte Connection zu den Daten existiert nicht. Warum dann die Functions dafür nutzen? Weil es ein supereinfaches System ist, bei dem man nur ein bisschen Code schreibt, um ans Ziel zu gelangen. Wollte man zum gleichen Ergebnis kommen ohne einer Function App, muss man sich um den Host und die ganze Infrastruktur kümmern. Das möchte man nicht, zumindest ich möchte das nicht mehr. Also bleibe ich bei der Lösung mit der Functions App aber mit ein bisschen Old School.

Wenn man Daten aus einer Datenbank holt und dafür C# verwendet, dann ist das okay, aber nervend, weil damit alles typisiert sein muss. In der Azure Welt wird zum Glück sehr viel mit JSON gearbeitet. Darum ist es sehr praktisch, dass man Funktionen in einer Functions App in JavaScript schreiben kann. Für ein hübsches Schriftbild verendet man üblicherweise das npm package mssql, um Daten aus einem SQL Server zu lesen.

Well connected

Alles ist vorbereitet. Wir haben eine Function App mit einer Funktion, geschrieben in JavaScript, die super einfach Daten aus einer Datenbank liest und irgendwie weiterverarbeitet. Blöd nur wenn plötzlich ein Datenbank Admin neben einem steht und fragt:

"Samma, wat hastn da jebaut? Ick hab hier hundate offne Connections. Dein User baut zwar uff, abber nich mehr ab."

Wie jetzt? Ich habe doch überall das notwendige pool.close() oder sql.close() eingebaut.

Nach einiger Analyse stellt sich tatsächlich heraus, dass die Connection zwar geöffnet wird, aber nicht wieder abgebaut. Wie kann das sein? Eine Analyse muss her.

Anderes Package

Wenn es am mssql Package liegt, dann wechselt man eben auf tedious, welches zwar im Hintergrund von mssql läuft, aber man kann ja nicht wissen.
Ohne Erfolg, die Connections bleiben auch hier bestehen und werden nicht geschlossen.

Datenbank in der Cloud

Nanu, damit geht es plötzlich? Wenn die Datenbank in der Cloud läuft, dann baut sich die Connection ab. Okay, hier wird keine Hybrid Connection Manager gebraucht.
Blöd nur, dass die Datenbank meistens aus rechtlichen Gründen nicht in der Cloud laufen darf. Das ist also keine Option als Lösung.

Andere Sprache

Bei genauerer Betrachtung der anderen Projekte, die ich mit Functions umgesetzt habe sieht man offensichtlich, dass ich mit C# und JavaScript arbeite. Ausgerechnet die Funktionen in C# und mit Datenzugriff scheinen die Connection sauber zu schließen, bzw. den Connection Pool korrekt zu nutzen. Sprich wenn ich alle Funktionen, die Datenbankzugriffe haben, auf C# umstelle, dann könnte ich auf der sicheren Seite sein.
UPDATE: Falsch gedacht. Nach eingehender Betrachtung habe ich feststellen müssen, dass der Connection Pool mit C# zwar eine Weile lang weiter verwendet wird, sprich keine weiteren Connections aufgebaut werden, aber nach einer Weile - Zeitraum unbekannt - werden weitere Pools angelegt. Die bereits vorhandenen werden dann nicht abgebaut, sondern bleiben bestehen. Mist aber auch. :(

Resümee

Nach einigen Tests ergab, dass JavaScript zwar die coolere Sprache für die Cloud ist, weil alles sofort in JSON zur Verfügung steht. Wenn aber in der Kombination JavaScript + Hybrid Connection Manager + SQL Server die Connections nicht wieder abgebaut werden, muss man in den sauren Apfel beißen und JavaScript durch C# + Newtonsoft.Json austauschen. In diesem Fall hat alles bestens funktioniert. Warum die Connections nicht abgebaut werden ist nicht wirklich klar. Dass in diesem Fall die Function Apps nichts damit zu tun haben würde ich daraus schließen, dass bei der Verwendung von C# alles okay ist und wenn man mit JavaScript eine Datenbank in der Cloud abfragt ist auch alles okay.
UPDATE: Leider falsch, siehe 'Andere Sprache'. Auch unter C# werden die Connections nicht korrekt abgebaut. Was man nun versuchen kann, ist das External Table Bindig zu verwenden. Dieses ist jedoch immer noch (seit fast zwei Jahren) im experimental Stadium.