Freemarker Template Caching – Analyse eines Struts2 Performance Problems

2 Kommentare

Bei der Durchführung von Lasttests auf einem Projekt bemerkte ich einen spontanen Abfall der Antwortzeiten. Je mehr Benutzer auf die Anwendung zugriffen, desto langsamer wurde sie. Die Anwendung wurde mit dem Struts 2 Java Framework erstellt und griff auf die Datenbank über Hibernate zu. Eigentlich eine ganz simple Anwendung, umso verwunderlicher waren für mich die schlechten Ergebnisse. Nach dem Studium der von AppDynamics aufgezeichneten Messwerte wurde mir aber schnell klar, dass es sich um ein Problem mit Freemarker, der in Struts2 nutzbaren Template Engine, handeln musste:

Alle HotSpots gingen auf freemarker.cache.URLTemplateLoader.getURL() zurück, welches die abstrakte Definition einer in im Struts org.apache.struts2.views.freemarker.StrutsClassTemplateLoader implementierten Methode ist.

Also, was verursacht diese langen Wartezeiten? AppDynamics hat dankenswerterweise automatisch einige Threaddumps für uns gezogen, welche alle auf die blockierten Threads in sun.misc.URLClassPath.getLoader() hinweisen.


Schaut man sich den Source Code davon an, erkennt man dass dieser Code auf die URLClassPath Instanz synchonisiert ist.

private synchronized Loader getLoader(int index) {

Eigentlich hatte ich nicht erwartet, dass dies ein Engpass sein könnte, doch ich fand einige Threads, welche ebenfalls dieses Lock nutzten, aber es für Classloading verwendeten. Dieses war leider durch Speicherallokation im Betriebssystem erheblich gebremst.

Die Art wie Freemarker/Struts2 Templates laden ist also bei genauer Betrachtung gar nicht falsch. Dennoch wird offensichtlich unnötig oft auf diese Ressourcen zugegriffen.

Normalerweise verändern sich Templates nicht sehr häufig, weswegen sich Caching besonders anbieten würde. Und in der Tat gibt es die Möglichkeit in Freemarker Templates für eine Zeit zu cachen:

template_update_delay=60000

Diese Einstellung kann man in Struts über eine Datei namens freemarker.properties vornehmen. Laut der Dokumentation von Struts wird der eingestellte Wert als die Anzahl von Sekunden verwendet, welche ein Template im Cache sein muss bevor es auf Aktualität überprüft wird. Also sollte dieser Wert hoch gewählt werden.

Die Dokumentation empfiehlt ferner eine Einstellung namens struts.freemarker.templatesCache, welche auf true gesetzt werden soll. Angeblich sei es notwendig die Templates aus dem Classpath in das Filesystem zu kopieren, da Freemarker andernfalls nicht korrekt das Änderungsdatum bestimmen könne. Auf den von mir getesteten aktuellen JVMs funktionierte dies jedoch tadellos.

template_update_delay hat für das von mir getestete Projekt wunderbar funktioniert, dennoch frage ich mich warum überhaupt Templates erneut gelesen werden sollten. Auf echten Produktionssystemen würde ein einmaliges Laden völlig ausreichen. Zumal die Prüfung in freemarker.cache.TemplateCache mit nicht unerheblich viel Code verbunden ist.

Ich denke, es lohnt sich über eine eigene Implementierung der Klasse org.apache.struts2.views.freemarker.FreemarkerManager nachzudenken. Sie verbindet Struts2 mit Freemarker und versucht in der Standardimplementierung das Template zweimal aus dem Dateisystem zu lesen bevor der Classpath verwendet wird. Dieses Verhalten bietet zwar einige Flexibilität zur Entwicklungszeit, verursacht aber in Produktion unnötige Wartezeiten.

Nach der Veränderung der „Cache“-Einstellung verschwanden die HotSpots und die Anwendung lief deutlich schneller. Insbesondere der Classloader war kein Engpass mehr.

Fabian Lange ist Lead Agent Engineer bei Instana und bei der codecentric als Performance Geek bekannt. Er baut leidenschaftlich gerne schnelle Software und hilft anderen dabei, das Gleiche zu tun.
Er kennt die Java Virtual Machine bis in die letzte Ecke und beherrscht verschiedenste Tools, die JVM, den JIT oder den GC zu verstehen.
Er ist beliebter Vortragender auf zahlreichen Konferenzen und wurde unter anderem mit dem JavaOne Rockstar Award ausgezeichnet.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

  • René Gielen

    Hallo Fabian,

    sehr schöner Artikel und überaus informative Analyse. Die Tatsache, daß Du erst den Profiler anschmeißen musstest um den Flaschenhals in den Griff zu bekommen, deutet darauf hin, dass wir (ich gehöre zum Apache Struts 2 Team) entweder die notwendigen Anpassungen zum Produktivgang nicht prominent genug dokumentiert haben, oder aber dass wir über eine Vereinfachung, z.B. in Form von Profilen (Development, Staging, Production) nachdenken sollten – etwas ähnliches gibt es ja auch in JSF.
    Natürlich kann man jederzeit an eine eigene Implementierung denken, aber vielleicht möchtest Du ja darüber nachdenken, viele andere von Deinen Erkenntnissen und Anregungen profitieren zu lassen? Z.B. die Diskussion auf dev@struts.apache.org anregen, und / oder ein JIRA Ticket für ein Improvement anlegen unter https://issues.apache.org/jira/browse/WW, mit einer kurzen Dokumentation dieser Erkenntnisse…
    Ich fände es sehr gut, wenn wir dadurch Verbesserungen in den nächsten Releases erreichen würden.

    – René

  • Fabian Lange

    Hallo Rene,
    das Problem liegt eher daran, dass bei der Umsetzung des Projektes nicht die Dokumentation gelesen wurde. Ich werde dann in der Regel recht spät hinzugezogen und find leider viel zu häufig einfache Dinge.

    Ich würde eher eine Art Checkliste machen. Sowas wie das hier: http://symfony-check.org/

    Dass die Cache Einstellung wichtig ist, kann man in der Dokumentation ja schon erahnen, ist ja ein Ausrufezeichen davor 🙂

    Fabian

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.