Firebird 5 geht in den Beta-Test

Alles was nicht direkt zu den obigen Foren passt, findet hier Platz. Also Fragen zu allem was generell firebirdspezifisch ist oder sonst einen Bezug zum Forum hat.

Moderator: martin.koeditz

Benutzeravatar
martin.koeditz
Beiträge: 443
Registriert: Sa 31. Mär 2018, 14:35

bfuerchau hat geschrieben: Do 4. Mai 2023, 09:44 Queryzeiten:
Bei einer komplexen Aggregat-Abfrage über eine Tabelle mit 1. Mio. Zeilen benötigt die FB auf einem I7 mit 20CPU's, 64GB Speicher, 2,6GHz und SSD ca. 70 Sekunden.
Diese Abfrage wird in verschiedenen Varianten 3 Mal geladen.
Dies macht 210 Sekunden.
Mache ich diese Abfragen über parallele Connections, benötigt jede Abfrage ca. 135 Sekunden. Durch die Parallelität bleibt es aber bei 135 zu 210 Sekunden eben besser.
Die Frage ist nun, warum 1 Abfrage über 1 Tabelle einzeln 70 und parallel 135 Sekunden dauert.
Guten Morgen bfuerchau,

nutzt ihr VMs oder sind das dezidierte Installationen auf einem "Blech"?

Gruß
Martin
Martin Köditz
it & synergy GmbH
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Das ist mein Entwicklungslaptop.
Bei den Kunden sind i.d.R. VM's, ist ja heute Standard.
Nach langer Überredung habe ich be dem einen oder anderen Kunden meine Empfehlung
- 8 CPU's
- 64 GB Speicher
schon mal durchbekommen. Welche Queryzeiten da dann sind, kann ich nicht sagen.

Von der Grundinstallation werden immer 2 Server verwendet.
- Web Server IIS (.Net-Entwicklung)
- Datenbank-Server (nur DB und ETL)
Ob meinen Empfehlungen, auch die FB-Config anzupassen, folge geleistet wird, kann ich nicht sagen.
Meine Testerfahrung weisen auf diese Einstellungen:

TempBlockSize = 8M
TempCacheLimit = 512M
DefaultDbCachePages = 500000 <= 16K = 1 Page ca. 8GB, je nach Speicher anpassen.

Der Web-Server hält permanent 1 Verbindung offen um den Pool nicht zu löschen (letzter Close leert ja alles). Diese macht auch bei jeder neuen Abfrage vor einen Commit damit keine alten Transaktionen offen sind.
Alle anderen Abfragen werden über eine eigene Pool-Verwaltung gesteuert, die die Verbindungen nach 15 Sekunden Idle wieder trennt.

Ich habe bzgl. eines anderen Tests mal kleine Parallelel Abfragen getestet.
Also simple "Select f1, f2, f3 from MyTable".
Bei meiner Konstellation kann ich dann ca. 250.000 Zeilen/Sekunde lesen.
Jetzt hat die Tabelle aber 6 Felder. Lese ich alle 6 reduziert sich das dann auf ca. 125.000/Sekunde.
Also habe ich gedacht, ich lade die Tabelle über 2 Verbindungen parallel.
Also 2 Threads mit einen "Select f1, f2, f3 from MyTable" und einen mit "Select f4, f5, f6 from MyTable".
Was muss ich sagen? Die beiden Abfragen liefern mir jeweils wieder ca. 125.000 Zeilen per Sekunde.
Satzversionen sind keine vorhanden.
Ich weiß ja, dass die FB die Zeilen erst mal dekomprimieren müssen und das nun gleich 2 Mal. Jedoch bei der 3-Feld-Abfrage schafft die FB ja das doppelte.
Ich habe einfach die Vermutung, dass bei paralleler Abfrage derselben Daten die Pages im Speicher gegenseitig gesperrt werden. Also als ob es eben eine Lesesperre der Pages gibt. Durch die Versionierung ist ja eigentlich ein Lock nicht erforderlich.

Ich werde noch verschiedene Dinge ausprobieren.
Bei einem 64-GB-Speicher kann ich ja mal 32GB Cache definieren.
Das wären dann 2Mio. Pages. Ob das aber performant sein wird weiß ich noch nicht, denn die müssen ja auch verwaltet werden.

Unsere DWH-Datenbanken sind im Schnitt 40-80 GB, Tendenz steigend, da ja ständig Daten dazu kommen. Ein Save/Restore, 1x wöchentlich, bereinigt wieder ganz gut und verkleinert die DB's um ca. 20%.

