Seitenaufrufe von über 3 Sekunden durch "HTML Komprimierung aktiviert"

TL;DR siehe unten.

 

Wir haben seit Wochen starke Performance Probleme (seit Upgrade auf 5.6.4) und waren schon am verzweifeln. Heute habe ich erneut xdebug angeworfen um mich abermals in die Untiefen von shopware zu stürzen. Vorab eine kurze Analyse des ist-Zustandes.

 

Je mehr Artikel in einer Kategorie oder Suche, desto langsamer der Seitenaufbau. Und damit meine ich wirklich ob 100 oder 1.000 (angezeigt werden jeweilse nur 48). Sprich es hatte nur indirekt mit der Anzahl zu tun. Der Seitenaufbau war bei 1.000 Artikeln dann schon bei ~3 Sekunden angekommen. Die Zeit wurde fast ausschließlich als CPU Zeit in PHP verbraten. strace war leider auch nicht hilfreich.

 

Alle Lösungsansätze hier im Forum hatten nicht geholfen bzw. ich hatte vermutlich nicht die richtigen Stellen gefunden. Auch das Changelog durchwühlen war ein herumstochern ohne Ergebnis. Im nachhinein habe ich einen Beitrag von Moritz Naczenski gefunden, der dies anspricht, dem Threadersteller aber nicht geholfen hatte und auch den entsprechenden Changelog Eintrag (zu 5.6.0).

 

Nun also die Erkenntnisse:

