Varnish-Cache mit Magento 2 in einer verteilten Infrastruktur

Für unseren Kunden PUKY – einen führenden Hersteller im Bereich der Kinderfahrzeuge, was Kinderfahrräder, Laufräder, Dreiräder, Go Karts und passendes Zubehör umfasst – sind wir im Januar mit einem Onlineshop auf Basis von Magento 2 online gegangen.

Hier stellten sich mehrere Herausforderungen:

  • PUKY hat vorher Produkte nicht direkt an Endkunden vertrieben, der Vertrieb erfolgte ausschließlich über Fachhändler und Handelspartner.
  • Dies umfasste auch das Geschäft mit Ersatzteilen: Für den Verbraucher sinnvoll, um kleinere Reparaturen bei naturgemäß stark strapazierten Kinderfahrzeugen schnell und kostengünstig durchzuführen – bei Händlern weniger beliebt, da die Marge sehr gering ist.
  • Bestellungen von Händlern sind bisher wenig automatisiert eingelaufen – per eMail, Fax, Telefon.
  • Zudem muss auch eine möglichst einfache Garantieabwicklung und die Bereitstellung von Anleitungen gewährleistet sein.

Kurzum: PUKY hat mit einem Onlineshop, der sowohl B2C als auch B2B abdeckt, Neuland betreten. Die Last war vorher nur sehr vage abzuschätzen, besonders, da im Geschäftsmodell einige besondere Anforderungen stecken.

Mit dem Onlinegang im Januar war uns auch bewußt, dass wir zielstrebig auf das Ostergeschäft zugehen – neben Weihnachten für diese Branche natürlich eine Hochphase, denn die eigenen Produkte sind als Geschenke für Kinder geradezu prädestiniert.

Technisches Umfeld vor dem Caching

PUKY stellt sich mit dem Onlineshop sehr international auf, das stellt sich auch in der Anzahl der landes-/sprachspezifischen Shop dar, die von Beginn an dabei waren:

  • B2C: Deutschsprachig (D/A/CH), Dänemark, Frankreich, Polen, Spanien, Niederlande
  • B2B: Deutschsprachig, Englisch (Währung Euro, für Nicht-UK-Händler), Frankreich, Spanien, Niederländisch, Dänisch (Währung DKK), Polnisch (Währung PLN), Britisch (Währung GBP)
  • Im Bereich B2B gibt es zudem noch die Unterscheidung verschiedener Händlergruppen, die Auswirkungen auf Produktverfügbarkeiten und -daten haben

Vor der Vielzahl der unterschiedlichen Stores und weiterer spezifischer Anforderungen befinden wir und grob in diesem technischen Umfeld:

  • Magento 2-Basis-Installation auf Basis von Apache, PHP und MySQL
  • Anbindung Payone als Zahlungsanbieter
  • Integration von Google-Docs/Google-Sheets zum Laden von spezifischen Informationen (etwa die Liste der zu einem Fahrzeug zugehörigen Ersatzteilen, teilweise mit der Verkettung zu Nachfolgeprodukten sowie die Liste der Händler für eine Händlersuche)
  • Anbindung des CommerceConnector um die Verfügbarkeit spezifischer Produkte bei Händlern in der Nähe zu ermitteln
  • Anbindung der Gaxsys-Börse (hier werden B2C-Bestellungen einer Börse zugeführt, bei der Händler die Bestellungen übernehmen und ausliefern können, nur wenn sich kein Händler findet, erfolgt die Lieferung direkt durch PUKY)
  • Integration von ProAlpha als ERP: Import der Produkte, Verfügbarkeiten, Preislisten, Händler, die für den B2B-Bereich zugelassen sind; im Gegenzug werden Bestellungen aus Magento heraus nach ProAlpha zur weiteren Verarbeitung (etwa durch die Logistik) exportiert. Zudem stellt ProAlpha auch die Bestellungen der Händler bereit, die direkt bei PUKY eingegangen sind, so dass diese dem Händler im Bereich "Meine Bestellungen" ebenfalls angezeigt werden.
  • Akeneo Enterprise als PIM System: Die erweiterten Produktdaten (jenseits von Preisen und Verfügbarkeiten) werden hier sowohl durch die Zentrale als auch durch Übersetzer gepflegt und werden in regelmäßigen Abständen nach Magento synchronisiert.

