Home ORDIX AG             Dienstleistung             Trainingsshop    Kunden / Referenzen Aktuelles    Kontakt
Home  Pfeil  ORDIX News  Pfeil  2/2009  Pfeil  Java/JEE
suche: 
Dieser Artikel richtet sich an Softwareentwickler und -architekten, die Hibernate einsetzen möchten.

Glossar

EHCache
Easy Hibernate Cache. Cache Provider für den Second Level Cache.
Log4j
Framework zum Loggen von Anwendungsmeldungen in Java. Es wird von unzähligen Open-Source- als auch kommerziellen Softwareprodukten verwendet und hat sich als De-facto-Standard etabliert.
SLF4J
Simple Logging Facade for Java. Das SLF4J führt alle klassischen Logging-Frameworks wie z. B. Log4j oder java.util.logging zusammen.


Performance-Tests mit Hibernate (Teil II))

Easy Hibernate Cache - mehr PS oder mehr Sicherheit?


Neben der Geschwindigkeit spielt beim objektrelationalen Mapping auch die Sicherheit eine große Rolle. Was bringt es, wenn eine Applikation performant läuft, aber die Datenkonsistenz nicht sichergestellt werden kann? Im zweiten Teil der Artikelreihe wird daher sowohl die Performance als auch das Transaktionsmanagement beim objektrelationalen Mapping betrachtet. Am Beispiel des EHCache Providers werden die Ergebnisse untersucht und bewertet.

Provider Provider-Klasse Caching Hibernate Query Cache Open Source
EHCache org.hibernate.cache.
EhCacheProvider
Speicher,
Festplatte
Ja Ja
OpenSymphony
OSCache
org.hibernate.cache.
OSCacheProvider
Speicher,
Festplatte
Ja Ja
SwarmCache org.hibernate.cache.
SwarmCacheProvider
Cluster
Cache
Nein Ja
JBoss Cache org.hibernate.cache.
TreeCacheProvider
Cluster
Cache
Ja Ja
Abb.1: Auflistung der gängigsten Cache Provider.
 
Provider Read-only Nonstrict-read-write Read-write Transactional
EHCache X X X
OSCache X X X
SwarmCache X X
JBoss Cache X X
Abb. 2: Unterstützte Caching-Strategien
 
Provider Provider-Klasse
EHCache org.hibernate.cache.EhCacheProvider
OpenSymphony OSCache org.hibernate.cache.OSCacheProvider
SwarmCache org.hibernate.cache.SwarmCacheProvider
Abb. 3: Log-Kategorien.
 
1  instantiating cache region: Student usage strategy: read-only
2  Thread-0 entering run() in Thread 8
3  Thread-1 entering run() in Thread 9
4  Thread-0 checking cached query results
5  Thread-0 query results were not found in cache
6  Thread-0 select "Spaltennamen" where NAME like 'Larry'
7  Thread-0 Caching: Student#1
8  Thread-0 Caching: Student#2
9  Thread-0 Caching: Student#3
10 Thread-0 caching query results; timestamp=4939808663855104
11 Thread-0 Durchlauf: 1 Zeit: 218 
12 Thread-0 checking cached query results
13 Thread-0 Checking query spaces for up-to-dateness: [STUDENT]
14 Thread-0 returning cached query results
15 Thread-0 Cache hit: Student#1 
16 Thread-0 Cache hit: Student#2 
17 Thread-0 Cache hit: Student#3
Abb. 4: Logging-Ausgabe: Read-only.
 
1 instantiating cache region: Student usage strategy: read-write
2 Thread-1 entering run() in Thread 8
3 Thread-0 entering run() in Thread 9
4 Thread-0 checking cached query results
5 Thread-0 query results were not found in cache
6 Thread-0 select "Spaltennamen" from STUDENT where NAME like 'Larry'
7 Thread-0 Caching: Student#1
8 Thread-0 Cached: Student#1 
9 Thread-0 Caching: Student#2
10 Thread-0 Cached: Student#2 
11 Thread-0 Caching: Student#3
12 Thread-0 Cached: Student#3
13 Thread-0 Caching query results; timestamp=4939807278854144
14 Thread-0 Durchlauf: 1 Zeit: 219 
15 Thread-0 checking cached query results
16 Thread-0 Checking query spaces for up-to-dateness: [STUDENT]
17 Thread-0 returning cached query results
18 Thread-0 Cache lookup: Student#1
19 Thread-0 Cache hit: Student#1 
20 Thread-0 Cache lookup: Student#2
21 Thread-0 Cache hit: Student#2 
22 Thread-0 Cache lookup: Student#3
23 Thread-0 Cache hit: Student#3
24 Thread-0 Durchlauf: 2 Zeit: 16
25 Thread-0 exiting run() in Thread 9
26 Thread-1 checking cached query results
27 Thread-1 Checking query spaces for up-to-dateness: [STUDENT]
28 Thread-1 returning cached query results
29 Thread-1 Cache lookup: Student#1
30 Thread-1 Cached item was locked: Student#1
31 Thread-1 select "Spaltennamen" from STUDENT where MATRIKELNUMMER=?
32 Thread-1 Caching: Student#1
33 Thread-1 Item was already cached: Student#1
34 Thread-1 Cache lookup: Student#2
35 Thread-1 Cached item was locked: Student#2
36 Thread-1 "Spaltennamen" from STUDENT where MATRIKELNUMMER=?
37 Thread-1 Caching: Student#2
38 Thread-1 Item was already cached: Student#2
39 Thread-1 Cache lookup: Student#3
40 Thread-1 Cached item was locked: Student#3
41 Thread-1 select "Spaltennamen" from STUDENT where MATRIKELNUMMER=?
42 Thread-1 Caching: Student#3
43 Thread-1 Item was already cached: Student#3
44 Thread-1 Durchlauf: 1 Zeit: 250 
45 Thread-1 checking cached query results
46 Thread-1 Checking query spaces for up-to-dateness:[STUDENT]
47 Thread-1 returning cached query results
48 Thread-1 Cache lookup: Student#1
49 Thread-1 Cache hit: Student#1 
50 Thread-1 Cache lookup: Student#2
51 Thread-1 Cache hit: Student#2 
52 Thread-1 Cache lookup: Student#3
53 Thread-1 Cache hit: Student#3
54 Thread-1 Durchlauf: 2 Zeit: 0
55 Thread-1 exiting run() in Thread 8
Abb. 5: Logging-Ausgabe: Read-write.
 