Da die Dashboards z.T. Massendaten mit mehreren Tabellen und ca. 1 Mio. Zeilen á 15-30 Spalten umgehen, brauchen wir Performance. Der SQL-Server wäre da ca. 20% schneller, aber der Preis steht dazu nicht im Verhältnis.
Benutzeravatar
martin.koeditz
Beiträge: 443
Registriert: Sa 31. Mär 2018, 14:35

Ich habe einfach die Vermutung, dass bei paralleler Abfrage derselben Daten die Pages im Speicher gegenseitig gesperrt werden. Also als ob es eben eine Lesesperre der Pages gibt. Durch die Versionierung ist ja eigentlich ein Lock nicht erforderlich.
Welche Isolationsstrategie verwendest du hier? Mich würde mal interessieren ob ein READ UNCOMMITTED hier einen Leistungsunterschied bringt.

Gruß
Martin
Martin Köditz
it & synergy GmbH
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Bei uns gehts um ein Datawarehouse (neu: DataLake).

Das mag schon sein, allerdings sind dann die Daten ggf. nicht konsistent.
Durch die Satzversion und ReadCommitted ist es für Dashboards garantiert, Daten zu bekommen.
Ein ETL-Prozess kann Daten zu jeder Zeit erstellen/ergänzen.
Hier werden Deletes und/oder "Insert or Update" durchgeführt.
Wenn dieser aber abschmiert (gezielt oder durch Fehler), wird ein Rollback gemacht und die vorherigen Daten sind wieder da.
Lese ich uncommitted bekomme ich beim Dashboard u.U. zu wenig Daten, da ein Teil oder alles schon gelöscht und aber noch nicht vollständig wieder geladen wurde.
Erzähl das mal einem Kunden, dass seine Dashboards mal alle, mal teilweise oder keine Daten haben.

Zur Zeit arbeite ich an einer InMemory-DB für FB, die alle benötigten Tabellen nach Bedarf native in den Speicher lädt und Joins sowie Where zur Laufzeit nur im Speicher gemacht werden. Das lesen der Tabellen (wie an meinem komplexen SQL zu sehen) lässt sich in Einzelschritten erheblich schneller laden.
Der neu .Net-Client 9.1.1 schafft z.B. 60 Mio. Zeilen mit 3 Spalten mit ca. 320.000 Zeilen / Sekunde zu lesen. Bei nur 1 Mio Zeilen sind das sogar 450.000 Zeilen / Sekunde.
Der Grund hier ist der Net-Garbagecollector der dann beginnt auszubremsen weil Millionen Objekte erstellt und wieder zerstört werden.
Da arbeite ich gerade noch an einer Verbesserung.

Der SQL-Server schafft hier vergleichbar ca. 675.000 Zeilen / Sekunde konstant über alle 60 Mio. Zeilen, was allerdings wohl an der fehlenden Komprimierung liegt.
Aber wer kann sich schon einen SQL-Server leisten, außer der Developer-Version.
Benutzeravatar
martin.koeditz
Beiträge: 443
Registriert: Sa 31. Mär 2018, 14:35

Moin bfuerchau,

werden die Änderungen in den offiziellen .NET-Treiber einfließen? Eventuell können wir das ebenfalls auch auf andere Treiber (z.B. PHP) anwenden.

Gruß
Martin
Martin Köditz
it & synergy GmbH
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Ich habe einen Vorschlag mit entsprechenden Hinweisen ins Git-Forum eingebracht.
https://github.com/FirebirdSQL/NETProvider/issues/1110
Aber selbst nach 5 Tagen hat noch niemand darauf reagiert.

Die Quellen liegen ja vor und ich habe mir das Thema mal angesehen.
Allerdings sind da so viele Abhängigkeiten gegeben, dass eine einfache Umsetzung nicht möglich scheint.

Es steht und fällt mit dem sog. FetchSize (max. 32768). Dieser beschreibt die max. Anzahl Zeilen, die mit einem Call vom Server abgerufen werden können.
Diese werden erst in eine Queue geschrieben und dann bei jedem Read() aus der Queue gelesen. Hierbei wird dann je Zeile eine DbValue[] Array erstellt, dass die Werte enthält.
Bei 1.Mio. Zeilen also 1 Mio.*Anz.Spalten DbValue-Objekte.
Beim Lesen wird jedoch nur 1 einziges DbValue[]-Array benötigt, da GetValue den eingebetten Spaltenwert als Single-Objekt zurückgibt.
Diese Objekte sind kein Problem, da man mit diesen ja i.d.R. weiterarbeitet, wenn sie z.B. in ein DataTable einfließen.
Bei jedem Read() wird das DbValue[] Array überschrieben und diese Objekte in den Garbage geschickt. Ca. nach 2 Mio. Zeilen schlägt der GC dann erbarmungslos zu, gibt Objekte frei und reorganisiert den Speicher und bremst das Laden dann aus.
Aber wer braucht denn wirklich mehr als 2 Mio. Zeilen für eine Abfrage?
Wir leider, für unsere Dashboards.

Ich habe mich daran selbst versucht, bin jedoch kläglich gescheitert.

Ich konnte zwar das DbValue[]-Array für die Queue als Object[]-Array für die Werte ersetzen, allerdings nicht die Queue. Dadurch ergab sich ein leichter Vorteil von 10-30% je nach Anzahl Spalten.
Rufe ich die Daten ohne Queue mit einem Read() ab, geht der Geschwindigkeitsvorteil wieder vollkommen verloren. Zusätzlich tritt ein Transaktionsproblem auf, da u.U. nicht alle Daten aus dem internen Puffer ausgelesen werden, z.B. beim internen GetSchemaTable des FbDataReaders, der dieselben Funktionen verwendet.