“HTML Komprimierung aktivieren” unter Performance > Einstellungen > Allgemein > Verschiedenes nutzt einen Regex (siehe https://issues.shopware.com/issues/SW-23199 bzw. engine/Shopware/Components/Template/HtmlMinCompressor.php), der von stackoverflow (https://stackoverflow.com/questions/5312349/minifying-final-html-output-using-regular-expressions-with-codeigniter) übernommen wurde und leider inperformant ist (in Sachen CPU-Usage).

 

Hier erst mal das Ergebnis:

Unkomprimiert:

-rw-r--r-- 1 root root 242192 Mar 12 18:33 index.minified.html
-rw-r--r-- 1 root root 950373 Mar 12 18:32 index.orig.html

Komprimiert:

-rw-r--r-- 1 root root 24170 Mar 12 18:33 index.minified.html.gz
-rw-r--r-- 1 root root 38295 Mar 12 18:32 index.orig.html.gz

 

Ich will gar nicht absprechen, dass das durchaus sinnvoll sein kann (wobei wir hier von 14k Differenz sprechen), ich finde nur fatal wie es umgesetzt wurde.

Ich habe ein Testskript erstellt, was nur den Inhalt via file_get_contents einliest, den Regex aus engine/Shopware/Components/Template/HtmlMinCompressor.php anwendet und per file_put_contents wieder speichert:

# time php HtmlMinCompressor.php 

real    0m2.498s
user    0m2.468s
sys    0m0.012s

Danach habe ich einfach aus dem gleichen stackoverflow Beitrag weiter unten eine Funktion übernommen, die erst alle Einträge, die nicht minified werden dürfen, durch Platzhalter ersetzt, dann HTML minified und am Ende die Platzhalter wieder mit dem originalen Inhalt austauscht, eingebunden (die original Funktion aus dem Beitrag entfernt als erstes noch alle Kommentare, das habe ich nicht übernommen). Das Ergebnis ist bis auf einen abschließenden Zeilenumbruch identisch.

# time php HtmlMinCompressorOptimized.php 

real   0m0.023s
user   0m0.016s
sys    0m0.004s

 

Soweit so gut. Jetzt habe ich noch schnell geschaut, wieso ich überhaupt 700.000 whitespaces in meiner Seite habe und bin über den Filter gestoßen (Grundeinstellungen > Storefront > Filter / Sortierung), der auf jeder Seite ist und bei uns immer alle Eigenschaften, die die Artikel haben, anbietet. Dieser wird immer umfangreicher, je mehr Artikel in einer Kategorie sind, weil dann immer mehr Optionen zu den Eigenschaften angeboten werden.

 

Hier ein Beispiel eines einzelnen Eintrags:

                                                                                                                            Streifen

Davon gibt es dann halt z.B. 328 Einträge auf einer Seite mit vielen Artikeln.

 

Vielleicht wäre es besser gewesen einen Template HTML Minifizierer zu erstellt, damit dies nur beim kompilieren passiert und nicht bei jedem Seitenaufruf. Dann wäre die schlechte Performance auch nicht fatal gewesen.

 

TL;DR

“HTML komprimieren” ist mittels inperformantem regex-only umgesetzt worden und sollte nicht genutzt werden (bis es hoffentlich schnellstmöglich gepatcht wird).

Hallo, wir haben heute von SW 5.5.3 auf 5.6.6 geupdatet. Ich war voller Vorfreude, dass durch die neue HTML Komprimierung vielleicht ein kleiner Geschwindigkeitsbonus zustande kommt.

Aber bei uns ist die Realität wie von dir beschrieben ebenfalls so, dass die Seitenaufrufe deutlich langsamer sind mit eingeschalteter Komprimierung. Auch das neue Aufbauen des HTTP-Caches dauert gefühlt 3-4x länger als ohne Komprimierung.

Ich wundere mich, dass die HTML Komprimierung als eine Performaceverbesserung angepriesen wurde. Auf 5.5.3 hatten wir noch das Plugin Performance Optimierungen installiert. Auch hier konnte ich die HTML Komprimierung nicht ernsthaft aktivieren weil es sich ebenfalls negativ ausgewirkt hat.

Unser Server ist eigentlich keine Gurke (i7-7700 CPU @ 3.60GHz (8 core(s)), 64GB Ram, 512GB NVMe SSD) dafür, dass nur eine Shopwareinstallation darauf läuft. Den Server möchte ich als Grund eher ausschließen.

Gibt es hier denn auch gegenteilige Erfahrungen…Hat es jemand im Einsatz und konnte schnellere Ladezeiten realisieren?

Wenn du gzip aktiv hast, bringt die HTML-Komprimierung ohnehin so gut wie nichts. Bei großem Markup (viele Filter, viele Produkte usw.) wirkt sich das eher negativ auf die Performance aus (siehe oben). Da die Komprimierung mit allerlei Plugins funktionieren muss, ist der Regex relativ komplex. Wir werden die per Default mit dem nächsten Update deaktivieren, macht nur für sehr kleine Shops Sinn.

Besser Serverseitig die Komprimierung einrichten und dann läuft das ähnlich schnell.

Und vielleicht schaut Ihr euch den Vorschlag von das_knopfloch einmal genauer an. Er scheint ja eine performantere Lösung gefunden zu haben.

@Moritz Naczenski schrieb:

Wenn du gzip aktiv hast, bringt die HTML-Komprimierung ohnehin so gut wie nichts. Bei großem Markup (viele Filter, viele Produkte usw.) wirkt sich das eher negativ auf die Performance aus (siehe oben). Da die Komprimierung mit allerlei Plugins funktionieren muss, ist der Regex relativ komplex. Wir werden die per Default mit dem nächsten Update deaktivieren, macht nur für sehr kleine Shops Sinn.

Besser Serverseitig die Komprimierung einrichten und dann läuft das ähnlich schnell.

Selbst wenn Ihr den default ändert, solltet Ihr dennoch die Funktion austauschen! Die Funktion so wie sie ist zu belassen sollte kein Option sein. Sonst aktiviert das jemand, merkt keinen Unterschied und importiert danach große Bestände und findet nicht den Fehler.

 

Auch gzip verbraucht mehr Zeit, wenn er unnötig viele whitespaces komprimieren muss. Zwar bei meinen Tests nur ca. 3ms, das macht das minify aber nur zusätzlich lukrativer.

 

Ich würde das feature wirklich gerne nutzen, da es Sinnvoll ist, also bitte bitte die Funktion auch tauschen! (obacht, das Beispiel bei stackoverflow enthält in der ersten Zeile ein strip-comments zusätzlich, was entfernt werden muss ; siehe https://stackoverflow.com/questions/5312349/minifying-final-html-output-using-regular-expressions-with-codeigniter/38884356#38884356).

Es gibt natürlich bessere Regexe - aber das Problem ist, dass diese auch mit allen Plugins funktionieren müssen und da zeigte sich halt die Erfahrung aus dem Frosh-Plugin, dass dies deutlich komplizierter ist. Als individuelle Lösung auf den eigenen Shop zugeschnitten kann man immer einen passenden und schnellen Regex finden. Einen Regex der mit jeglicher Form von HTML-Modifizierung funktioniert, ist da schon deutlich komplexer. Es ist ja nicht so, als ob wir da nicht auch verschiedene Dinge schon ausprobiert haben. Als schnelle Lösung setzen wir den per Default auf inaktiv und machen einen Hinweis dran.

Perspektivisch kann man dann schauen, ob er Regex sich noch optimieren lässt, aufgrund der Plugin Thematik aber halt deutlich aufwendiger, da man nicht nur einen Case durchtesten muss.

Die Quellcode Funktion hier im Forum zerstört irgendwie Quellcode …

Moin @das_knopfloch‍ also ich bin interessiert an deiner Lösung. Wenn ich das richtig verstehe, sollte die Verbesserung nur die Filter betreffen, aber alles andere nicht? Kann man das explizit nur für die Filter nutzen?

Hier ein neuer Versuch, die Codeschnippsel-Funktion hier im Forum ist ein graus. Also, die Funktion ist ein 1 zu 1 Ersatz für eure aktuelle Funktion. Sie ist lediglich schnell, wodurch es zu keinem Performance-Problem kommt. Ansonsten produziert sie identischen Code zu eurer aktuellen Funktion.

 

public function minify(string $content): string
{
    $blocks = [];
    $regex = '!]*?>.*?\1>!is';

    // It is assumed that this placeholder could not appear organically in your
    // output. If it can, you may have an XSS problem.
    $placeholder = "@@@@";
    $placeholderLength = strlen($placeholder);

    // Replace all the tags (including their content) with a placeholder, and keep their contents for later.
    $content = preg_replace_callback(
        $regex,
        function ($match) use (&$blocks, $placeholder) {
            $blocks[] = $match[0];
            return $placeholder;
        },
        $content
    );

    // Remove whitespace (spaces, newlines and tabs)
    $content = trim(preg_replace('/[\n\t]{2,}|[\n\t]/m', ' ', $content));

    // Iterate the blocks we replaced with placeholders beforehand, and replace the placeholders
    // with the original content.
    $position = 0;
    foreach ($blocks as $block) {
        $position = strpos($content, $placeholder, $position);
        if ($position === false) {
            throw new \RuntimeException("Found too many placeholders in input string");
        }
        $content = substr_replace($content, $block, $position, $placeholderLength);
    }

    return $content;
}

 

2 „Gefällt mir“

@Moritz Naczenski schrieb:

Es gibt natürlich bessere Regexe - aber das Problem ist, dass diese auch mit allen Plugins funktionieren müssen und da zeigte sich halt die Erfahrung aus dem Frosh-Plugin, dass dies deutlich komplizierter ist. Als individuelle Lösung auf den eigenen Shop zugeschnitten kann man immer einen passenden und schnellen Regex finden. Einen Regex der mit jeglicher Form von HTML-Modifizierung funktioniert, ist da schon deutlich komplexer. Es ist ja nicht so, als ob wir da nicht auch verschiedene Dinge schon ausprobiert haben. Als schnelle Lösung setzen wir den per Default auf inaktiv und machen einen Hinweis dran.

Perspektivisch kann man dann schauen, ob er Regex sich noch optimieren lässt, aufgrund der Plugin Thematik aber halt deutlich aufwendiger, da man nicht nur einen Case durchtesten muss.

 

Meine Bitte wäre: Lasst das ein mal kurz einen Programmierer sich anschauen, wir würden das Feature halt wirklich gerne nutzen, das default ändern hilft uns dabei nicht und wie oben beschrieben habt ihr dann immer noch problematischen code in eurem System der shops unbrauchbar macht, wenn sie wachsen.

@brettvormkopp schrieb:

Moin @das_knopfloch‍ also ich bin interessiert an deiner Lösung. Wenn ich das richtig verstehe, sollte die Verbesserung nur die Filter betreffen, aber alles andere nicht? Kann man das explizit nur für die Filter nutzen?

 

Nein, die Funktion entfernt per se erstmal nur unnötige whitespaces in HTML. Aktuell wird sie auf den gesamten Webseiteninhalt ausgeführt, was dann auch bei Filtern hilft. Eine Lösung nur auf Filter ist so nicht vorgesehen.

1 „Gefällt mir“

Habe deine Funktion eingefügt bei uns und es ist wirklich schneller. Danke dafür. Richtig gute Arbeit!

Ggf. könnte man noch select in die Placeholder stecken, denn große “dropdowns” mit hunderten options-Zeilen haben da auch Potenzial für unerwartete Probleme.

Mach gerne einen Pull-Request auf Github, dann bekommst du auch Feedback zu deiner Lösung und es ist im ganz normalen Workflow mit drin.

Ein Nebeneffekt dieser Funktion ist, dass der HTTP Cache auch weniger schnell wächst. Die Einsparungen dort sind manchmal gravierender als die Ersparnis beim Ausliefern der Seite, weil wie [@Moritz Naczenski](http://forum.shopware.com/profile/14574/Moritz Naczenski “Moritz Naczenski”)‍ schon sagt holt GZip bei der Übertragung via HTTP schon mehr als die Whitespace-Funktion raus. Nur beim Cache auf der Platte greift GZip nicht.

btw. Ich hab schon ein funktionerden PullRequest:

https://github.com/shopware/shopware/pull/2334

@h_lohaus schrieb:

btw. Ich hab schon ein funktionerden PullRequest:

https://github.com/shopware/shopware/pull/2334

Oh, das war irgendwie irritierend. Ich hatte deinen pull-request zwar gesehen, aber gedacht das hat nur irgendwas mit SEO zu tun und nicht mit diesem Problem.

problematisch ist, dass die Migration zum feature es per default aktiviert. Lässt mich auf jeder Node die ein 5.6 Update durchmacht die max_execution_time kennen lernen. Eine Bitte wäre das boolean true in SW-23199 - Add html minify · shopware/shopware@c0a52a0 · GitHub auf false abzuändern.

Die gute Absicht hinter dem Feature verstehe ich und bedauere, dass sich die Idee zunächst nicht bewährt. Einen genauen Benchmark habe ich nicht, aber wo ich es gesehen habe war der Mehranteil nicht vertretbar.

ich sehe, dass Ihr das mit der 5.6.7 konservativer macht - danke

 SW-24877 - HTML-Minfier ist bei Neuinstallationen nicht mehr standardmäßig aktiv 

@das_knopfloch‍ Danke für deinen Beitrag, bei uns hat das durch enorm viele Produkte und Filter Ladezeiten von teilweise 10 Sekunden auf 1 Sekunde gedrückt. Wir sind total baff!!