Da sich PUKY für einen "Soft"-Start entschieden hat und der Launch explizit nicht von einer Kampagne begleitet wurde sondern lediglich von er alten Webseite (die lediglich Produktinformationen jedoch keinen Shop enthielt) auf den neuen Shop umgeleitet wurde, ist die Last gemächlich angestiegen. Das gab uns zum Einen die Möglichkeit, nicht als kritisch angesehene Features auch noch nachträglich zu Implementieren; zum Anderen konnten wir hier ohne übermäßigen Druck die Performance des Systems beobachten und geeignete Maßnahmen ergreifen, ohne dass wir bereits im Vollausbau starten.

Erste Optimierungen

Für die Magento-Installation kommt ein dedizierter Server mit SSD-Platten, 12 Cores und 128 GB RAM zum Einsatz, diese Ausbaustufe haben wir durch ein Upgrade erreicht – vorher standen hier nur 16 GB RAM und 8 Cores zur Verfügung. Das hat sich vor allem in den nächtlichen Importen bemerkbar gemacht: Nachdem das ERP lediglich die vollständige Produktdatenbank per XML exportieren kann, war mit dem "alten" Server aller etwa 5.500 Produkte (sowie der Zuordnung der Varianten zu Mutterartikeln) nicht flüssig möglich, erst das Upgrade hat dieses Problem dauerhaft gelöst.

Da die Auslastung des Servers weiter anstieg, haben wir einige Optimierungen vorgenommen, die das Verhalten verbessert haben:

  • Als Session- und Cache-Backend wird nun Redis genutzt.
  • Die Google-Docs-Daten werden ebenfalls in Redis zwischengespeichert, diese Daten werden ausschließlich aktualisiert, wenn sich die zugrunde liegenden Dateien bei Google geändert haben (dies wird die die Abfrage der Dokumentenversion geprüft). Dadurch ist die Belastung durch wiederkehrende Cron-Jobs reduziert worden
  • Magento wird im Produktionsmodus betrieben: Wir minimieren CSS- und JS-Dateien. Auf bas Minimieren von HTML-Dateien verzichten wir derzeit, da der entsprechende Kompressor Probleme mit Zeilenkommentaren ("//") innerhalb von PHP-Code hat, etwas, das wir vor allem bei externen Modulen nicht wirklich beeinflußen können. Selbes gilt für das Bundling und Merging von CSS- und JS - hier funktioniert die Administrationsoberfläche teilweise nicht fehlerfrei
  • Der Opcode-Cache von PHP ist aktiviert
  • Nach dem Import der Produktdaten wird ein Cache-Warmer verwendet, der dafür sorgt, dass alle Produkt- und Kategorieseiten im Magento-Cache landen, so dass auch der erste Besucher einer spezifischen Produktseite nicht auf den Cache-Aufbau warten muss.

Dies hat einige Wochen vorgehalten – in Bezug auf das "drohende" Osterfest war jedoch nach einer stabilen Phase ein weiterer Anstieg der Auslastung zu erkennen. Nicht kritisch – ein akuter Ausfall drohte nicht, jedoch stiegen die Antwortzeiten des Shops weiter an.

Warum Varnish?

Dabei ist zu bedenken, dass ein Großteil der ausgelieferten Daten relativ statisch sind: Produkttexte, jede Menge Produktbilder und Downloads. Die Anzahl der Änderungen, die hier über den Tag hinweg erfolgen, sind überschaubar. Der Großteil der Daten kommt aus externen Systemen (ERP, Google Docs mit Produkttexten, Akeneo) und wird in der Nacht als Vollexport geladen

Allerdings konnten der Vielzahl an Dateien und Anfragen auch die SSD-Festplatten hier keine dauerhafte Linderung verschaffen, somit war klar, dass ein Reverse Proxy eingesetzt werden muss. Die Wahl fiel dabei sehr schnell auf Varnish, dafür sprachen einige Punkte:

  • Magento-Integration:
    • Die Integration ist nativ vorhanden und kann direkt in der Administrationsoberfläche erfolgen ohne dass eine Erweiterung installiert werden muss.
    • Das leeren des Caches etwa in der Adminstrationsoberfläche oder mit Hilfe des Kommandozeilentools "magento" schlägt direkt auf Varnish durch: Magento initiiert entsprechende PURGEs, somit werden diese Seiten von Varnish beim nächsten Aufruf aufgefrischt
    • Änderungen an CMS-Inhalt, Produkten, Kategorien etc. haben den selben Effekt: Magento sorgt automatisch dafür, dass diese Seiten in Varnish aktualisiert werden, man läuft hier also in keine Cache-Falle: Der Cache liefert hier keinen veralteten Content aus, obwohl das Quellsystem bereits neue Daten bereitstellt
  • Varnish existiert als Standardsystem und kann ggf. mit der Enterprise-Version auch geclustert werden, sofern das notwendig sein sollte
  • Varnish hält alle Dateien In-Memory, was die Auslieferung deutlich beschleunigt!
  • Durch die VCL-Sprache lassen sich sehr leicht Anpassungen vornehmen, wie Varnish mit bestimmten URLs umgeht, dies war im vorliegenden Projekt wichtig, da wir etwa die Anbindung einer Schnittstelle für eine Händlersoftware anbieten. Diese Aufrufe dürfen etwa auf keinen Fall von Varnish zwischengespeichert werden
  •  

