Türchen 08: Magento Cache Warming und weitere Caching Tricks

Dass Caches bei Web-Applikationen eine besondere Rolle spielen ist schon lange kein Geheimnis mehr. Und dass Magento ohne richtiges Caching sehr langsam sein kann, leider auch nicht. Umso wichtiger ist es, die Caching-Strategien eines jeden individuellen Shops zu analysieren und auf die Produktupdate- und Deployment-Prozesse anzupassen.

In diesem Blogpost schauen wir uns genauer an wie die serverseitigen Caches aufgewärmt werden können um zu verhindern, dass viele gleichzeitige ungecachte Requests die Server nach einem Deployment oder Produkt-Update in die Knie zwingen. Außerdem sollte man das zeitaufwendige Generieren der cachebaren Inhalte nicht den Besuchern und den Suchmaschinen zu überlassen, sondern die Seiten schon vor dem ersten Besucher erzeugt und im Cache eingelagert haben.

Caching-Architektur

Im einfachsten Fall betreibt man einen einzelnen Webserver auf dem Magento läuft. Die Magento-Instanz verwendet eines der verfügbaren Cache-Backends:

Cache_einfach

Wenn man es mit dem Shop ernst meint, sieht die Architektur dann vermutlich eher so aus:

Cache_erweitert

Mehrere Magento-Serverinstanzen teilen sich ein Backend-Storage (das wiederum aus mehreren Instanzen bestehen kann und sollte). Die gerenderten Seiten werden von einem Reverse Proxy Server wie beispielsweise Varnish gecacht. Aus Gründen der Ausfallsicherheit sollte es auch davon mindestens zwei Instanzen geben, die über einen Load-Balancer eingebunden sind.

In beiden beiden Szenarien gilt: Man hat immer die volle Kontrolle über alle Caches, die in der eigenen Infrastruktur liegen (also rechts von der „Internetwolke“ in den Grafiken oben) aber keine Kontrolle über die Caches die außerhalb liegen, wie zum Beispiel die Browser-Caches oder ggf. die Caches anderer Proxy-Server.

Cache Warming

Je nach Produktupdate- und Deployment-Prozess gibt es Situationen in denen der gesamte Cache oder große Teile davon leer sind oder geleert werden müssen. In diesen Fällen kann man das Befüllen der Caches den Besuchern und Suchmaschinen überlassen oder es selbst in die Hand nehmen. Letzteres hat den Vorteil das kein Request eines echten Besuchers einen Cache-Miss erzeugt und überdurchschnittlich lange dauert.

Generierung der Urls

Für das Aufwaermen der Caches wird eine Liste der Urls benötigt, die sinnvoll gecacht werden können. Oft sind das folgende Seiten:

  • Startseiten und Landingpages (für alle Store-Views)
  • Produktseiten
  • Kategorieseiten
  • CMS-Seiten

