Hallo zusammen,
weiß jemand, wo ich Informationen über das Laden/Entladen einer UDR-lib (dll oder .so) bekomme? Dh, wann eine UDR-lib geladen wird und wann sie wieder entladen wird?
Mein aktueller Stand ist, zusammengereimt aus einem Firebird-Bugticket https://github.com/FirebirdSQL/firebird/pull/8992,
dass eine UDR-lib lazy geladen wird, dh, nicht bei Serverstart und auch nicht beim connect, sondern erst, wenn in einer connection eine Prozedur/Funktion aus der UDR aufgerufen wird.
Und dass sie 60 Sekunden, nachdem keine Connection mehr aktiv ist, wieder entladen wird.
Konkret fiel mir bei einer Webanwendung (dh kurzlebige Connections) auf, dass die Ausführung einer UDR-SP reproduzierbar deutlich länger brauchte, wenn zwischen den Connections mehr als eine Minute Pause war. Nach etlichen Tests war klar, dass die Verzögerung durch das Neuladen der UDR-lib verursacht wurde. In Statistiken von DBA-Tools wird diese Zeit bei der prepare-Zeit ausgewiesen.
Ich habe beim Test eine relativ große UDR-lib verwendet (10 MB), der Firebird-Server braucht lokal ca 260 msek, sie zu laden. Bei kleinen UDR-libs sieht man den Effekt nicht bzw geht er im Rauschen unter.
Zusätzlich gab es folgenden schrägen Effekt: Jedes Neuladen der lib dauerte etwas länger, zwischen 30 und 50 msek. Nach 20 Ausführungen (immer mit mindestens einer Minute Pause dazwischen) dauerte es bereits 1.1 Sekunden, die lib zu laden. Und blieb dann auf dem hohen Wert bei weiteren Runden. Erst nachdem Firebird neugestartet wurde, dauerte das Laden wieder 260 msek - und stieg dann wieder an.
Das sind mehrere Probleme:
1. das UDR-Laden ist ein Performancefresser und passiert zu häufig
2. die Ladezeit sollte nicht anwachsen
3. bei gleichzeitigem UDR-Laden durch verschiedene Connections (also hohe Last) kommt es gelegentlich zu Konflikten, wenn es den Server beim lib-Laden auf dem falschen Fuß erwischt. Die Fehlermeldung ist dann bspw "Error while parsing procedure TEST_SP's BLR Zugriffsverletzung bei Adresse 4711 in Modul myudr.DLL". Je länger das Laden dauert, desto wahrscheinlicher wird ein solcher Konflikt, falls das Laden nicht 100% abgesichert ist.
Deshalb bitte ich diejenigen hier, die UDRs im Einsatz haben, zu versuchen, den Effekt zu reproduzieren. Wichtig wären mir die Punkte 1 und 2.
Testaufbau, zb in Flamerobin / dbeaver:
eine UDR-Funktion/UDR-SP aufrufen,
prepare-Zeit notieren, commit,
reconnect,
eine Minute warten
und das ganze 10-20 mal. Beim Test darf es keine weitere connection zum Server geben.
Danke! Gruß vr
Ladeverhalten einer UDR-lib
Moderator: thorben.braun
- martin.koeditz
- Beiträge: 543
- Registriert: Sa 31. Mär 2018, 14:35
Hi,
das müsste ich tatsächlich mal mit meinen UDRs versuchen. Vielleicht dauert nicht das Laden der UDR so lange, sondern das Laden der DB-Seiten. Bei Web-Anwendungen wird meist auf den Pool zugegriffen. In der firebird.conf kannst du ja das Timeout für die Connection-Pools hinterlegen. Nach Ablauf werden diese abgeräumt. Damit gehen auch einige Seiten-Caches verloren.
Ich weiß nicht, ob die UDRs überhaupt entladen werden. Kann ich mir eigentlich nicht vorstellen, da sie ja separat registriert werden. Aber da kommen wir schon dahinter.
Viele Grüße
Martin
das müsste ich tatsächlich mal mit meinen UDRs versuchen. Vielleicht dauert nicht das Laden der UDR so lange, sondern das Laden der DB-Seiten. Bei Web-Anwendungen wird meist auf den Pool zugegriffen. In der firebird.conf kannst du ja das Timeout für die Connection-Pools hinterlegen. Nach Ablauf werden diese abgeräumt. Damit gehen auch einige Seiten-Caches verloren.
Ich weiß nicht, ob die UDRs überhaupt entladen werden. Kann ich mir eigentlich nicht vorstellen, da sie ja separat registriert werden. Aber da kommen wir schon dahinter.
Viele Grüße
Martin
Martin Köditz
SynDesk SW GmbH
SynDesk SW GmbH
Das Problem bei den Anwendungen ist das falsche Konzept für das behandeln der Verbindungen.
Unabhängig vom Timer wird der Speicher freigegeben, wenn die letzte Verbindung geschlossen wird.
Somit sind alle Ressourcen der DB wieder verfügbar.
Da nützt es auch nichts, den Seiten-Cache zu erhöhen o.ä.
Nun gibts 2 Möglichkeiten:
1:
Einschalten des Verbindungspoolings:
- Jeder Close erfolgt nicht physisch, die Verbindung wird in den Pool gestellt
- Beim Open wird eine Verbindung aus dem Pool geladen und falls leer, eine Weitere erzeugt.
Vorteile:
- Hohe Verwendungsrate der DB-Seiten im Cache
- Schnelleres Verbinden, da die ja schon vorhanden sind
- Datensicherungen sind auch im laufenden Betrieb möglich
Nachteile:
- Die DB ist i.d.R. dann niemals frei für u.U. anfallende Verwaltungsarbeiten.
- Ggf. eine hohe Anzahl von Poolverbindungen, die aus Stoßzeiten resultieren, vorhanden, die nie wieder benötigt werden. Was sich allerdings, falls der Treiber das unterstützt, auch konfigurieren läst (MinPoolSize, MaxPoolSize).
2:
Wenn man keine Pools mag oder der Treiber keine verwaltet, kann man eine statische App-Connection, die nie verwendet wird, einrichten. Da somit eine Verbindung immer offen bleibt, bleibt der Cache geladen.
Dabei ist darauf zu achten, dass diese keine offene Transaktion festhält, da damit die gesicherten Transaktionsdaten sehr schnell auflaufen, die DB langsamer wird, und der Sweep halt nicht startet (oldest interested Transaction).
Grundsätzlich muss man mal messen, wie lange denn der Open i.d.R. dauert, wenn man keinen Pool verwendet. D.h. dass selbst das simpelste Laden einer einzelnen Zeile um die Openzeit drastisch verlängert wird! Dies gilt vor allem für Server-Anwendungen, wie Web oder Multi-Clients.
Im Beispiel C# kann man auch Using() verwenden, da der implizite Dispose() die Verbindung in den Pool stellt.
Ich habe mir da einen eigenen Pool gebaut, der die Anzahl der Verbindungen auf eine max. Anzahl beschränkt, da auch im Multithreading eben parallele Anfragen vom Server wieder sequentialisiert werden. Das geht gut mit z.B. einer ConcurrentQueue/ConcurrentStack, da hier solange gewartet werden kann, bis eine Verbindung wieder verfügbar wird.
Übrigens:
Man kann die FetchSize z.B. locker auf 32.000 stellen (Default ist 200) um bei Abfragen, die mehr als 200 Zeilen betragen, die Anzahl der Server-Roundtrips zu minimieren. Das kann u.U. zur 100%-tigen Leistungssteigerung bei einer einzelnen Abfrage führen (also doppelt so schnell).
Unabhängig vom Timer wird der Speicher freigegeben, wenn die letzte Verbindung geschlossen wird.
Somit sind alle Ressourcen der DB wieder verfügbar.
Da nützt es auch nichts, den Seiten-Cache zu erhöhen o.ä.
Nun gibts 2 Möglichkeiten:
1:
Einschalten des Verbindungspoolings:
- Jeder Close erfolgt nicht physisch, die Verbindung wird in den Pool gestellt
- Beim Open wird eine Verbindung aus dem Pool geladen und falls leer, eine Weitere erzeugt.
Vorteile:
- Hohe Verwendungsrate der DB-Seiten im Cache
- Schnelleres Verbinden, da die ja schon vorhanden sind
- Datensicherungen sind auch im laufenden Betrieb möglich
Nachteile:
- Die DB ist i.d.R. dann niemals frei für u.U. anfallende Verwaltungsarbeiten.
- Ggf. eine hohe Anzahl von Poolverbindungen, die aus Stoßzeiten resultieren, vorhanden, die nie wieder benötigt werden. Was sich allerdings, falls der Treiber das unterstützt, auch konfigurieren läst (MinPoolSize, MaxPoolSize).
2:
Wenn man keine Pools mag oder der Treiber keine verwaltet, kann man eine statische App-Connection, die nie verwendet wird, einrichten. Da somit eine Verbindung immer offen bleibt, bleibt der Cache geladen.
Dabei ist darauf zu achten, dass diese keine offene Transaktion festhält, da damit die gesicherten Transaktionsdaten sehr schnell auflaufen, die DB langsamer wird, und der Sweep halt nicht startet (oldest interested Transaction).
Grundsätzlich muss man mal messen, wie lange denn der Open i.d.R. dauert, wenn man keinen Pool verwendet. D.h. dass selbst das simpelste Laden einer einzelnen Zeile um die Openzeit drastisch verlängert wird! Dies gilt vor allem für Server-Anwendungen, wie Web oder Multi-Clients.
Im Beispiel C# kann man auch Using() verwenden, da der implizite Dispose() die Verbindung in den Pool stellt.
Ich habe mir da einen eigenen Pool gebaut, der die Anzahl der Verbindungen auf eine max. Anzahl beschränkt, da auch im Multithreading eben parallele Anfragen vom Server wieder sequentialisiert werden. Das geht gut mit z.B. einer ConcurrentQueue/ConcurrentStack, da hier solange gewartet werden kann, bis eine Verbindung wieder verfügbar wird.
Übrigens:
Man kann die FetchSize z.B. locker auf 32.000 stellen (Default ist 200) um bei Abfragen, die mehr als 200 Zeilen betragen, die Anzahl der Server-Roundtrips zu minimieren. Das kann u.U. zur 100%-tigen Leistungssteigerung bei einer einzelnen Abfrage führen (also doppelt so schnell).