Monatsarchiv für Dezember 2008

Ja es ist mal wieder soweit. Allen Lesern dieses Blogs wünsche ich auf diesem Wege “Frohe Weihnachten”.
Wer noch nicht zu 100% mit der Familie eingespannt ist und noch etwas zu Thema Java zum lesen sucht, den möchte ich noch zwei Bücher ans Herz legen:

  • The Pragmatic Programmer ist zwar schon ein paar Jahre alt, sollte aber aus meiner Sicht für jeden Softwareentwickler Pflichtlektüre sein.
  • Effective Java, seit kurzem in der zweiten Auflage erschienen und aus meiner Sicht die Bibel wenn es um Java Entwicklung geht.

Dann bleibt mir nur noch allen ein schönes Weihnachtsfest und ein gutes und erfolgreiches 2009 zu wünschen.

In letzter Zeit bekomme ich immer wieder mal die Frage, warum denn eine Webanwendung so langsam ist, bzw. was man denn tun könnte, um sie schneller zu machen. Nach meiner Erfahrung sind Performance Probleme bei Webanwendungen zu über 50% Datenbankprobleme. Besonders unerfahrene Entwickler haben im Rahmen ihrer Ausbildung zwar häufig den SQL Syntax und Regeln zur normalisierung gelernt, aber selten wird auf die Auswirkungen eines bestimmten Statements auf die Ausführungsgeschwindigkeit eingegeben.
Der folgende Artikel soll daher mal in grob vereinfachter und abstrakter Weise die Vorgänge und die unterschiedlichen Auswirkungen in einer relationalen Datenbank erklären. Der Artikel soll dabei weder alles wissenschaftlich exakt sein, noch geht er auf Datenbank spezifische Details ein, sondern soll als Grundlage dienen um einen ersten Eindruck einer Datenbank zu bekommen.

Zugrifssmöglichkeiten

Versucht man mit Hilfe eines einfachen SQL wie zum Beispiel:

select customerno, firstname, lastname from customer where lastname='Ma%'

auf eine Datenbank zuzugreifen, hat die Datenbank grundsätzlich drei verschiedene Möglichkeiten:

  1. Die komplette Tabelle wird nach Zeilen durchsucht, für die die angegebene Bedingung zutrifft. (FULL TABLE SCAN)
  2. Ein Index kann verwendet werden, um die betreffenden Zeilen anzusprechen. (INDEX ACCESS)
  3. Ein Zugriff über einen Eindeutigen Schlüssel ermöglichkeit den Zugriff auf die Daten. (UNIQUE INDEX ACCESS)

Im folgenden gehe ich davon aus, dass der Zugriff auf alle Zeilen in der Datenbank gleich schnell erfolgt, und alle Vergleichsoperationen gleich viel Rechnezeit benötigen. Damit hängt die Verarbeitungsgeschwindigkeit nur noch von der Anzahl der zu prüfenden Zeilen ab. Wenn ein Statement schneller sein soll, muss es also gelingen die Anzahl der Vergleiche zu reduzieren:

  1. Beim Abgleich aller Zeilen muss die Datenbank grundsätzlich jede Zeile der Tabelle prüfen. Der Aufwand eines  Zugriffs ohne Index steigt also proportional mit der Anzahl der in der Tabelle.
  2. Nicht eindeutige Index Daten werden intern als Baum Struktur verwaltet. Dieser Baum ist sortiert so dass die Datenbank in der Wurzel mit der Suche beginnt und in jedem Knoten nur den Ast weiterverfolgen muss, der Zeilen mit der zutreffenden Bedingung beinhaltet.  Der Aufwand einen solchen Baum zu durchlaufen ist proportional zur Tiefe des Baumes und diese Ergebit sich aus dem Logarithmus der Anzahl der Zeilen der Tabelle. Der Aufwand beim Zugriff über einen nicht eindeutigen Index steigt also logarithmisch mit der der Anzahl der Zeilen.
  3. Für eindeutige Index Daten berechnet die Datenbank eine Hash Funktion. Mit Hilfe dieser Funktion kann Sie aus dem übergebenen Parameter genau die Adresse im Speicher berechnen an der sich der gesuchte Datensatz befindet.  Dabei ist es unabhängig ob es sich im eine sehr große oder sehr kleine Tabelle handelt.  Der Aufwand beim Zugriff über einen eindeutigen Index ist unabhängig von der Anzahl der Zeilen in der Tabelle.

