Sonntag, Dezember 12, 2010

Query Performance–Open Source, Reflection und “Java”-Tools

Wie  üblich bei den meisten Applikationsentwicklungen ging es bei uns in den letzten Wochen u. a. um das Thema Performance. Die aktuelle Anwendung nutzt verschiedene Open Source Komponenten zum Verarbeiten der Daten. Die Komponenten sind convertierte Java-Entwicklungen, teilweise mit langem Hintergrund.

Zum einen muss ich sagen, dass man wirklich extrem Differenzieren muss zwischen den einzelnen Projekten. Teilweise sollte man vor dem Einsatz noch ein Code-Review und Analyse einschieben. Eine Open Source Komponente nutze Exceptions exzessiv zur Flusssteuerung, was schlechtes Design ist und u. U. Performance kostet. Leider nutzen wir auch eine Datenzugriffskomponente, die eigentlich fast keine Arbeit abnimmt, da damit nur Objektzuweisung durchgeführt werden. Ein OR-Mapper, der Arbeit abnimmt, würde eine wesentlich bessere und einfachere Entkopplung ermöglichen.

Die Datenzugriffskomponente, ich weigere mich OR-Mapper zu sagen, arbeitet intern heftig mit Reflection um die Properties zu setzen. Leider ist gerade bei Massenoperationen Reflection keine gute Wahl. Reflection ist ein zentraler Bestandteil im .NET Framework, dennoch kostet jede Operation dennoch einige Millisekunden, die wenigen Millisekunden potenzieren sich dann bei mehreren Tausend Objekten zu einigen Sekunden, im schlimmsten Fall zu Minuten. Allgemein sollte vor dem Einsatz eines OR-Mapper immer geprüft werden, ob es sich um Massenoperationen handelt, diese sollten ggf. mit klassischen ADO-Mitteln abgehandelt werden. Um unsere Roundtrips zur DB zu reduzieren, haben wir die verschiedenen Statements zu einem "großen" Statement zusammengefügt. Leider wurde dadurch das resultierende Objekt größer und die Property Zuweisungen haben sich auch vervielfacht. Aufgrund der Eltern-Kind-Beziehung in den Statements benötigten die Abfragen anschließend 40s (5s beim Ausführen in der DB). Grundsätzlich kann durch Joins die Abfragegeschwindigkeit gesteigert werden, OR-Mapper wie Lind2SQL oder NHibernate machen diese Optimierungen durchaus auch. Wir haben unsere Zugriffe durch Aufsplitten unserer Joins um das 10-fache beschleunigt. Die Joins wurden eingeführt, um einige DB-Roundtrips zu sparen, leider wurden unserer Datensätze dadurch aber auch ver-100-facht. 100-fache Datenmenge und viel Reflection haben sich so massiv ausgewirkt, dass wir bis zu 40s für einfache Selects benötigt haben. Somit wurden also 2 Statements abgesetzt, 1 die Elterndaten und die 2. Abfrage mit den Kinderdaten, in beiden Statements wurde die gleiche Bedingung benutzt. Wir haben die Zuordnung der Kinder zu Ihren Elternelementen im Code realisiert, da dies sehr einfach war und wir nicht ggf. 100te Roundtrips produzieren. Würde man zu jedem Customer (Beispiel) die entsprechenden Orders über den PK laden, wären dass ggf. sehr viele Operationen. Um das ganze weiter zu optimieren, kann man bei einigen Datenbankprovidern mehrere SQL-Operationen in einem Rutsch absenden, so dass die Weitere Zeit kaum ins Gewicht fällt.

Hier ein sehr abstraktes und einfaches Beispiel:

1 select * from Customers c 2 INNER JOIN Orders o ON c.ID=o.customerID 3 WHERE o.orderdate>@DATE; 4 /* Splittet into 2 Statements */ 5 select c.* from Customers c 6 INNER JOIN Orders o ON c.ID=o.customerID 7 WHERE o.orderdate>@DATE; 8 select o.* from Customers c 9 INNER JOIN Orders o ON c.ID=o.customerID 10 WHERE o.orderdate>@DATE;

Dies soll kein Post gegen Open Source oder konvertierte Java-Componente sein. Jedoch finde ich es falsch, einfach anzunehmen, dass gut funktionierende Java-Componenten, auch gut in .NET funktionieren. Es sollte immer der Anwendungsfall im Vordergrund stehen. Componenten/Frameworks sind nur Unterstützer zu diesem Weg. Nebenbei bin ich sehr  gespannt, wie es mit JAVA weiter geht http://goo.gl/rUAHG.

Vielleicht bekomme ich es doch noch mal hin, dass ich diese Jahr noch einen weiteren Post erstelle. Ich wollte seit Wochen mal was zu NDepend schreiben, aber …

Keine Kommentare: