Nudge am 17.10.2009

Memcache – gib mir Speed!

in PHP, Tipp, Uncategorized, Web | Tags: Apache, Cache, memcache, memcached, PHP, Server, SQL

Ich habe diese Woche den memcache-daemon memcached in Version 1.4.2 mit der PHP-extension memcache ausprobiert und bin einfach nur begeistert.

Was ist denn Memcache?

Memcache ist ein Dienst, der es erlaubt, Daten im Arbeitsspeicher vorzuhalten. Daneben erlaubt er es auch noch, dies über mehrere Server zu verteilen, also ein richtiges Speicher-Netzwerk aufzubauen. Und Arbeitsspeicher ist eine der schnellsten Zugriffsformen auf Daten, die wir zur Zeit haben. Memcache kann durch das Zwischenspeichern von Daten, die sonst mühsam aus anderer Stelle geholt werden müssen, das Leben leichter und angenehm schneller gestalten.

Dazu muss man sagen, dass ja Webanwendungen von Seitenaufruf zu Seitenaufruf jeweils neu initialisiert werden müssen. Als Benutzer in einer Sitzung (Session) ist einem das oft nicht bewusst, aber es kostet den Server bei jedem Klick eine entsprechende Zeit, die Session zu restaurieren und die Daten wieder so herzustellen, damit die nächste Aktion ausgeführt werden kann. Die Sessions werden in einer Datei auf dem Server abgelegt. Das heißt also auch Festplattenaktivität pro Klick. Das ist doof. Noch dümmer, dass man nicht wirklich viel in solch eine Session legen kann und sollte. Je größer das Session-File, desto langsamer wird jeder einzelne Aufruf. Dinge also, die nicht wirklich essentiell sind, holt man also immer wieder neu aus einer Datenbank oder anderen Quellen.

Daneben gibt es noch Session-unabhängige Daten. Bei einem News-Portal also die aktuellen Beiträge zum Beispiel. Doch die sollte man nicht jedem der Millionen von Nutzern in die Session legen. Dabei sind diese ständig gefragt, was macht man also damit? Memcache ist hier die richtige Lösung. Mit Memcache lassen sich diese nutzerunabhängigen Daten genau einmal speichern, aber für jeden Nutzer in hoher Geschwindigkeit bereitstellen. Das macht richtig Spaß.

Was geht in Memcache rein?

Das Beste ist: Memcache bildet nur eine simple Zuweisung von Key-Value-Paaren ab. Unter einer Zeichenkette als Schlüssel, der maximal 250 Zeichen Länge betragen darf, werden einfache Zeichenketten als Werte hinterlegt. Die Werte dürfen maximal 1 MB Größe haben. Was man darin speichert, ist tatsächlich vollkommen egal. Man kann also gezielt eine Datenbank beschleunigen oder aber auch Template-Engines entlasten, also ganze HTML-Vorlagen ablegen, vielleicht auch schon komplett mit allen Inhalten gerendert. Ebenso lassen sich die Einträge unabhängig von deren Nutzung befüllen, zum Beispiel nachts, wenn die Last der Server geringer ist. Tagsüber stehen dann z.B. aggregierte Daten zum schnellen Abruf bereit.

Die PHP-Schnittstelle ist dazu mit einer automatischen Serialisierung implementiert. Man kann also einfach komplexere Objekte und Arrays einfach hineinwerfen – und so kommen sie auch heraus. Einzig und allein Pointer wie Dateizeiger oder auch (Datenbank-)Verbindungen sollte man nicht hineinwerfen, denn Ressourcen können im Allgemeinen nicht richtig wiederhergestellt werden. Jeder Eintrag kann dabei on-the-fly mit zlib komprimiert werden, sollte es sich um größere Datenmengen handeln, um im 1-MB-Bereich zu bleiben oder einfach aus Speichergründen. Der Dienst memcached wird außerdem mit einer Option -m für die Gesamtspeichergröße

1
/usr/local/bin/memcached -d -m <Anzahl der MB> -p 11211 -l 127.0.0.1 -u nobody
/usr/local/bin/memcached -d -m <Anzahl der MB> -p 11211 -l 127.0.0.1 -u nobody

gestartet, sodass bei -m 2048 maximal 2 GB des Arbeitsspeichers belegt werden. Nicht zuletzt wird beim Zwischenspeichern ein Zeitlimit gesetzt, wie lange der Cache-Eintrag gültig sein soll.

Anwendung am Beispiel www.ebrosia.de

Zunächst habe ich ein Profiling unserer Startseite gemacht. Die sollte wohl sehr häufig aufgerufen werden. Insgesamt bietet allein diese Seite Anlass für über 100 Datenbank-Abfragen, wenn man alles streng nimmt. Also ist das mein erstes Optimierungsgebiet gewesen. Es gibt natürlich Abfragen, die man besser nicht zwischenspeichert, wie zum Beispiel den Warenkorb des Benutzers – der sollte immer auf die Sekunde aktuell sein. Die Zusammenfassung des Warenkorbs (x Artikel mit y Gesamtpreis) wandert also am besten in die Session: Verbraucht nicht viel Platz, ist nutzerabhängig und wird bei einer Änderung am Warenkorb neu errechnet, also eher selten.