Obwohl der eingesetzte Webserver über 128 GB RAM verfügt und somit mehr Spielraum als notwendig existiert, haben wir uns explizit dafür entschiedenen, Varnish auf einem vorgeschaltetem Root-Server zu betreiben:

  • Fällt der eigentliche Online-Shop aus oder ist nicht mehr verfügbar, so lässt sich über Varnish eine Wartungsseite schalten – eine Alternative zur "Connection refused" Seite des Browsers
  • Sollte eine DDoS-Attacke gegen den Webshop gestartet werden, wird erst einmal nur der Varnish-Server in Mitleidenschaft gezogen. Zudem ist davon auszugehen, das Varnish hiermit besser klarkommt als die Magento-Installation. Zudem ist somit selbst bei einem gelungenem Zugriff Dritter somit der Magento-Server weiterhin sicher, da Varnish nur via HTTP mit dem Online-Shop kommuniziert.
  • Ist ein Umzug des Online-Shops auf einen anderen Server mit geänderten Netzwerkdaten notwendig, umgeht man hier das Problem, dass sich DNS-Änderungen erst nach einer gewissen Zeit im Internet durchsetzen – es muss lediglich sichergestellt sein, dass Varnish die neue Adresse kennt und zuverlässig mit dem neuen Server kommuniziert.

Besonders der letzte Punkt hat uns zu Beginn einige Probleme bereitet, die wir allerdings erwartet haben: Für die Domain des Shops mussten wir natürlich den DNS-Eintrag ändern, so dass die Benutzer auf den Varnish-Server und nicht mehr direkt zu Magento geleitet wurden. Hinzu kam, dass wir Magento nun verständlich machen mussten, nicht mehr zur eigenen Basis-URL (die im Rahmen der Umstellung eine Subdomain ist) umzuleiten, wenn der Benutzer nicht direkt auf dieser Subdomain unterwegs ist - ein Feature, dass sonst recht nützlich ist.

Als Hardware für den Root-Server dient hier ein 8-Core-System mit 32 GB RAM - durchaus ausreichend, im Live-Betrieb hat sich gezeigt, dass Varnish etwa 6 GB benötigt, wir haben hier also auch bei umfangreichen Content-Erweiterungen noch Skalierungsmöglichkeiten

Die Herausforderungen während der Magento-/Varnish-Integration

Es gab einige Herausforderungen im Umfeld der Umstellung, die wir bewältigen mussten. Einige haben wir erwartet - wieder andere kamen doch etwas überraschend. Zu erst einmal galt es folgende organisatorische Dinge zu bewältigen:

  • Die Integration sollte erst auf dem Testsystem erfolgen.
  • Im konkreten Projekt wird die Domain DNS-seitig nicht von uns sondern von einem externen Provider verwaltet, dies ist auch nicht der Provider, bei dem der Server betrieben wird - für die finale Umstellung musste also ein passables Zeitfenster gefunden werden.
  • Die Umstellung sollte unterbrechungsfrei stattfinden.
  • Uns war bewusst, dass nach der DNS-Umstellung für einige Tage die Zugriffe teilweise dennoch auf den Magento-Server direkt gelangen - da die Verteilung von DNS-Updates nicht gesteuert werden kann.

Technisch haben sich folgende Anforderungen gegeben:

  • Statischer Content muss durch Varnish direkt ausgeliefert werden.
  • Alle Zugriffe in Richtung Warenkorb, Checkout, Payment, Benutzerkonto, Login und API-Zugriffe von außen müssen direkt an Magento durchgereicht werden.
  • Zur besseren Analyse des Varnish-Verhaltens sowie zur ggf. manuellen Beeinflußung sollte das [Enhanced Varnish Dashboard](https://github.com/brandonwamboldt/varnish-dashboard) von Brandon Wamboldt genutzt werden.
  • Zielsetzung war eine Auslieferung von mehr als 80% der Anfragen direkt durch Varnish

Es gab jedoch ein paar Problemstellungen, die wir nicht auf dem Schirm hatten. Angefangen hat dies mit der Verteilung der Installation: Varnish und Magento werden aus Sicherheitsgründen nicht auf dem selben Server betrieben.

Diese Trennung sieht Magento 2 explizit in der Konfiguration in der Administrationsoberfläche vor, doch scheint das nicht vollends durchdacht zu sein. Hier ergaben sich bei ersten Tests grundsätzliche Probleme, die durch eine vollständige Berücksichtigung leicht lösen lassen:

  • Trägt man im Magento als Basis-URL die Domain ein, auf der Magento tatsächlich läuft (in unserem Fall die Subdomain des Magento-Hosts, während die eigentliche Domain inklusive www-Alias auf den Varnish-Server zeigt), führt dies dazu, dass der erste Request eine Umleitung direkt zu dieser Subdomain vornimmt, der Benutzer eben jene URL im Browser sieht und auch der Datenverkehr am Varnish vorbeiläuft, da der Browser wieder direkt mit dem Magentosystem kommuniziert. Einfache Lösung: Als Basis-URL muss entsprechend die Domain des Varnish-Servers verwendet werden, was letztlich die bisherige Domain/der WWW-Alias ist
  • SSL: Varnish kann nativ mit den Quellhosts nicht mit SSL kommunizieren – entsprechend muss Magento auch konfiguriert sein, so dass die Basis-URLs ohne SSL auskommen.
  • In dieser Konstellation kam es jedoch dazu, dass keine Logins bestand hatten – es hat sich gezeigt, dass die Session-Cookies schlichtweg unter der falschen Domain angelegt wurden. Hier muss in Magento in diesem Fall explizit die Varnish-Domain als Cookie-Domain definiert werden, da sonst ggf. die Subdomain des Magentosystems als Cookie-Domain verwendet wird und so gesetzte Cookies dann schlichtweg nicht von Varnish an Magento durchgereicht werden.
  • Ein Login in die Administrationsoberfläche war trotz korrekter Cookie-Domain nicht möglich – Magento 2 verfügt hier über einen Sicherheitsmechanismus, die zuvor konfigurierte Basis-URL gegen den HTTP_HOST im $_SERVER-Array gegenprüft – in ersterem steht jedoch die Varnish-Domain, in letzterer aber die Subdomain des Magento-Servers. Somit verhindert Magento hier einen Login. Dies lässt sich jedoch leicht lösen, in dem man den HTTP_X_FORWARDED_HOST als HTTP_HOST übernimmt, sofern die Anfrage vom Varnish-Server kommt. Wir haben einen entsprechenden PHP-Dreizeiler in der .htaccess als auto_prepend_file eingebunden – so dass dieser Code ausgeführt wird, bevor Magento mit der Anfrage in Berührung kommt.

Zudem hatten wir noch einige Themen, die erst mit den Tests aufgetreten sind:

  • Varnish hält statische Dateien (JS, CSS, HTML, Bilder) in der Standardkonfiguration explizit nicht vor sondern fordert diese als Bypass immer explizit vom Quellsystem an. Da wir in unserem Projekt jedoch davon ausgehen können, dass diese wirklich statisch sind, haben wir diese Filterung in der VCL-Datei ausgeklammert, somit werden diese Dateien von Varnish bedient – was einen deutlichen Schub in der Cache-Ausbeute gebracht hat.
  • Der Zahlungsanbieter Payone aktualisiert per Webhook den Status einer Zahlung – dies hatten wir zu Beginn nicht auf dem Schirm. Die hier aufgerufenen URL muss natürlich auch auf der Whitelist stehen, so dass Varnish diese immer direkt zu Magento durchreicht. Durch Anpassungen in der VCL stehen jetzt auf unserer Whitelist (Auszug):
    • Alle URLs der Administrationsoberfläche
    • Die Payone-Webhook-URLs
    • Weitere APIs, die von Händlersystemen aufgerufen werden
    • Unsere eigene Health-Check-URL, die etwas von [updown.io](http://updown.io) zur Überwachung genutzt wird
    • Alle Sub-URLs /customer, so dass alle kundenkonto-relevanten Seiten nicht zwischengespeichert werden
    • Alle Sub-URLs den Warenkorb, Versanddaten und die Magento-internen APIs betreffend (/rest)
    • Alle URLs unterhalb von /catalogsearch – die Suche wird bereits auf dem Magentosystem optimiert durch die Verwendung von Sphinx bzw. ElasticSearch, zudem ist die Varianz der verschiedenen Suchen einfach zu groß, als dass sich der Aufwand einer Vorhaltung auf Varnish lohnen würde.

Nach dem erfolgreichen Test der Integration gingen wir abgestimmt mit dem Kunden und dem Provider, der die DNS-Änderungen vornimmt live. Das ging etwa vier Stunden gut – und dann lieferte Varnish nur noch eine Wartungsseite aus. Uns war im ersten Moment auch nicht klar, woran das lag. Der Magento-Server war vom Varnish-System schlichtweg nicht mehr zu erreichen, ein direkter Zugriff von unserem Netzwerk aus war jedoch problemfrei.

Das hat im ersten Schritt erst einmal dazu geführt, dass wir die VCL-Datei um einen Director-Eintrag erweitert haben – wir haben hier einen Fallback-Mechanismus integriert. Varnish nutzt hierbei einen Health-Check: Alle 15 Sekunden prüft Varnish, ob das Magento-System erreichbar ist. Ist dies nicht der Fall, werden alle Requests von Varnish an ein anderes System weitergeben. In unserer Konfiguration sieht das bei einem Ausfall der direkten Netzwerkverbindung zwischen Varnish und Magento dann so aus, dass die Anfragen über einen anderen Server geleitet werden, der lediglich durch einen Apache-Reverse-Proxy die Anfragen an das Magento-System weiterleitet.

Interessant hierbei: Nach etwa einer Stunde war die Kommunikation zwischen Varnish und Magento wieder möglich – das Problem trat jedoch immer wieder auf. Am Ende haben wir festgestellt, dass wir einfach die Rechnung ohne den Hoster gemacht haben: Sowohl der Managed Server, auf dem Magento betrieben wird, als auch der Root-Server auf dem Varnish läuft werden zwar vom selben Anbieter betrieben – jedoch handelt es sich um unterschiedliche Produkte mit verschiedenen Zuständigkeiten. Doch was war passiert? Dadurch, dass nun der gesamte Datenverkehr an den Magento-Server von einem System kam (Varnish) hat die Netzwerkinfrastruktur des Rechenzentrums reagiert und die Quelle als vermeintlichen Bot/Angreifer identifiziert und ihn zeitweise netzwerkseitig blockiert. Die Aufnahme in eine Whitelist war nur unserer Hartnäckigkeit zu verdanken – interessant hierbei: Die Idee, den Root-Server in der Cloud-Umgebung für einen vorgeschalteten Varnish-Server zu nutzen kam sogar vom Anbieter selbst.

Mit dem Onlinegang von Varnish hat sich ausgerechnet im Netzwerk unseres Kunden PUKY zudem ein interessantes Phänomen ergeben: Sie sahen auf der Webseite das Hauptmenü mit den Kategorien nicht mehr, der Seiteninhalt selbst war jedoch da. Am Ende hat sich herausgestellt, dass dieses Menü von Magento in der Layoutkonfiguration von Haus aus mit einer TTL von einer Stunde versehen wird, was in der Kombination mit Varnish dafür sorgt, dass Magento hier selbst ein ESL von Varnish im HTML ausliefert, was normalerweise von Varnish durch den richtigen HTML-Schnipsel ersetzt wird. Allerdings: Ausgerechnet bei unserem Kunden war noch der alte DNS-Eintrag gecached – der Traffic ging also explizit nicht über Varnish und somit wird das ESL auch nicht ersetzt, was für ein leeres Menü gesorgt hat. Die Entfernung der TTL (die bei einem vorgeschaltetem Varnish auch nur bedingt sinnvoll ist) bringt das Menü auch bei direktem Zugriff auf Magento wieder zum Vorschein.

Fazit

Inzwischen läuft die Installation mehrere Monate – ohne dass nach den Startschwierigkeiten weitere Probleme aufgetreten sind.

Dies ist die vereinfachte Darstellung – vor dem Varnish-Cache läuft auf dem Server noch ein Apache, der die Kommunikation zum Client hin per SSL absichert, jedoch die Daten unverschlüsselt an Varnish übermittelt, da dieser mit SSL nicht betrieben werden kann.

Die DNS-Umstellung hat erwartungsgemäß einige Tage benötigt, bis die Änderung auch bei allen Providern berücksichtigt wurde. Nun bedient Varnish etwa 80 bis 85 % aller Anfragen, das ist innerhalb unserer Zielsetzung – die restlichen 15 bis 20 % der Anfragen, die noch zu Magento durchgereicht werden, sind auf der Whitelist und durchaus als Bypass beabsichtigt.

Da wir uns innerhalb des Projektes immer noch im Ramp-Up befinden (momentan können Fahrzeuge etwa für Endkunden nur aus Deutschland heraus bestellt werden) haben wir uns mit Varnish und vor allem der Verteilung auf getrennte Systeme deutliche Vorteile für eine spätere Skalierung geschaffen.