Mega-Menüs oder Dropdown-Menüs ermöglichen dem Besucher den gezielten Einstieg in die Tiefen umfangreicher Websites. Die Performance, um ein solches Menü aufzubauen, ist meist eher schlecht. Der Grund liegt auf der Hand: der gesamte Seitenbaum (oder zumindest die ersten Ebenen davon) müssen ausgelesen und als Baum aufgebaut werden. Und weil ein Menü durch Hervorhebung des aktiven Pfades bzw. der aktuellen Seite für jede Seite anders ist, muss es jeweils neu generiert werden.
tl;dr:
Fluid macht Templating deutlich einfacher, kann aber auch Flaschenhälse verursachen.
TypoScript ist mächtig und kann - richtig angewandt - deutlich mehr als Fluid.
Wenn man aber die Datenaufbereitung korrekterweise aus dem Templating herausnimmt, dann lässt sich mit mehr Überlegung und Wissen sogar noch mehr Performance rauszukitzeln.
Wie performt TYPO3 bei umfangreichen Menüs?
Wir wollten es einmal wissen: Und gibt es einen Unterschied zwischen einfach pflegbaren Fluid-basierten Menüs und klassischen, auf TypoScript basierenden Menüs?
Da der Flaschenhals oftmals die Datenbank ist, lag der Fokus darauf:
wieviele SQL-Queries sind nötig, um die Navigation zu rendern?
Messung
Als Test-Projekt diente eine Hochschul-Website mit rund 800 Menü-Einträgen im Hauptmenü auf 4 Ebenen.
Die Messungen beziehen sich jeweils auf folgende Aufrufe (in dieser Reihenfolge):
- Home (nicht im Menü enthalten)
- Impressum (nicht im Menü enthalten)
- Fakultät Maschinenbau (Page 1)
- Fakultät Wirtschaftsingenieurwesen (Page 2)
- Fakultät Wirtschaftsingenieurwesen - Unterseite (Page 2.1)
Vor jeder Messung wurde der Cache komplett geleert (typo3 cache:flush).
Disclaimer:
Natürlich würde durch Seiten-Cache, durch StaticFileCache, Varnish & Co, Warming/Crawler der Seitenbesucher die Seiten kaum in diesem Zustand aufzurufen bekommen, aber bei diesem Vergleich interessierte uns, was wirklich unter der Haube abläuft.
Fluid-basiertes Menü vs. klassisches TypoScript-Menü
Zu Beginn stellen wir die beiden Grund-Ansätze gegenüber: einmal wird das Dropdownmenü mit Fluid und dem MenuProcessor gebaut, einmal auf klassischem Weg mit TypoScript und dem HMENU.
Vergleich der jeweils benötigten SQL-Queries beider Versionen:
| Seite | Fluid-basiert | TypoScript-basiert |
|------------|---------------|--------------------|
| Home | 6094 | 4699 |
| Impressum | 1259 | 904 |
| Page 1 | 1251 | 895 |
| Page 2 | 1251 | 895 |
| Page 2.1 | 1250 | 895 |
"Aber mit b13/menus..."
Klar, wer sich mit Performance bei Menüs beschäftigt, der stößt rasch auf die Menus-Extension. Diese liefert DataProcessors bzw. eigene cObjects, die performanter den Baum auslesen, der der Navigation zugrundeliegt. In das reine Rendering des Menüs greift die Extension hingegen nicht ein.
Vergleich der jeweils benötigten SQL-Queries beider Versionen:
| Seite | Fluid-basiert | mit b13/menus |
|------------|---------------|--------------------|
| Home | 6094 | 7436 |
| Impressum | 1259 | 1704 |
| Page 1 | 1251 | 1695 |
| Page 2 | 1251 | 1695 |
| Page 2.1 | 1250 | 1694 |
Nachtrag: Gepimpte Menus-Extension
Das Ergebnis hatte uns verwundert, weshalb wir das Gespräch mit Benni, dem Entwickler der Menus-Extension, gesucht haben. Dabei wurde deutlich, dass in Zeiten von TYPO3 v12 sich der Vorteil der Extension wohl nicht mehr so sehr bemerkbar macht, wie unter TYPO3 v9, als die Extension entstanden ist. Die Notwendigkeit der f:link.*-ViewHelper im Template drückt heute die Performance viel massiver.
Mit etwas Pimpen (Danke Benni!), indem die MenuItems auch im TreeMenuProcessor mit dem Link angereichert werden, lassen sich die Werte aber deutlich verbessern.
| Seite | Fluid-basiert | mit b13/menus | mit b13/menus (gepimpt) |
|------------|---------------|--------------------|-------------------------|
| Home | 6094 | 7436 | 7436 |
| Impressum | 1259 | 1704 | 18 |
| Page 1 | 1251 | 1695 | 11 |
| Page 2 | 1251 | 1695 | 11 |
| Page 2.1 | 1250 | 1694 | 11 |
Wow! Fluid ballerte über 1600 SQL-Queries raus, um die 800 Links zu erzeugen. D.h. im Vergleich zum normalen MenuProcessor werden fürs Auslesen/Zusammenbauen des Menüs die Datenbankabfragen durch "Menus" drastisch reduziert, der Effekt jedoch durch die f:link.*-ViewHelper wieder wettgemacht.
Ohne diesen kleine Hack ist der Einsatz kontraproduktiv.
Warum immer bei 0 beginnen?
Eingangs hatte ich geschrieben, dass TYPO3 Menüs pro Seite neu generiert, weil immer der aktive Pfad und die aktuelle Seite berücksichtigt werden müssen. Aber muss deshalb immer bei 0 begonnen werden?
Nehmen wir eine Website mit vier Ästen (Page1, Page2, Page 3, Page4). Warum müssen die Äste 2-4 neu ausgelesen und gerendert werden, wenn sich der Besucher nur in Page1 und deren Unterseiten bewegt? Der aktive Zustand bewegt sich nur in diesem Ast - die anderen drei Äste bleiben unverändert. Warum sollten diese also nicht gecacht werden? Und zwar nicht nur ihre Datenstruktur, sondern das gesamte renderte HTML?
Vor 5 Jahren hatte ich mich bereits einmal ausführlicher damit befasst und in meinem Blog im Artikel "Mega-Menü mit Cache optimieren" eine TypoScript-basierte Lösung vorgestellt.
Welche Auswirkung hat dieser Ansatz auf unser Test-Szenario?
Vergleich der jeweils benötigten SQL-Queries beider Versionen:
| Seite | Fluid-basiert | TS-basiert, cached |
|------------|---------------|--------------------|
| Home | 6094 | 4728 |
| Impressum | 1259 | 844 |
| Page 1 | 1251 | 837 |
| Page 2 | 1251 | 837 |
| Page 2.1 | 1250 | 837 |
Geht da noch mehr?
Mein bisheriges gecachte TypoScript-Menü ist von einem ausgeglichenen Seitenbaum ausgegangen. D.h. es funktioniert gut, wenn alle (Haupt-)Navigationspunkte etwa gleich viele Unter- & Unterunterseiten haben.
Bei unserem Projekt tanzt aber ein Menüpunkt/Ast aus der Reihe: “Fakultäten”. Auf Ebene 2 sind dort die einzelnen Fakultäten, Ebene 3 & 4 dann die ganzen Fakultätsunterseiten. Damit befinden sich unterhalb dieses einen Menüpunktes ca 2/3 aller Menüeinträge. Die Struktur ist also weit weg von einem ausgeglichenen Baum.
Der bisherige Ansatz war, das Menü zu splitten, pro Hauptnavigationspunkt ein HMENU zu konfigurieren und diese jeweils zu cachen.
Aber warum eigentlich nur auf Ebene 1 cachen?
Beim Stöbern durch die TypoScript Reference wird mehr oder weniger schnell klar: innerhalb eines HMENU gibt es nicht wirklich Möglichkeiten, um Teile des Menüs zu cachen. Außer, wir denken komplizierter.... Wir hatten bereits das HMENU mittels First-Level-Funktion auf Ebene 1 gecacht. Wenn also Ebene 2 eines Baums auch wieder ein HMENU wäre, aber als Menüpunkt innerhalb des HMENU der Ebene 1 gerendert wird...
Ja, da ging noch was!
| Seite | Fluid-basiert | TS-basiert, 2 Caches |
|------------|---------------|----------------------|
| Home | 6094 | 5206 |
| Impressum | 1259 | 56 |
| Page 1 | 1251 | 116 |
| Page 2 | 1251 | 113 |
| Page 2.1 | 1250 | 116 |
Die Details erklären wir (incl. Code-Snippets) in einem eigenen Artikel: Dropdown-Menü ohne Last - mittels Multilevel-Cache
Zusammenfassung
Vergleich aller getesteten Menü-Varianten hinsichtlich der jeweils benötigten SQL-Queries:
| Seite | Fluid-basiert | mit b13/menus | TypoScript-basiert | TS-basiert, cached | TS-basiert, 2 Caches | mit b13/menus (gepimpt) |
|------------|---------------|----------------|--------------------|--------------------|----------------------|-------------------------|
| Home | 6094 | 7436 | 4699 | 4728 | 5206 | 7436 |
| Impressum | 1259 | 1704 | 904 | 844 | 56 | 18 |
| Page 1 | 1251 | 1695 | 895 | 837 | 116 | 11 |
| Page 2 | 1251 | 1695 | 895 | 837 | 113 | 11 |
| Page 2.1 | 1250 | 1694 | 895 | 837 | 116 | 11 |
99% weniger Datenbank-Transaktionen
Wie immer bei Performance-Optimierung, muss man genau gucken, wo es konkret hängt bzw. wie die Rahmenbedingungen sind. Nicht jede performantere Lösung ist auch verständlicher/schöner. Es bleibt immer ein Frage der Abwägung. Aber mal ehrlich: 99% weniger SQL-Queries ab dem zweiten Seitenaufruf innerhalb der Website? Das ist es doch wert. Bei über 800 Seiten heißt das, dass wir (bevor der Seitencache greift) rund eine Million Queries einsparen!
Kannst Du nicht glauben?
Glaube keiner Statistik, die Du nicht selbst gefälscht hast, heißt es immer so schön. Verständlich. Deshalb möchten wir Euch einladen, den Vergleich selbst anzuschauen, zu optimieren, andere Messkriterien zu prüfen,... - und darüber zu reden, zu posten, zu diskutieren.
Im Git-Repository liegt ein Test-Projekt mit
- TYPO3 v12 als Basis
- über 1000 Seiten (davon etwa 800 in der Hauptnavigation)
- 5 (plus 2) inhaltlich gleichen Menü-Varianten
- fertig startbarer DDEV-Umgebung
- vorkonfiguriertem xhprof und XHGui
- aktivierte MySQL Report-Extension
Noch mehr Zahlen des Vergleichs
In diesem Artikel haben wir nur an der Oberfläche des Vergleichs gekratzt und lag der Fokus auf der Anzahl der SQL-Queries. Bei Performance & Last spielen aber natürlich auch anderen Werte eine Rolle, und kommt es immer auf das Szenario sowie das Software-/Hardware-Setup an.
In "Menü-Vergleich: Zahlen, Zahlen, Zahlen" stellen wir viel mehr Zahlen im Vergleich vor.
Lösung für Dein Projekt
Egal, mit welcher Lösung Du liebäugelst: Finde eine Lösung, die zu Deinem Projekt passt!
Wie schon mehrfach erwähnt: jedes Projekt ist anders, hat seine Besonderheiten - und seine Stolpersteine. Daher gibt es nicht die eine, beste Lösung, sondern soll dieser Vergleich vielmehr Denkanstöße geben, welche Möglichkeiten es überhaupt gibt.
Falls Du Unterstützung bei der Analyse und Lösung Deiner Performance-Probleme suchst, darfst Du Dich natürlich gerne an uns wenden ;-)