Was bedeutet das nun in der Praxis? Wenn meine Kundentabelle 20 Millionen Einträge hat und das Vergleichen einer Tabellenzeile 0,1 Millisekunden dauert,  ergeben sich folgende Aufwände:

  • Zum Auslesen aller Tabellenzeilen muss ich zwanzig Million Zeilen lesen und vergleichen. Das dauert zwei Million Millisekunden, oder 2000 Sekunden oder ca. 30 Minuten.
  • Wenn ich einen nicht eindeutigen Index benutzen kann muss ln(20.000.000)= 17 Vergleiche durchführen, das dauert ca. 1,7 Millisekunden
  • Wenn ich einen eindeutigen Index habe, muss ich nur einen Vergleich durchführen, das dauert ca. 0,1 Millisekunden

Das ist natürlich eine extrem vereinfachte Bedtrachtung die nur zur Kennzeichnung der Dimensionen dienen soll. Ich habe hier weder Zeiten für die Wahl des korrekten Index, für die Auswertung der Index Inhalte oder den Hash Algorithmus berücksichtigt. Ausserdem legt eine Datebank oft auch schon interne temporäre Index oder Tabellendaten an, die in der Praxis definitiv zu anderen Zeiten führen werden.

Man kann also mit Hilfe eines Index bei großen Datenmengen Zugriffszeiten um gewaltige Dimensionen beschleunigen. Die Kunst ist es also Index Daten und SQL Statements so aufeinander abzustimmen, das beide optimal zusammenspielen. Tipps man SQL Statements in dieser Richtung optimiert, gibt es im zweiten Teil.

Beim Aufbau eines Volltextindex für eine Java Webanwendung bin ich diese Tage auf Solr gestoßen. Aufbauen auf der bekannten Apache Lucene Engine stellt Solr einen Suchserver bereit, mit dem man mit Hilfe einer REST API kommunizieren kann.
Dabei erweitert Solr Lucene um viele interessante Dinge und bringt auch noch eine grafische (web basierte) Administrationsoberfläche mit. Für alle Anwendungen die effizient über große Datenmengen suchen müssen sollte man sich das Apache Projekt auf jeden Fall einmal ansehen.

Nachdem ich vor einiger Zeit schon mal hier über Hudson berichtet hatte, wird es nun Zeit für einen kleinen Zwischenbericht.
In der Zwischenzeit wird die Mehrheit meiner Projekte auf einem Hudson Server zusammengebaut. Mein Buildwerkzeug der Wahl ist dabei maven2. Zusätzlich zum “normalen” build und dem ausführen der Unit Tests haben sich dabei folgende Plugins bewährt:

  • JavaDoc
  • Open Tasks
  • Checkstyle
  • Findbugs
  • Compiler Warnings

Screenshot vom Build Prozess

Screenshot vom Build Prozess


Erst seit dem diese Dinge für jeden sichtbar sind und mit jedem build Lauf überprüft werden, haben Sie einen Sinn. Vorher hatte ich immer wieder das Problem das Coding Conventions nach einer gewissen Zeit in Vergessenheit gerieten oder dass Compiler Warnungen einfach abgeschaltet werden. Erst seit dem es ein unbestelliches System gibt und diese Dinge für jeden transparent sind, werden diese Dinge beachtet und die Qualität hat sich deutlich erhöht.

Schon seit Jahren verwende ich die jakarta commons collections Klassen in meinem Code. Die Bibliotheken der Apache Foundation haben sich eigentlich immer als hochqualitativ und stabil erwiesen. Seit meinem Upgrade auf Java 5 habe ich aber das Problem, dass diese Bibliotheken nicht generisch sind. Deswegen habe ich einmal einen Blick auf die Google Collections geworfen. Diese sind, wenn man typsicher (und ohne bzw. mit Compiler Warnungen) arbeiten will doch eine sehr gute Alterantive zu dem guten alten Apache Code. Zwar ist der aktuelle Stand offiziell noch Alpha, aber wenn man dem WIKI glauben darf, scheint dieser Code massiv bei Google in Produktion zu sein, damit hat er seine Praxis tauglichkeit wohl mehr als bewiesen.

Ich habe vor kurzem dieses Block auf WordPress 2.7 aktualisiert. Damit gibt es jetzt eine “auto update” Funktion. Sprich ich kann per Knopfdruck automatisch die aktuellste Version installieren. Damit erspare ich mir jedesmla die Dateien herunter zu laden und mühsam zu aktualisieren. Die PHP Welt scheint hier doch um einiges agiler als die Java Welt zu sein. Bisher ist mir keine Java Webanwendung bekannt die ein ähnliches Feature eingebaut hätte, aber was nicht ist kann ja noch werden.