Hinzu kommt, dass die Vorschläge wahrscheinlich gar nicht aufgenommen werden, da sie mit den Async-Methoden von Net-Core vollkommen hinfällig sind.
Auch kann ich nichts dazu sagen, wie sich das mit einer Client-App / Db-Server-Konstellation verhält, da ja dann noch das Netzwerk dazu kommt und da weitere Bremsen entstehen. Wir haben z.B. getrennte Server für Web (IIS) und Datenbank (FbServer) gefordert um keine Ressourcen-Konkurrenz (Speicher/CPU's) zwischen beiden zu bekommen.
Das Ganze ist also nur sinnvoll bei einer Single-Server-Lösung, also App und Db-Server auf der selben Kiste.

Anscheinend wird ab Net-Core alles Asynchron (Async/Await) über Task's geregelt.
Da sinkt der Durchsatz gegenüber .Net-Framework 4.8 im Schnitt sowieso um über 50% da das Erstellen von Threads (hier Task's) einen gigantischen Overhead mit sich bringt, dass die Leserate von ca. 320.000 Zeilen/Sekunde dann auf 120.000 Zeilen / Sekunde sinkt. Da ist der Gabagecollector dann glatt noch unterfordert.

Es ist also nicht alles Neue dann tatsächlich auch gut, wenn sich Antwortzeiten für Dashboards dann verdreifachen können. Keine rosigen Aussichten.

Da ist es dann auch kein Wunder, dass man mit Power BI in die Cloud muss, da man dieses o.a. Manko nur mit entsprechend mehr Ressourcen ausbügeln kann, was OnPremises, wie unsere Lösung, kaum zu leisten ist.

Ob da wohl eine bestimmte Absicht hinter steht :lol: ?

Was deinen PHP-Treiber angeht, so können wir uns das beide gerne mal zusammen ansehen. Ich glaube aber nicht, dass du den Net-Client intern anwendest.
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

So, abschließend sei gesagt, dass der NetProvider kaum optimierbar scheint, da die Compiler-Optimierungen gegen die zusätzlich erforderlichen Calls stehen.
M.a.W. trotzdem, dass der GC nach meinen Änderungen kaum noch zuschlägt, fressen die dann nicht optimierbaren Calls den Vorteil gleich wieder auf.

Wichtig ist nur, den Parameter FetchSize des FbCommand korrekt zu setzen, um Servertrips zu sparen. Das Maximum ist hier 32768 Zeilen, der Default liegt bei 200 und kann auch in der Verbindungseinstellung pauschal angegeben werden, z.B. "FetchSize=10000;".
das kann dann schon sehr viel mehr bringen als die üblichen 200.
Eine große FetchSize stört dann auch nicht, wenn das Resultset kürzer ist. Es wird dann halt nur in einem Rutsch übertragen.
Bei vielen parallelen Verbindungen (viele Clients, Web-Sessions) kann es dann schon mal besser sein. Andererseits kann der Durchsatz insgesamt aber wieder sinken, wenn der FbServer oder das Netz dem wieder entgegensteht.
Dies betrifft allerdings dann nur gleichzeitige Queries.

Es bleibt halt immer auszuprobieren, was denn da besser ist, da es von App zu App sehr unterschiedlich sein kann.
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Für meine Änderungsvorschläge bzgl. des NetProviders (Readperformance mit bis zu 30% Steigerung) müssen sog. PullRequests durchgeführt werden.
Da ich davon keine Ahnung habe, stelle ich gerne die modifizierten Quellen des NetProviders zur Verfügung und erkläre/dokumentiere die Änderungen.
Einfach nur die Quellen hochladen wird da nicht akzeptiert.
Kann das jemand machen?

Hat sich inzwischen erledigt.
Ich habe es tatsächlich geschafft.
Benutzeravatar
martin.koeditz
Beiträge: 443
Registriert: Sa 31. Mär 2018, 14:35

Danke für deine Mühen. Ich schaue mir das bei Gelegenheit an.
Fall noch jemand Interesse an den Änderungen hat, hier ist der Link:
https://github.com/FirebirdSQL/NETProvider/pulls

Gruß
Martin
Martin Köditz
it & synergy GmbH
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Mit der Bereitstellung der PullRequests ist mir ein Fehler unterlaufen.
Mit Git bin ich nicht so firm, so dass zusammenhängende Änderungen in getrennten Request stehen und somit nicht zusammen getestet werden können, was dann natürlich auf die Nase fällt.
Ein paar Antworten bzgl. des FbDataReader habe ich bereits erhalten und die muss ich noch korrigieren. Ich bin schließlich auch nicht perfekt.
Die Korrekturen betreffen Teile, die ich für meine Apps nicht brauche.
Sobald die Requests wieder da sind, trage ich den Link hier wieder ein.

So, irgendwie begreife ich Git nicht.
Ich habe einen neuen Pullrequest hinbekommen, allerdings bekomme ich für den FbDataReader nun keinen separaten Pullrequest.
Vielleicht kennt sich ja damit jemand aus:

https://github.com/FirebirdSQL/NETProvider/pull/1119

Meinen Fork habe ich nicht geschützt, ihr könnt euch ja darüber mal den eigenen NetProvider mit meinen Änderungen erstellen und ausprobieren:
https://github.com/BFuerchau/NETProvider

Geschwindigkeitssteigerungen zwischen 10 - 30%. Es hängt natürlich vom jeweiligen Query ab, da Aggregate/Grouped eben per se langsamer sein können oder wenn die Indexstrategie nicht passt.
Aber die häufigen einfachen Abfragen um ein DateTable o.ä. zu laden funktionieren erheblich schneller.
Wichtig in diesem Zusammenhang ist auch die FetchSize.
Diese kann man bereits in der Verbindung mit angeben oder explizit auf einem FbCommand anwenden.
Der Default ist 200, der maximale Wert ist 32767.
Ich benutze den Maximalwert, da dieser auch z.B. Schemaabfragen bereits beschleunigt und die Server-Requests reduziert. Es ist kein Performancenachteil den Wert grundsätzlich zu setzen, da man ja sowieso immer alle Daten haben will.
Und je weniger Requests, desto schneller.
Der FbServer bestimmt dann auch die tatsächliche Anzahl Zeilen per Request, es können dann auch weniger kommen.
Z.B. eine Tabelle mit 9 Feldern und 60 Mio. Zeilen auf einem LocalHost-Server haben mit dem Standardtreiber und FetchSize 32767 ca. 150.000 Zeilen/Sekunde lesen können. Mit meinen Änderungen stieg das dann auf 198.000 Zeilen/Sekunde.
Am Anfang waren sogar bis zu 450.000 Zeilen/Sekunde aber je länger man Daten abruft desto mehr hat trotz allem der GC zu tun und daher sinkt der Durschnitt.

Dies gilt nur für die synchronen Abrufe mit Read() und GetValues(). Ein ReadAsync wird aus technischen Gründen (Task-Management) nicht schneller und schafft in meinem Scenario ca. 100.000 Zeilen/Sekunde.
Und jeden Wert einzeln mit GetValue(i) abzurufen verlangsamt ein weiteres Mal.
Dies liegt darin begründet, dass a) jeder Call zählt, und b) intern der Type über ein If/Else-Konstrukt über alle Types jedes mal geprüft wird.
Und genau das macht sich erheblich bemerkbar.
Antworten