Abb. 6: Transaktionales Verhalten.
Abb. 6: Transaktionales Verhalten.
 

Vorwort

Im ersten Artikel der Reihe [1] wurde bereits näher auf das Cache-System von Hibernate und dessen Auswirkungen eingegangen. Es empfiehlt sich, diesen Artikel vorab zu verinnerlichen.

Grundlagen

Der Second Level Cache kann auf der Ebene eines Prozesses bzw. einer Virtual Machine oder auf der Ebene eines Clusters eingesetzt werden. Bei der ersten Möglichkeit werden alle Objekte, die in einer Virtual Machine gespeichert oder geladen werden, gecacht. Werden beispielsweise von mehreren Studenten Informationen über ein Buch abgefragt, dann muss das Buch nicht für jeden Studenten neu geladen werden, sondern kann direkt aus dem Second Level Cache bezogen werden.

Die Steuerung des Second Level Cache erfolgt über einen Cache Provider. Die gängigsten Cache Provider sind in Abbildung 1 aufgelistet.

Weiterhin kann optional für das Mapping einer Klasse oder Collection die Caching-Strategie festgelegt werden. Es wird zwischen folgenden Strategien unterschieden:

Read-only - nur lesen bitte
Bei dieser Strategie wird davon ausgegangen, dass die gelesenen Daten nie geändert werden. Es findet keine Transaktionsisolation statt. Wird ein Objekt trotzdem verändert, wird eine Exception ausgeführt.

Nonstrict-read-write
Bei dieser Strategie können die Daten geändert werden. Es gibt jedoch keine Garantie für die Konsistenz zwischen der Datenbank und dem Cache. Es könnte passieren, dass bei zeitgleichem Zugriff veraltete Daten aus dem Cache gelesen werden. Diese Strategie sollte nur bei Daten angewendet werden, die sich selten ändern und von ihrer Bedeutung nicht so wichtig sind.

Read-write
Die Daten können geändert werden. Der Unterschied zur nonstrict-read-write-Strategie besteht darin, dass die Transaktionsisolation auf der Ebene Read Commited zugesichert wird. Dabei werden nur die Daten gesehen, die bereits commited sind. Datensätze aus nicht beendeten Transaktionen werden nicht beachtet. Die Lösung kann eingesetzt werden, wenn Daten oft gelesen werden. Darüber hinaus ist die Lösung dort sinnvoll, wo es wichtig ist, zu verhindern, dass in zeitgleich ablaufenden Transaktionen veraltete Daten sichtbar werden. Diese Strategie ist nicht in einem Cluster anwendbar.

Transactional - das Rundum-Sicherheitssystem
Hierbei wird die Transaktionsisolation auf der Ebene Repeatable Read zugesichert. Das bedeutet, dass laufende Transaktionen keine Daten ändern können, die von nicht beendeten Transaktionen gelesen werden. Diese Strategie sollte bei Daten eingesetzt werden, die oft gelesen werden und deren Bedeutung sehr wichtig ist (z. B. Kontostand).

In Abbildung 2 ist eine Übersicht über die unterstützten Caching-Strategien der vorgestellten Caching-Provider dargestellt.

Szenariobeschreibung

Um die Auswirkungen der Caching-Strategien des EHCache mit aktiviertem Query Cache zu untersuchen, wurde folgendes Szenario entwickelt:

In diesem Szenario werden zwei Threads erstellt, die zeitgleich dieselbe Abfrage ausführen. Aus diesem Grund konnte die exakte Laufzeit nicht ermittelt werden.

