Vaadin unter Last – Teil 3: Ergebnisse der Lasttests (Speicherverhalten)

Wir hatten im ersten Teil einen Lasttest für eine Vaadin-Anwendung aufgebaut. Im zweiten Teil hatten wir das Verhalten dieser Anwendung in Bezug auf Antwortzeit analysiert. In diesem dritten Teil analysieren wir nun Nutzung und Auslastung des Arbeitsspeichers. Auch hier werden wir die Anteile der verschiedenen Komponenten der Anwendung und die Rolle von Vaadin diskutieren.

Zur Durchführung des Tests verwenden wir (identisch zu Teil 2) zwei Server auf der Amazon EC²:

  • Ein Server dient als Application Server für den Officewerker – dieser Server wird unter Last gesetzt.
  • Ein Server dient zur Ausführung des Testprogramms unter JMeter – dieser Server erzeugt die Last.

Die Server sind wie folgt ausgestattet:

  RAM Cores
Server unter Last 15 GB 8 Cores
Test-Treiber-Server 7,5 GB 4 Cores

Da sich beide Server im Amazon EC²-Netz befinden, ist von einem sehr performanten Netzwerk mit vernachlässigbarem Einfluss auszugehen.

Das JMeter-Testprogramm simuliert die vom Browser gestellten Netzwerk-Anfragen (vergleiche die beiden vorangegangenen Artikel). Ziel des Tests ist also die Server-Seite von Vaadin. Auch wird die gleiche Reihe von Aktionen durchgeführt:

  • Aufruf der Startseite
  • Laden der Ressourcen
  • Einloggen
  • Liste der Eingangsrechnungen
  • Suche nach der Rechnungsnummer

Um möglichst nur das Speicherverhalten zu testen, haben wir eine zufällig gewürfelte Wartezeit („Uniform Random Timer“) zu Beginn jedes Threads (simulierten Benutzers) eingefügt. Damit starten diese versetzt – und alle Anfragen kommen auch sauber durch.

Daneben haben wir auch die Größe aller Thread-Pools im dergestalt dimensioniert, dass sie mindestens so viele Threads bereitstellen wie wir parallele simulierte Nutzer in JMeter haben. In unserem Fall werden im Minimum 200 Threads bereitgestellt. Da wir Speicherverhalten, nicht Antwortzeit messen wollen werden pro Durchlauf nur 10 Rechnungen gesucht – nicht wie in Teil 2 bis zu 500. Initial wird weiterhin die Liste aller Rechnungen angezeigt.

Für 50, 100 respektive 200 parallele simulierte Nutzer ergibt sich dann folgendes Bild:

Threads Durchläufe Rechnungen Threads max. Mem. max. Sockets an DB
50 20 10 222 3,0GB 14
100 20 10 222 3,0GB 12
200 20 10 227 3,7GB 14

Die Ergebnisse sind dabei zunächst nicht intuitiv – lassen allerdings folgende Schlüsse als Startpunkt weiterer Untersuchung zu:

  • Die Anzahl der Threads steigt – analog der Anzahl der Sockets an die Datenbank nur langsam an; es gibt also wohl kein (verfälschendes) Bottleneck bei der Antwortzeit.
  • Es ist noch nicht klar, ob Sessions abgeräumt werden (der Officewerker implementiert dies durch Custom-Code). Damit ist noch keine Aussage darüber getroffen, ob obige Tabelle das Bild von 200 oder 4000 Sessions zeichnet. Dies gilt es (siehe „Entwicklung des Speicherverbauchs“) zu untersuchen.
  • Bei 50 parallelen simulierten Nutzern ist der Speicherverbrauch hoch – zumindest im Vergleich zu 200 parallelen simulierten Nutzern. Eine Vermutung wäre hier, dass Entities gecached werden – schließlich greifen alle Nutzer auf den gleichen Rechnungsbestand zu. Wir werden diese Hypothese unten („Breakdown des Speicherverbrauchs“) noch genauer untersuchen.

Entwicklung des Speicherverbrauchs

Die folgende Grafik zeigt die Entwicklung der Speicherauslastung für 200 parallele simulierte Nutzer über die Zeit (ist also eine Detailsicht auf die letzte Zeile obiger Tabelle):

SpeicherverbrauchVaadinLasttest

Aus dieser Grafik erhält man nun schon ein detaillierteres Bild: nach einer Aufbauphase (mit 1 GB Speicherauslastung) ist der Speicherverbrauch konstant – was sich letztlich nur dadurch erklären lässt, dass der Officewerker (gemäß Intention im Quelltext) die „alte“ Session bei der zweiten Anmeldung des gleichen Nutzers invalidiert und für die Garbage Collection freigibt. Die Sägezahn-förmige Struktur des Used Heap spricht ebenfalls dafür.

Im Umkehrschluss bedeutet dies allerdings, dass die Tabelle oben 3,7 Gigabyte Speicherauslastung ausweist – und zwar für 200 Sessions. Zieht man den initialen Speicherverbrauch ohne Sessions ab, so ergibt sich 2,7GB / 200 Nutzer = 13,5 MB Session-Größe, was getrost als viel bezeichnet werden kann.

Breakdown des Speicherverlaufs

Um den Grund dafür zu identifizieren haben wir (in einer lokalen Installation) den Speicherverbrauch im Detail analysiert. Die folgende Abbildung stellt die Klassen mit dem größten Speicherverbrauch dar:

Speicherverlauf1VaadinLasttest

Der Vollständigkeit halber zeigt die folgende Grafik (in der gleichen Liste nach unten gescrolled) noch den Verbrauch der Speicher-hungrigsten Vaadin-Klassen:

Speicherverlauf2VaadinLasttest

Aus dieser Darstellung lässt sich nun tatsächlich ein differenziertes Bild ableiten – und es lässt sich die Ursache für den hohen Speicherverbrauch identifizieren:

  • Die ersten beiden Klassen (in der Persistenzschicht verankert) verbrauchen zusammen mehr als ein Drittel des Speichers. Daneben wird die Bean „IngoingInvoice“ (also die persistente Entity für die Rechnung) fast 900.000 Mal instanziiert (der Testdatensatz umfasst – vgl. Teil 1 – 10.000 Datensätze). Das ist ein starker Indikator für das Fehlen des Paging auf der Datenbank
  • Im Gegensatz dazu sind Vaadin-Klassen maximal 2.800 Mal instanziiert, also Zehnerpotenzen darunter. Als Kandidaten für Optimierungen scheiden sie damit aus.

Es gilt also, den Quelltext der Schichten unterhalb von Vaadin in Sachen Paging auf der Datenbank zu optimieren. Dies ist die Hausaufgabe, die den Speicherverbrauch des Officewerker signifikant senken wird.

Fazit

Wie schon im zweiten Teil ist die Performance einer Vaadin-Anwendung bestimmt durch die Schichten unterhalb der UI. Das gilt wie oben gezeigt genauso für den Speicherverbrauch. 2.800 maximale Instanzen sind nicht viel – und auch der Speicherverbrauch von ca. 300kB für dieselben hält sich im Rahmen, erst recht im JEE-Umfeld. Durch die sehr geradlinige Programmierung in Java lassen sich entsprechende Probleme auch sehr schnell aufdecken – und entsprechend (hier: durch Paging auf der Datenbank) korrigieren.

Wenn Sie Fragen haben, schreiben Sie einfach einen Kommentar oder schreiben Sie uns eine E-Mail:

oliver.damm [at] akquinet.de
sebastian.rothbucher [at] akquinet.de