Bei der Entscheidung, ob ganze Templates optimiert werden sollen, habe ich mich dagegen entschieden: Die Mehrzahl der Seiten bietet für eingeloggte Nutzer individualisierte Inhalte, dazu sind die Templates nicht wirklich die Zeitfresser, da reines HTML und PHP zum Einsatz kam, also keine Layout-Engine verwendet wird. Die Seiten leben eher von den kleinen Abfragen an die Datenbank. Hier und da geht es um Links zu einem Artikel, um einen Preis, einen Artikelnamen. Speziell zu Wein kamen noch das Land, ein Gebiet oder eine Rebsorte hinzu.

Wenn man einen Shop betreibt, dann sind die Produktdaten das A und O, gerade bei etwa 1000 Artikeln. In fast jeder Shop-Seite, besonders auf der Startseite, kommen mehrere, auch wechselnde Artikel vor. Jeder Artikel hat einen Namen, einen Preis, einen Rabatt und eine Lieferbarkeit. Diese Daten werden eventuell mehrfach pro Sekunde abgerufen, aber vielleicht nur einmal im Monat geändert, zum Beispiel bei einer Rabatt-Aktion. Setzt man hier ein Zeitlimit von 10-20 Minuten an, sollten also geschätzte 90% der Aufrufe mit Memcache abgefangen werden, ohne dass die Aktualität leidet. Ich habe mich für ein kurzes Zeitfenster von 10 Minuten entschieden, um im Fehlerfall flexibler zu bleiben.

Also habe ich dort angesetzt. Memcache bildete eine Art Zwischenschicht zwischen dem Shop als Gestaltung und dem Datenbank-Backend. Letztlich konnte ich so mit nur einer kleinen Memcache-Implementierung im Backend gleich sehr viele Shop-Seiten verbessern, die auf die optimierten Funktionen mit den SQL-Abfragen zugreifen. Mit der Zwischenspeicherung von Preisen, Rabatten, Namen von Artikeln und Kategorien habe ich die Startseite auf ein durchschnittliches Volumen von etwa 5 SQL-Anfragen gedrückt. Diese Anfragen gehen auf die stetige Erneuerung von Cache-Einträgen wie auch das Rotieren von Produkten in Teasern zurück. Letztlich betrug die Zeit der Auslieferung der Startseite nur noch 35-50 Millisekunden (PHP-Start bis PHP-Ende). Seiten ohne SQL-Inhalte bewegten sich gleichen Zeitrahmen. Man konnte demnach davon ausgehen, dass das Caching die Belastung des MySQL-Servers auf ein Minimum reduziert hatte.

Im zweiten Schritt ging es dann an Artikel- und Kategorie-Seiten. Diese waren schon weitgehend durch den vorigen Schritt entlastet. Es waren nur noch wenige Handgriffe zu tun, um auch hier an die gleichen Rendering-Zeiten zu gelangen. In Frieden ließ ich den individuellen Konto-Bereich der Kunden. Hier wäre auch das Optimierungspotenzial geringer. Mit dem investierten Aufwand bin ich bereits weit genug gekommen für eine erste Runde.

Rollout auf dem Webserver

Libevent installiert und memcache kurz kompiliert, schon konnte es losgehen. Der Dienst startete angenehm einfach. Das PHP-Modul ist mit einem einfach phpize, configure und make ebenso leicht aufgesetzt. Jetzt nur noch das Modul in der php.ini fest eingetragen und nach einem kurzen Neustart des Apache sagte mir ein Testskript “Ja, es ist angerichtet.” 🙂

Der Shop wurde synchronisiert, und schwupps, kamen erste Ergebnisse im Cache an. Das Gefühl eines schnelleren Arbeitens war wirklich omnipräsent, das fühlte sich gleich gut an. Nach einigen Stunden Laufzeit hatte Memcache eine Trefferquote von etwa 87% zu verzeichnen, und die Last auf dem DB-Server war damit drastisch gefallen. An die Optimierung weiterer Bereiche kann nun Schritt für Schritt herangegangen werden. Nagios zeichnet flachere Kurven, und der Admin ist zufrieden. Jetzt kann Weihnachten kommen. 🙂


Das mark ich mir: Alltagz Mr Wong Yigg Del.icio.us Yahoo MyWeb Blinklist Google folkd
 

3 responses to “Memcache – gib mir Speed!”

  1. Nudge says:

    Das ist ja saugeil, dieses APC…ich kann gar nicht in die Mittagspause…Faktor 2 ist bei uns real, die Startseite unseres Shops im Intranet liegt jetzt bei 21-25ms, wenn der memcache alle Inhalte findet… 🙂

  2. Seebi says:

    Hi nudge, du alter opti-mizer – da war wir uns montag drüber unterhalten hatte:

    hier ein aktueller artikel über apc, was ähnlich eAccel ist
    http://www.phpgangsta.de/481

    gruss und bis heute abend 🙂

  3. DonTermi says:

    So ein ähnliches Verfahren habe ich in meiner rapid Engine in der MySQL Klasse integriert. Allerdings wird da nicht memcache für das Speichern im Arbeitsspeicher sondern das Dateisystem. In einem Ordner Cache werden dann die zwischengespeicherten Ergebnisse gelagert und bei Bedarf abgerufen. Natürlich brauch man sich nicht streiten das der Arbeitsspeicher das schnellere Medium von beiden ist.
    Aber leider kann man memcache nicht auf normalen Webhostings einsetzem da dieses Feature dort leider nicht installiert ist 🙁

Leave a Reply

Your email address will not be published. Required fields are marked *