Stattdessen wurde mit Hilfe von Log4j der Verkehr mitgeschnitten und ausgewertet. Um Systemabläufe zu loggen, benutzt Hibernate das Simple Logging Facade for Java (SLF4J) [2]. Dabei wurde viel Wert darauf gelegt, dass die Logging-Ausgaben detailliert, aber trotzdem noch gut lesbar sind. Abbildung 3 zeigt einen Ausschnitt der zur Verfügung stehenden Log-Kategorien.

Sehr empfehlenswert ist auch die Statistik API, die es ermöglicht, das Verhalten von Hibernate zur Laufzeit zu beobachten [3]. Die zur Verfügung stehenden Möglichkeiten werden in einem zukünftigen ORDIX News Artikel beleuchtet.

Beide Mechanismen sind bei dem Test zum Einsatz gekommen.

Ergebnisanalyse

Read-only
Wie in Abbildung 4 dargestellt, werden Thread 0 und Thread 1 nahezu gleichzeitig gestartet. Thread 0 überprüft beim ersten Durchlauf den Query Cache und sucht nach dem Select-Statement. Der Query Cache ist jedoch leer. Daraufhin führt Thread 0 eine Datenbankabfrage durch und selektiert alle Studenten mit dem Namen "Larry". Dabei werden der First Level Cache, der Second Level Cache und der Query Cache gefüllt. Der Query Cache beinhaltet nun das Select-Statement und die Identifikatoren der Ergebnismenge. Weiterhin wird der Zeitstempel gesetzt.

Im zweiten Durchlauf von Thread 0, wird wieder der Query Cache und dessen Timestamp geprüft (siehe Zeile 12). Es wurde keine Änderung der Tabelle vorgenommen und somit ist der Datensatz aktuell. Das Select-Statement und die dazugehörigen Identifikatoren der Ergebnismenge wurden gefunden. Im nächsten Schritt wird im Second Level Cache nach den Identifikatoren gesucht. Die Suche ist erfolgreich und es findet ein Cache Hit statt, weil bereits m ersten Durchlauf die Objekte in dem Second Level Cache gespeichert wurden. Die beiden Durchläufe von Thread 1 verfahren nach dem gleichen Prinzip wie der zweite Durchlauf von Thread 0.

Nonstrict-read-write
Die nonstrict-read-write-Strategie verfolgt den gleichen Ansatz wie die read-only-Strategie.

Read-write
Abbildung 5 stellt die Logging-Ausgabe bei der read-write-Strategie dar. Die beiden Durchläufe von Thread 0 verfolgen wieder den gleichen Ansatz wie die beiden vorherigen Strategien (siehe Zeile 1 bis 25). Thread 1 reagiert hingegen anders. Beim ersten Durchlauf von Thread 1 wird der Query Cache erfolgreich durchsucht. Dann wird der Second Level Cache nach den Identifikatoren durchsucht. Die Suche ist jedoch erfolglos, weil Thread 1 nicht auf den Second Level Cache zugreifen kann (siehe Zeile 30).

Thread 0 hat noch kein commit durchgeführt und blockiert somit den Second Level Cache (siehe Abbildung 6). Daraufhin startet Thread 1 eine Datenbankabfrage, die anhand der Matrikelnummer des Studenten alle Spalten selektiert (siehe Zeile 31). Danach versucht Thread 1, die Objekte im Cache abzulegen. Die Objekte sind bereits im Cache vorhanden und werden nicht überschrieben.

In Zeile 45 bis 55 wird der zweite Durchlauf von Thread 1 beschrieben. Die Objekte werden wieder aus dem Second Level Cache geladen, weil Thread 0 committed hat.

Transactional
Die Caching-Strategie Transactional wird von dem EHCache Provider nicht unterstützt.

Fazit

Generell lässt sich sagen, dass sich die Performance von transactional bis read-only stetig verbessert. Zwischen read-only und nonstrict-read-write konnte allerdings anhand der Logging-Ausgaben kein Performance-Vorteil ermittelt werden. Die beiden Strategien verfolgen über weite Strecken den gleichen Ansatz. Ein Unterschied besteht darin, dass bei read-only die Tabelle nicht modifiziert werden darf. Auch muss Hibernate (bei einem Update) nicht überprüfen, ob ein Cache noch gültig ist. Read-write benötigt bei der gleichen Abfrage und dem zeitgleichen Zugriff mehr Zeit, weil es passieren kann, dass der Second Level Cache blockiert wird und die Daten somit wieder aus der Datenbank gelesen werden müssen. Negativ fällt hier auch auf, dass er dies mit einem Einzelsatz-Zugriff macht, was bei einer entsprechenden Menge von Sätzen performance-kritisch werden kann.

Ehe die Entscheidung für eine Caching-Strategie fällt, sollte man sich die Frage stellen, ob die Daten transaktionssicher sein sollen oder nicht und sich anschließend für eine der eben vorgestellten Strategien entscheiden.

Für weitere Informationen besuchen Sie unser Seminar "Hibernate und die Java Persistence API" oder sprechen Sie uns an!

Alexander Keil (info@ordix.de).