wir haben hier eine Konstellation, welche immer mal wieder zu Poolproblemen führt und zu Fehlern wie “zu viele Verbindungen” führt oder auch banal zu Verzögerungen bei DB-Anfragen.
Wir betreiben einen Webservice, dieser bedient auf einem PSQL-Server viele Datenbanken. Welche Datenbank zu verwenden ist, wird anhand der Mailadresse ermittelt, welche einen Endpunkt anspricht. Dabei geht der Webservice zunächst in eine bestimmte Datenbank, in welcher eine Zuordnungstabelle zwischen E-Mail und Ziel-Datenbank liegt.
Jeder Aufruf eines Endpunktes erzwingt somit die Verbindung zur Zuordnungs-Datenbank und danach die Verbindung zur eigentlichen Ziel-Datenbank. Die Verbindung wird ganz normal geöffnet und danach wieder geschlossen. Im Connectionstring wird das Flag “WithPooling” auf “true” gesetzt. Ich gehe davon aus, dass der Datenbankserver hier in irgendeiner Form die Connections poolt.
Jeder Aufruf eines Endpunktes kommt allerdings immer mit einem neuem Thread daher. Dies verhindert zum Beispiel, dass ich die Connections selber in einem Dictionary o.ä. aufheben kann, den ein threadübergreifendes Verwenden einer Connection geht nicht. Jeder einzelne Aufruf eines Endpunktes - auch zur gleichen Datenbank - erzwingt also eine neue (i.d.R. kurzlebige) Verbindung.
Was kann man nun tun, um die Verbindung zur DB so effektiv wie möglich zu gestalten?
Hm, wir benutzen den Treiber von “devart”, dort steht in der Beschreibung was von “Advanced connection pooling”, dass klingt ja schon mal gut. Ansonsten werden wir uns mit dem “pgBouncer” beschäftigen, der liest sich recht praktisch und offenbar muss man in der Clientconnection lediglich den Port wechseln, um ihn zu nutzen.
Ja, Connection Pooling im Applikationsserver ist das Beste.
Allerdings gibt es einen Fall, wo das nicht gut genug ist: wenn die Applikation so lahm ist, daß man mit der Workload nicht anders fertig werden kann, als daß man 27 Instanzen des Applikationsservers startet. Dann sind es 27 Connection Pools, und selbst wenn jeder einzelne vernünftig dimensioniert ist, ist das Ganze kein effektives Pooling mehr.
Dann braucht man einen externen Pooler wie pgBouncer, normalerweise auf der Datenbankmaschine.
Jetzt muss ich doch noch mal zum Pooling was fragen. Ich glaube, dass ich irgendwas noch nicht richtig verstanden habe…
Wir haben ein Testszenario aufgebaut, welches jede Menge Daten liest, schreibt oder löscht. Das geschieht größtenteils alles durch das Entity Framework. Dann haben wir diesen Testlauf einmal mit “Pooling = false” und einmal mit “Pooling = true” im Connectionstring laufen lassen. Ein Kollege hat die Anfragen an den Datenbankserver mitprotokolliert.
In beiden Läufen kommen wir ca. 33.000 Connections (Auswertung mit pgBadger). Die Laufzeit ohne Pooling ist erwartungsgemäß erheblich länger als mit Pooling. Der pgBouncer ist noch nicht installiert, wir wollten lediglich das Pooling des Devart-Treibers testen. Irgendwie wundert es mich, dass mit Pooling genauso viele Connections etabliert werden als ohne. Die Connections gehen alle in die gleiche Datenbank, hätte das nicht auf eine Connection reduziert werden müssen? Oder stehe ich irgendwo auf dem Schlauch?
Ich muss mich entschuldigen, wir hatten beim zweiten Mal einen alten Stand getestet.
Mit aktivem Pooling gehen die 33.000 Connections auf 220 zurück. Das Pooling hat also einen deutlichen Effekt. Dennoch bleibt die Frage, wieso es nicht theoretisch eine Connection pro Datenbank gibt…
Meine Vermutung wäre, die Anwendung macht mehrere gleichzeitige Zugriffe auf die Datenbank (“multi threading”).
Auf einer physischen Verbindung kann zu einem Zeitpunkt genau ein SQL Befehl ausgeführt werden. Will (muss) die Anwendung zwei SQL Befehle gleichzeitig ausführen, braucht sie zwei (physische) Verbindungen.
Ah ja, richtig, genau das passiert auch. wir haben das nochmal in Ruhe analysiert. Zum Einen gibt es parallel Zugriffe auf die gleiche DB und zum Anderen wurde durch einen Fehler eine bestimme Connection nicht geschlossen.
Wir haben den Code jetzt korrigiert und nun sieht das Verhalten viel besser aus. Es werden genauso viele Verbindungen aufgemacht, wie eben nötig und diese werden auch zügig wieder abgebaut. Das Pooling im Treiber funktioniert sehr gut, der Treiber stellt die gleiche Datenbankverbindung immer wieder zur Verfügung. Der pgBouncer ist von der Sache her nicht nötig, evtl. wird das aber im Massenbetrieb doch noch mal ein Thema. Wir reden dann von 20-30 Datenbanken und vielleicht 50-100 gleichzeitigen Usern.