Natürlich muss dabei auch darauf geachtet werden, dass diese Seiten tatsächlich in Magento oder einem anderen Cache eingelagert werden. Standardmäßig ist Magento eher konservativ was das Caching betrifft und wird keine Produkt- und Kategorieseiten cachen. Abhilfe schafft da zum Beispiel die extension Aoe_Static in Verbindung mit Varnish (http://www.fabrizio-branca.de/make-your-magento-store-fly-using-varnish.html).

sitemap.xml

Die einfachste Möglichkeit an eine Liste von relevanten Urls zu kommen ist das Google Sitemap Feature von Magento.
Dazu im Backend unter "Catalog > Google Sitemap" einen neuen Datensatz anlegen (z.B. mit dem Dateinamen sitemap.xml) und dann auf "Save & Generate" klicken:

google_sitemap2

Je nach Pfadeinstellung liegt nun vermutlich im Magento-Rootverzeichnis die sitemap.xml. Diese Datei kann direkt lokal verwendet werden falls das Cache-Warming-Skript auf dem gleichen Server laufen sollte oder per curl auf den entsprechenden Server heruntergeladen werden.

Die Sitemap ist zunächst einmal eine große XML-Datei, die Links zu allen Produkt-, Kategorie- und CMS-Seiten enthält.

Mit dem Kommandozeilentool "xpath" (Ubuntu: sudo apt-get install libxml-xpath-perl) kann man nun leicht die Urls extrahieren:

cat sitemap.xml | xpath -q -e "/urlset/url/loc/text()" > tmp.urls

Oder wenn man die Datei nicht lokal vorliegt:

curl --silent http://example.com/sitemap.xml | xpath -q -e "/urlset/url/loc/text()" > tmp.urls

n98-magerun

Kann man allerdings die Google-Sitemap nicht verwenden oder möchte man die Liste der Urls skriptgesteuert erzeugen, bietet das Kommandozeilentool n98-magerun eine elegante Lösung.

n98-magerun ist leicht und schnell installiert (https://github.com/netz98/n98-magerun#installation) und ist nicht nur für die Generierung einer Url-Liste ein tolles Tool, sondern hilft auch bei vielen anderen Problemen weiter.

Eine Liste von Urls lässt sich folgendermaßen generieren: (siehe auch https://github.com/netz98/n98-magerun#list-urls)

./n98-magerun.phar sys:url:list --add-all 4,5 > tmp.urls

Wobei "4,5" in diesem Fall die Liste der Store-Ids ist. Anstatt "--add-all" kann man auch eine Kombination von "--add-categories", "--add-products" und "--add-cmspages" verwenden um die Url-Typen in der Liste zu selektieren.

Direktes Cache-Warming

Sollte der Webserver von außen unter dem normalen Hostnamen erreichbar sein, kann man nun einfach die List der Urls durchgehen und Requests an den Webserver senden. Am einfachsten geht das mit dem Tool "siege":

siege -v -c 1 -r `cat tmp.urls | wc -l` -f tmp.urls

Schöner geht das mit curl, weil man hier einen besseren Einfluss auf die Request-Header hat. Nicht selten halten Reverse Proxies (und die internen Caches der Web-Applikationen) verschiedene Versionen einer Seite anhand verschiedener Header vor. Am weit verbreitetsten ist “Vary: Accept-Encoding”, das besagt, dass für jeden unterschiedlichen Accept-Encoding-Header eine eigene Version vorgehalten werden muss. In der Regel senden die modernen Browser den Header „Accept-Encoding: gzip, deflate“. Inhalte dürfen also vom Webserver komprimiert werden. Andere Konfigurationen sehen zum Beispiel auch unterschiedliche Inhalte auf Basis des User-Agents vor.

In jedem Fall müssen schon beim Cache-Warming die passender Header und idealerweise alle Headerkombinationen (sollten mehrere Vary-Header verwendet werden) berücksichtigt werden. Ansonsten werden irrelevante Cache-Inhalte eingelagert, die später troztzdem zu Cache-Misses führen. Verwendet man curl hat man auch Einfluss auf die Header:

for i in `cat tmp.url`; do
  curl -H "Accept-Encoding: gzip, deflate" -s -X GET -I "$i"
done

Mit siege ist das ebenfalls möglich:

siege -v -c 1 --header="Accept-Encoding: gzip, deflate" -r `cat tmp.urls | wc -l` -f tmp.urls

Indirektes Cache-Warming

Verwendet man mehrere Reverse Proxies (siehe Grafik oben) und/oder ist das Deployment noch nicht unter dem Original-Hostnamen erreichbar, kann man die Proxies auch direkt ansprechen und den ursprünglichen Hostnamen als Header übermitteln. Auf diese Art und Weise können die Caches aller Reverse Proxies aufgewärmt werden. Dafür ist allerdings dann auch ein Vielfaches an Requests notwendig.

Führt man ein A/B-Deployment auf Verzeichnis- oder Serverebene durch (siehe http://www.fabrizio-branca.de/meet-magento-de-2012-high-performance-multi-server-magento-in-der-cloud.html) oder möchte man vor einem (Re-)Launch die Seite crawlen bevor sie von außen erreichbar ist kann man das wie folgt machen:

for i in `cat tmp.urls`; do
  echo $i
  URLHOSTNAME=`echo "$i" | sed -e 's@.*//\([^/]*\)/*.*@\1@'`
  URLPATH=`echo "$i" | sed -e 's@.*//[^/]*\(/*.*\)@\1@'`
  for varnish in 'varnish1hostname varnish2hostname'; do
    curl -H "Accept-Encoding: gzip, deflate" -H "Host: ${URLHOSTNAME}" -s -X GET -I http://$varnish/$URLPATH | egrep 'HTTP/|Age:|X-Cache'
  done
done

In diesem Fall iteriert man zunächst über die Liste der Urls. Bei jeder Url wird der Hostname und der Rest der Url voneinenander getrennt. In einer inneren Schleife wird über alle IP-Adressen oder Hostnamen der Reverse Proxies iteriert. Diese müssen noch nicht von außen erreichbar sein. Hauptsache das Cache-Warmingskript hat Zugriff. Nun wird der Originalhostnamen durch den Hostnamen des Proxies ersetzt und der Originalhostname wird stattdessen als Header übergeben. (Genauso funktioniert übrigens auch das HTTP-Protokoll...)

Achtung: Die Größe aller beteiligten Caches muss natürlich so groß gewählt werden, dass alle Seiten abgelegt werden können. Ansonsten werden – je nach Strategie der Caches – die ersten Seiten immer wieder von den neu hinzukommenden Seiten verdrängt.

In einigen Situation ist der Produktkatalog so riesig, dass das Crawlen aller Urls zu lange dauern würde. In diesem Fall muss die Url-Liste auf die wichtigsten Urls beschränkt werden. Die Kategorieseiten sind hier vermutlich in den meisten Fällen eine gute Wahl.

Cache-Prefix

Führt man ein A/B-Deployment durch (egal ob auf Verzeichnisebene oder mit ganzen Servergruppen) ist es sinnvoll jedem Deployment sein eigenes Cache-Prefix in der local.xml zu vergeben:

<config>
  <global>
    <cache>
      <prefix>FOO_PROD_<strong>197</strong>_</prefix>
    </cache>
  </global>
</config>

Auf diese Weise treten die Cache-Einträge des alten Deployments nicht in Konflikt mit den neuen Cache-Einträgen und können im selben Cache-Backend koexistieren. Dass setzen eines neuen Cache-Prefixes für das neue Deployment hat zur Folge, dass die Cache in diesem Namensraum zunächst einmal leer ist. Ein Grund mehr den Cache vor dem Liveschalten aufzuwärmen.

Weitere Herausforderungen

In einer idealen Welt müssten Cache-Einträge niemals verfallen (siehe auch: http://martinfowler.com/bliki/TwoHardThings.html). Sollte ein Cache-Eintrage invalidiert werden, kann ihn man aktiv aus allen Caches entfernen. Obwohl das einfach für den Magento-internen Cache geht und auch die Caches der Reverse Proxies bei Änderungen aktiv "gepurgt" werden können, ist das leider nicht für alle beteiligten Caches möglich (vor allem nicht für die Browsercaches). Hier gilt es nun eine Cache-Lifetime zu wählen die großzügig genug ist um die Server nicht unnötig mit neuen Requests zu belasten aber klein genug ist, um im Falle einer Änderung nicht zu lange alte Inhalte anzuzeigen.

Ein gängiger Trick hierbei ist die Cache-Lifetime für alle Caches über die wir die Kontrolle haben lange zu halten, aber die Cache-Lifetime für die Clients über die Cache-Header kurz zu halten. So hat man die volle Kontrolle über die Inhalte und im Client abgelaufene Requests werden schnell und effizient von den Reverse Proxies bearbeitet, die die Inhalte länger cachen dürfen. Wie das in Varnish gemacht werden kann ist hier zu finden: https://www.varnish-cache.org/trac/wiki/VCLExampleLongerCaching

Fazit

Eine saubere Caching-Architektur und Prozesse im Umgang mit den Cache-Inhalten sind nicht trivial. Hat man dies allerdings im Griff ist das der Schlüssel zur Performance. Das Ausarbeiten eines ausgefeiltes Caching-Konzept, das Caches auf allen Ebenen berücksichtigt, ist sicher gut investierte Zeit und zahlt sich schnell durch erfolgreiche Projekte und zufriedene Kunden aus!



Ein Beitrag von Fabrizio Branca
Fabrizio's avatar

Fabrizio Branca (Twitter: @fbrnc) ist Lead Magento Developer bei AOE. Er lebt mit seiner Familie in San Francisco, California. Auf seiner Webseite http://www.fabrizio-branca.de bloggt er über TYPO3, Magento, Varnish, Selenium und seine Fotos. Außerdem sind dort auch einige seiner freien Magento Module wie zum Beispiel Aoe_Profiler, Aoe_Scheduler, Aoe_TemplateHints oder die Magento-Varnish-Integration Aoe_Static zu finden.

Alle Beiträge von Fabrizio

Kommentare
Rokko_11 am

Hallo Fabrizio!

Danke für diesen tollen Artikel und vor allem für den daraus resultierenden Zweizeiler, der jedes 100-Zeilen lange Cache-Warming-Script überflüssig macht!

Eine kleine Frage habe ich noch zum Ausdruck

siege -v -c 1 --header="Accept-Encoding: gzip, deflate" \\
-r `cat tmp.urls | wc -l` -f tmp.urls

Hier wird die komplette URL-Liste sooft durchgelaufen, wie URLs vorhanden sind. Sollte das nicht

siege -v -c 1 --header="Accept-Encoding: gzip, deflate" \\
-r once -f tmp.urls

heißen?

Viele Grüße, Rokko

Drache am

Danke, hab mir das aufwärmen des Caches schwieriger vorgestellt. Zwei Befehle und das wars. Vielen Dank für den Beitrag !

Vinai am

Endlich komme ich dazu sie letzten Türchen nachzuholen, und bin froh mir die Zeit genommen zu haben - danke für den schönen Post!

Nils Preuß am

Mal wieder was sehr nettes von dir zum Thema Caching, leider erst heute drauf gestossen :)

JCG am

Super, vielen Dank für den sehr interessanten Artikel über Cache und Cache Warming.

Carmen am

Hi Fabrizio,

wenn ich Artikel von Dir zum Thema Caching lese, fühle ich mich immer so herrlich dilletantisch ;-).

Alles Gute zum Geburtstag,

Carmen.

Julian Kleinhans am

Danke Fabrizio!

Artikel in dieser Qualität wünsche ich mir viel mehr von Magento, das macht mir das einarbeiten enorm leichter ;)

Danke!

Tobias Vogt am

Toller Beitrag Fabrizio, danke! .. und natürlich alles gute zum Geburtstag!

Dein Kommentar