Zum Inhalt springen

Performance-Problem im Fluid Template

Erstellt am 20. Mai 2011 · 2 Antworten · letzte Antwort am 30. Mai 2011

Tags: Frage

a.​berl ·

Hallo beisammen,

wir entwickeln eine Sportseite auf Basis von Typo3 4.5 mit einer Extension auf Extbase/Fluid. Allerdings sind wir jetzt an ein Performance-Problem gestossen, dass sich wie folgt auswirkt (als Beispiel nehme ich unsere Ergebnistabelle, da es dort besonders schwer wiegt):
Bild: images.trackmyrace.com/…/tmr_screenshot.png
Die Tabelle baut sich dynamisch aus bis zu 32 Objekten eines Repositories pro Pagination-Seite zusammen, welche über Eingabefelder über den Spalten gefiltert und sortiert werden können und per Ajax aktualisiert wird. Durch diese Dynamik fällt klassisches Caching als Totschlaglösung schonmal flach, d.h. die Seite muss an sich schon eine vernünftige Performanz zeigen, damit man sich beim Durchforsten der Tabelle nicht zu Tode warten muss.

Aktuell braucht die Ajax-Seite (ohne die letzte Spalte, die etwas zu viele if/thens noch hat) ~2.15s Serverseitig, was eindeutig zuviel ist. Davon werden ~99% in der Extension verbraten laut Typo3 Profiler und nach ein bisschen weiterem Timing sind davon nur ~300ms der reine Code in der Action (was auch noch Optimierungsbedarf hat, aber erstmal nicht der Bottleneck ist), also ca. 1,7s gehen für Extbase Initialisierung und das Fluid rendering drauf. Liegt das jetzt an an Fluid/Extbase generell, sprich ist es einfach nicht für die Generierung einer so dynamischen Tabelle geeignet, oder ist es einfach nur eine ungeschickt gewählte Kombination aus viewhelpern?

Hier das View-Template:

{namespace tmr=Tx_TmrCore_ViewHelpers}
                        <f:for each="{results}" as="result">
                            <f:cycle values="{0: 'odd', 1: 'even'}" as="zebraClass">
                            	<tr class="content_row result {f:if(condition:'{result.name} == {starter}',then:'highlight')}" rel="{f:uri.action(action:'individual', addQueryString:'true', additionalParams:'{tx_tmrcore_event: { controller: \'Event\', event: event, course: result.course, single_result: result } }')}">
	                                <tmr:tableBody each="{columns}" as="column" key="val" iteration="it" result="{result}">             
		                                <td valign="middle" class="{zebraClass}{f:if(condition:'{it.isFirst} == 1',then:' first')}">
		                                	{val}
		                                </td>
	                                </tmr:tableBody>
                                </tr>
                            </f:cycle>
                        </f:for>
                        <f:render partial="Results/Download" arguments="{event: event, course: course, list: list, results: results, count: count}" />
                        <f:render partial="Results/Pagination" arguments="{action: 'resultstable', lastpage: lastpage, page: page}"/>

Zur Erklärung: Die beiden Partials unten geben den Tabellenfooter mit einem Link zum dynamischen PDF-Download der Tabelle und der Pagination aus. Beide Zusammen nehmen etwa 100ms und sollte daher auch erstmal nur 2.-rangig für Optimierung sein.
Der tmr:tableBody Viewhelper ist eine Kombination aus for- und render-Viewhelper und iteriert über die Spaltenkonfiguration in {columns} (welche im Prinzip nur die Feldnamen enthält, die aktuell angezeigt werden sollen), liest den entsprechenden Wert aus {result} aus und rendert, falls vorhanden, ein eigenes Partial für diese Spalte, ansonsten den eigenen Content. Solch ein eigenes Partial wird für 4 Spalten genommen, welche den Textinhalt über einen weiteren Viewhelper nach einer festen Anzahl Zeichen abschneiden und den vollen Inhalt über das title-Attribut als Tooltip ausgeben.

