
| Cache Ein Cache erhöht die Leistung einer Anwendung, indem wiederholte Zugriffe auf Datenstrukturen durch Zwischenspeicherung im Hauptspeicher beschleunigt werden. |
| Timestamp Ein Zeitstempel, der den Zeitpunkt der letzten Bearbeitung von Datensätzen speichert. |
Weiterführende Links
| ||||||||||||||||||||||||||||||||||||||
In der ORDIX News 3/2006 haben wir bereits über die Grundlagen des Caching mit Hibernate [1] berichtet. Es ist sinnvoll, sich diese Grundlagen zu verinnerlichen, bevor Sie sich mit diesem Artikel auseinandersetzen.
Für unsere Performance-Tests wurde ein Klassenmodell entworfen, das eine Vorlesung mit Studenten und Dozenten in Kombination mit einer Bibliothek modelliert (siehe Abbildung 1). Das objektrelationale Mapping wurde mit Annotations realisiert. In diesem Artikel beschränken wir uns jedoch auf die Klasse Student.
Weiterhin wurden verschiedene Datenbank-Schemata mit einer unterschiedlichen Anzahl an Datensätzen angelegt, um bei den Tests flexibel zu sein.
Zur Messung der Ergebnisse wurde mit Hilfe von Java eine Stoppuhr entwickelt, die neben dem Starten und Stoppen der Zeit in Millisekunden auch den Durchschnittswert ermittelt. Messungen starten und enden mit einer Transaktion. Dazwischen finden die Testabläufe statt. Der Aufbau der Verbindung zur Datenbank fließt dabei nicht in die Testergebnisse mit ein.
Die Performance-Tests wurden auf einem Intel® Core™ 2 CPU 4300 @ 1.80 GHz mit 1 GB RAM durchgeführt. Als Betriebssystem wurde Microsoft Windows XP Professional (Version 2002) mit Service Pack 2 verwendet. Eclipse 3.2 mit dem JDK 1.6 Update 4 diente als Entwicklungsumgebung.
Mit drei verschiedenen Testszenarien (siehe Abbildung 2) untersuchen wir im Folgenden anhand der eben vorgestellten Umgebung die Auswirkungen des First- und Second Level Cache und des Query Cache.
Der First Level Cache ist standardmäßig aktiviert und kann nicht ausgeschaltet werden. Er ist mehrfach vorhanden und wird für die Dauer der Session von der aktuellen Hibernate Session selbst implementiert. Im ersten Szenario (siehe Abbildung 2) geht es um die Analyse der Geschwindigkeit des First Level Cache und die Vorteile, die sich aus der Nutzung ergeben.
Die Statistik zeigt, dass die erste Abfrage (siehe Abbildung 3) mit 1453 ms fast doppelt so viel Zeit benötigt wie die zweite Abfrage (734 ms). Die weiteren Messergebnisse schwanken zunächst noch geringfügig und pendeln sich dann im Bereich zwischen 600 ms und 670 ms ein.
Die Messergebnisse lassen sich folgendermaßen erklären: Zunächst öffnet die Session-Factory eine Session mit der Datenbank. Das hat die Aktivierung des First Level Cache zur Folge.
Danach prüft die erste Abfrage, ob die Objekte im Cache vorhanden sind. Das ist nicht der Fall, weil der Cache leer ist. Anschließend findet ein Zugriff auf die Datenbank statt. Dabei übermittelt Hibernate das generierte Select-Statement an die Datenbank, speichert die zurückgelieferten Daten in Objekten und lädt diese in den dazugehörigen Cache-Bereich.
Die zweite Abfrage prüft erneut, ob die Objekte im Cache vorhanden sind. Zu diesem Zeitpunkt kann Hibernate die Daten direkt aus dem First Level Cache beziehen, weil die erste Abfrage die benötigten Daten bereits im Cache abgelegt hat. Die weiteren Abfragen verfolgen die gleiche Strategie.
Der Einsatz des First Level Cache führt demzufolge zu Performance-Steigerungen, da durch den Zugriff auf das schnellere Speichermedium deutlich schnellere Antwortzeiten erzielt werden können. Zudem wird die Datenbanklast reduziert, da Hibernate nicht mehr bei jedem Laden eines Objekts die Informationen aus der Datenbank beziehen muss.
Im Gegensatz zum First Level Cache ist der Second Level Cache optional zuschaltbar und nur einmal pro Applikation vorhanden. Des Weiteren ist er langlebiger, weil er für die Lebensdauer einer SessionFactory existiert. Das zweite Szenario (siehe Abbildung 2) untersucht die Performance-Steigerung durch den Einsatz des Second Level Cache, die erst bei mehreren Threads zum Tragen kommt.
Abbildung 4 zeigt die Messergebnisse der beiden durchgeführten Threads. Es ist deutlich zu erkennen, dass beim ersten Thread die erste Abfrage mehr Zeit benötigt, als die darauf folgenden. Die Messungen pendeln sich anschließend zwischen 600 ms und 800 ms ein. Der zweite Thread bewegt sich ebenfalls in diesem Bereich.
Der erste Thread baut mit Hilfe der Session-Factory eine Session mit der Datenbank auf, die eine Aktivierung des First- und des Second Level Cache zur Folge hat. Es findet die erste Abfrage statt. Die Daten werden in Objekte gespeichert und anschließend in den First- und den Second Level Cache geladen. Die darauf folgenden Abfragen des ersten Threads laden die Objekte aus dem First Level Cache. Dadurch benötigt die erste Abfrage mehr Zeit als die zweite.
Nachdem der erste Thread beendet ist, startet der zweite Thread. Dieser baut ebenfalls mit Hilfe der SessionFactory eine neue Session auf und aktiviert dadurch die beiden Caches. Der First Level Cache ist bei der ersten Abfrage leer, da dieser nur für die Lebensdauer einer Session existiert. Der Second Level Cache existiert hingegen für die Lebensdauer einer SessionFactory und wurde bereits zuvor durch den ersten Thread mit Objekten befüllt.
Startet der zweite Thread die erste Abfrage, wird zunächst erfolglos der First Level Cache durchsucht. Anschließend prüft Hibernate den Second Level Cache. Dieser beinhaltet die gesuchten Objekte, weil es sich bei den beiden Threads um die gleiche Abfrage handelt. Aus diesem Grund benötigt die erste Abfrage des zweiten Threads deutlich weniger Zeit als die erste Abfrage des ersten Threads.
Der Second Level Cache wird insbesondere auf Ebene einer Virtual Machine oder eines Clusters eingesetzt. Bei der ersten Möglichkeit werden alle Objekte, die in einer Virtual Machine gespeichert oder geladen werden gecacht. Wenn z. B. mehrere Benutzer den gleichen Buchtitel abfragen, muss der Buchtitel nicht für jeden Benutzer neu geladen werden, sondern kann direkt aus dem Second Level Cache bezogen werden. Bei einem Cluster werden ebenfalls alle Objekte geladen, auch wenn die Applikation über mehrere Rechner verteilt ist.
Es gibt aber auch Situationen, in denen der Einsatz des Second Level Cache nicht empfehlenswert ist, z. B. bei einer großen Einfüge-operation. In diesem Fall ist es ratsam, den Second Level Cache zu deaktivieren, weil sonst unnötiger Overhead durch Speichern der Objekte in den Second Level Cache entsteht.
Der Query Cache ist ebenfalls optional zuschaltbar und ist bei Abfragen sinnvoll, die sich oft wiederholen. Neben SQL-Anweisungen (einschließlich aller eingebundenen Parameter) enthält der Query Cache auch die Identifikatoren der Ergebnismenge. Mit dem Query Cache ist auch immer ein Zeitstempel-Cache aktiv, der prüft, ob die Ergebnismenge der Abfrage noch aktuell ist. Es stellt sich nun die Frage, wie viel Performance-Gewinn der Query Cache bei gleichen Abfragen bringt. Um diese Frage zu beantworten, testen wir nun das dritte Szenario.
Abbildung 5 zeigt, dass durch die Aktivierung des Query Cache die Abfragen wesentlich schneller erfolgen. Im Durchschnitt benötigen die Abfragen ohne Query Cache 688 ms. Aktiviert man den Query Cache, ist eine deutliche Leistungssteigerung zu erkennen. Unter Verwendung des Query Cache benötigen die Abfragen durchschnittlich 545 ms. Das entspricht einem Performance-Vorteil von circa 20 Prozent.
Dieser Effekt hat folgenden Hintergrund:
Nach der ersten Abfrage werden alle drei Caches gefüllt. Der First- und der Second Level Cache speichern die Objekte, während der Query Cache das SQL-Statement und die Identifikatoren der Ergebnismenge speichert. Weiterhin wird ein Zeitstempel gesetzt.
Die zweite Abfrage prüft, ob der Query Cache das SQL-Statement beinhaltet. Unter Verwendung der gleichen Abfrage ist die Suche erfolgreich. Anschließend findet eine Prüfung des Zeitstempels statt. Da keine Änderungen in der Tabelle durchgeführt wurden, ist der Zeitstempel aktuell und die Ergebnismenge wird nicht verworfen.
Im nächsten Schritt sendet Hibernate die Identifikatoren der Ergebnismenge an den Second Level Cache und durchsucht diesen. Der Second Level Cache liefert anschließend die Objekte zurück und die Session speichert diese zum Beispiel in einer Liste.
Ein Datenbankzugriff wird dadurch vollständig vermieden, was - ähnlich wie beim First- und Second Level Cache - zu Performance- Steigerungen und einer Datenbankentlastung führt. Der Query Cache bietet sich nicht an, wenn die Tabelle oft modifiziert wird, weil dann der Zeitstempel häufig nicht mehr aktuell ist und die Ergebnismenge verworfen und neu geladen werden muss.
Das Cache-System bietet effiziente Möglichkeiten, um Performance-Optimierungen zu erzielen. Bei falschem Einsatz kann das Caching aber auch das Gegenteil bewirken. Als Beispiel sei hier das Modifizieren von Tabellen unter Verwendung des Query Cache genannt. Im nächsten Teil der Reihe werden die verschiedenen Caching-Strategien des EHCache näher unter die Lupe genommen und bewertet.
ORDIX verfügt in diesem Bereich über Erfahrung aus vielen Projekten. Gerne geben wir diese an Sie weiter [2]. Besuchen Sie unseren Trainingsshop oder sprechen Sie uns an!
Alexander Keil (info@ordix.de).