Abgesehen davon, dass es sich hier um ein O(m*n) Problem handelt - gibt es noch Möglichkeiten die Performance zu optimieren? Ich habe auch bereits versucht, die {results} nicht als QueryResult, sondern als Raw Array zu holen. Das hilft aber nur relativ wenig und ich verliere die Flexibilität eines echten Model Objects mit echten Gettern. Wenn es aber nicht anders geht, verzichte ich gerne auch auf etwas Flexibilität.

Wir werden auch mal XDebug auf dem Server installieren um noch genauer zu profilen, ich wäre aber dennoch über sämtliche Hinweise dankbar.

leviathan0 ·

Hallo,

bei dem Problem kann ich leider nicht sonderlich helfen, da mir dazu einiges an Wissen fehlt.
Dennoch:

Falls das Problem lokalisiert werden konnte, wäre es wiederum sehr hilfreich für mich, wenn Sie mitteilen könnten ob und wie es gelöst wurde.

Ein bescheidener Tipp meinerseits:
Falls es nicht gelöst werden kann, da Fluid / Extbase zu langsam sind. Ggf. eigenes Caching der Objekte in betracht ziehen und oder gänzlich auf Fluid / Extbase verzichten.

a.​berl ·

Nach langem Suchen sind wir erstmal einen Schritt weiter gekommen. Eines der größten Bottlenecks ist tatsächlich das wiederholte rendern von partials, welche jedesmal einen großen Overhead mit sich bringt, da für jede Zeile die partials gelesen, geparsed und dann mit den Variablen-Werten gefüllt werden. Wir haben dies vorerst so umgangen, dass wir in Tx_Fluid_View_AbstractTemplateView::renderPartial den Wert von $this->templateParser->parse($this->getPartialSource($partialName)) gecached haben, welcher den Objekt-Baum des Partials enthält. Dadurch und durch das zusätzliche cachen des Flaggen-Viewhelpers, welcher den vollen Landesnamen aus static tables ausliest und als tooltip mit ausgibt, ist die Zeit für das rendern dieser Ergebnisliste jetzt auf etwas über einer Sekunde, was immer nocht nicht optimal ist, aber weitaus annehmbarer.

public function renderPartial($partialName, $sectionName, array $variables) {
	if (!isset($this->level1cache[$partialName]))
	{
		$this->level1cache[$partialName] = $this->templateParser->parse($this->getPartialSource($partialName));
	}
	$partial = $this->level1cache[$partialName];

Ich weiß nicht, ob das cachen hier irgendwelche Nebeneffekte haben könnte, aber für uns funktioniert es soweit erstmal. Allerdings hilft dieser L1-cache auch nur, wenn ein bestimmtes Partial mehrmals in einem Aufruf gerendert wird, was bei uns momentan auch nur auf der Ergebnisliste vorkommt (4 Stück jeweils bis zu 32mal), dort aber entsprechend viel (das cachegrind log hat sich allein dadurch in der Größe halbiert).

Laut XDebug liegt momentan das Bottleneck in Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode->evaluate (Total Self: ~8%) gefolgt von php::mysql_query (Total Self: ~6,3%) - was anscheinend an den ganzen Typo3-Links in Kombination mit RealURL liegt, die generiert werden.
Dort werden wir auch als nächstes ansetzen, da ich nur ungern im Fluid Core rumspiele.
Falls es jemanden interessiert, hier das cachegrind File zum analysieren: http://www.trackmyrace.com/fileadmin/cachegrind.out.zip

@leviathan0: Danke für den Tipp, aber um gänzlich um Fluid/Extbase herum zu programmieren, sind wir schon zu weit fortgeschritten im Projekt. Ich denke aber, im Endeffekt werden wir nicht ganz um eine clevere Lösung für Typo3-Caching an den Stellen, an denen es möglich ist, herum kommen (z.B. indem man die "Standardansicht" der Ergebnistabelle cached und die dynamisch gefilterten und sortierten Ansichten nicht, wobei ich noch nicht weiß, wie man das mit Typo3 umsetzen kann).