Federleichtes Servieren
Artikel erschienen in Swiss IT Magazine 2004/15
Ein bekanntes Szenario: Auf Slashdot oder im Heise-Newsticker wird eine interessante Website vorgestellt. Wenn man auf den Link klickt, passiert nichts oder nur sehr langsam etwas. Denn auf dem Zielserver stehen sowohl der Apache-Webserver als auch das Datenbanksystem unter Vollast. Die Hardware ist am Anschlag. Diesem Problem kann man auf verschiedenen Wegen begegnen: Die Hardware um noch mehr Hardware erweitern oder das Setup optimieren. Während weitere Webserver und ein Load Balancer nicht gerade günstig sind, lassen sich mit einem simplen Tausch des http-Daemons noch ein paar Dutzend Besucher mehr pro Sekunde aus dem Server herausholen.
Um die Problematik von hoher Last (grosse Anzahl von gleichzeitigen Anfragen) im Zusammenhang mit dem Apache-Webserver verstehen zu können, muss man zuerst dessen Funktionsweise beleuchten. Startet man den Apache, wird zuallererst der Master-Prozess gestartet, der die Konfigurationsdatei einliest und seinerseits beginnt, eine gewisse Anzahl Child- oder Worker-Prozesse zu starten (Anzahl gemäss «StartServers»-Direktive in httpd.conf). Die warten auf Anfragen und beantworten diese. Sie tun das so lange, bis sie eine gewisse Anzahl Requests beantwortet haben (MaxRequestsPerChild) oder sie nicht mehr benötigt werden, beispielsweise, weil zu viele Child-Prozesse vorhanden sind. Da jeder Worker-Prozess nur eine Anfrage auf einmal beantworten kann, müssen, um mehr gleichzeitige Anfragen beantworten zu können, mehr Prozesse gestartet werden. Jeder Prozess, der gestartet wird, muss, bevor er betriebsbereit ist, alle konfigurierten Apache-Module laden. Zwar geht dies ziemlich schnell, doch frisst dies einerseits einige wertvolle Prozessor-Zyklen, und jeder Prozess mit den Standardmodulen und einem einigermassen schlanken mod_php verbraucht rund 3 MB an RAM. Bei 100 aktiven Child-Prozessen kommen 300 MB an RAM-Verbrauch alleine für den Apache zusammen, ohne dass irgendeine Zeile Applikationscode ausgeführt wurde. Dieser zusätzliche Ressourcenverbrauch kann unter anderem dazu führen, dass der Datenbankserver zu wenig Leistung zur Verfügung hat, wodurch die Antwortzeit länger wird und der Apache noch mehr Child-Prozesse erzeugt, die wiederum mehr Ressourcen verbrauchen. Somit ist es nur noch eine Frage der Zeit, bis die Maschine zusammenbricht.
Ein Anfang, um dieses Problem zu beheben, wäre, wie in InfoWeek 09/2004 bereits vorgestellt, die Apache-Prozesse leichter zu machen und PHP über FastCGI auszulagern. Dies hätte den Vorteil, dass der httpd die Prozesse schneller erzeugen kann und diese keine unnötigen Ressourcen fressen, wenn beispielsweise nur statische Daten wie Bilder ausgeliefert werden müssen. So kann zwar das Problem der zusätzlichen Prozesse verkleinert werden, es verschwindet aber nicht. Weitere Geschwindigkeitsvorteile lassen sich erst mit Single-Prozess-Webservern wie Zeus, thttpd oder lighttpd erreichen, von denen InfoWeek letzteren einmal unter die Lupe genommen hat.
lighttpd (www.incremental.de/pro ducts/lighttpd/) wurde von Jan Kneschke entwickelt und besonders auf ein günstiges Lastverhalten hin optimiert, um ihn für webbasierte Chats oder Ad-Server verwenden zu können. Dies wird vor allem durch das Weglassen wenig benötigter Funktionen sowie durch das Single-Prozess-Design erreicht. Trotzdem kann die Software auf eine beachtliche Funktionalität zurückgreifen, die für das Alltagsgeschäft in der Regel problemlos ausreicht:
Unterstützung für HTTP 1.0 und 1.1 sowie SSL
CGI 1.1
FastCGI mit Load-Balancing
Virtual-Hosting
Komprimierte Datenübertragung
Basic- und Digest-Authentifizierung gegen verschiedene Back-ends, u.a. LDAP
URL-Rewriting auf PCRE-Basis
HTTP-Redirects
Besonders die FastCGI-Implementation bietet einige interessante Ansätze. So ist es nicht nur möglich, die FastCGI-Prozesse auf demselben oder einem anderen Host zu verwenden, sondern diese auch auf mehrere Server zwecks Load Balancing zu verteilen. So könnte man zum Beispiel in stark belasteten Setups nicht nur den Datenbankserver auslagern, sondern auch die Scriptverarbeitung auf einen oder mehrere Server verteilen. Fällt einer aus, merkt dies lighttpd automatisch und lässt die übrigen Maschinen die Arbeit ausführen. Die ausgefallene Maschine wird dann kontinuierlich getestet, und sobald sie wieder erreichbar ist, wieder in den Arbeitsprozess miteinbezogen.
Dadurch, dass man zusätzliche Software entweder über CGI oder FastCGI anbinden muss, verschwinden auch die meisten Probleme, die ein forkendes Betriebsmodell wie beim Apache erforderlich machen. Stürzt beispielsweise ein PHP-Prozess ab, fängt dies die FastCGI-Schnittstelle ab, ohne dass der Webserver beeinträchtigt wird. Da der Webserver sich im über mehrere Wochen gehenden Testbetrieb als absolut stabil erwies, muss man beim Single-Prozess-Betrieb keine Probleme befürchten. Um ganz sicher zu gehen, ist es aber möglich, den Webserver so zu konfigurieren, dass ein Master- sowie ein Worker-Prozess gestartet werden. Stürzt der Worker-Prozess ab, gehen zwar die aktuell bearbeiteten Requests verloren, worauf der Master-Prozess aber gleich wieder einen Worker-Prozess startet.
Im Vergleich zum Apache-Webserver verhält sich der lighttpd entsprechend der Architekturunterschiede auch wie Tag und Nacht. Beim Start wird ein einziger Prozess erzeugt, der in Zukunft sämtliche Anfragen entgegennimmt und verarbeitet. Dieser genehmigt sich nach dem Start gerade einmal 2 MB RAM. Bei den Stresstests mit einer 50 KB grossen HTML-Datei stieg der RAM-Verbrauch auf rund 10 MB an, ein Wert, den drei Apache-Child-Prozesse alleine verbrauchen, ohne irgend etwas zu tun. Beim Durchsatz erreicht der lighttpd ohne Datenkompression mit einem Mittelwert von 4554 Requests pro Sekunde gut dreimal mehr Requests als der Apache (1532).
Beginnt man, die Auslieferungsgeschwindigkeit zwischen Apache und lighttpd bei mit PHP generierten Seiten zu vergleichen, mag sich je nach Testaufbau auf den ersten Blick die grosse Ernüchterung einstellen. Beim ersten Versuch ging es darum, ein Script abzuarbeiten, das 10'000 MD5-Hashes pro Request erzeugt. Sowohl Apache als auch lighttpd versendeten auf die erste Nachkommastelle genau gleich viele Requests pro Sekunde. Dies, da die Berechnung von MD5-Hashes in der CPU stattfindet, die damit den limitierenden Faktor darstellt. Somit ist der lighttpd gar nicht in der Lage, sein Durchsatz-Potential auszuspielen. Vergleicht man aber den Ressourcenverbrauch der beiden Webserver, stellt man erhebliche Unterschiede fest: Bis zum Ende des Tests (./ab –n 1000 –c 1000) nahm der Apache rund 360 MB RAM in Beschlag, der lighttpd kam zusammen mit 50 PHP-Instanzen mit 140 MB aus. Diese Differenz mag zwar bei den heutigen RAM-Preisen keinen grossen Unterschied mehr machen, doch sind diese 220 MB sicherlich besser für Datenbank- und Harddisk-Caches investiert, die wieder dabei helfen, die Ausführungszeit der eigentlichen Applikationen niedrig zu halten. Dies bemerkt man besonders, sobald bei der Seitenerzeugung Datenbankabfragen in Spiel kommen. Die Auswirkungen sind sichtbar, sobald man phpMyAdmin zum Test (./ab –n 500 –c 100) dazu nimmt: Apache liefert im Mittel 8,59 Seiten pro Sekunde aus, lighttpd dagegen 10,74.
Als Testsystem ist ein IBM xServer 225 mit 2 GHz Xeon-CPU, 512 MB RAM und einem SCSI-RAID-1-Array mit Suse Linux Enterprise Server 9 und Kernel 2.6.5 zum Einsatz gekommen. Sämtliche Tests wurden mit Apaches Benchmark-Utility ab und deaktiviertem Swap-Space ausgeführt. Verwendet wurden jeweils die aktuellsten Softwareversionen: Apache 1.3.1, lighttpd 1.2.5, PHP 5.1.0-dev mit ext/mysqli, MySQL 4.1.3-beta und phpMyAdmin 2.6.0-rc1.
Die Auslieferungsgeschwindigkeit von statischen Seiten wurde mit ./ab –n 1000 –c
Auslieferungsgeschwindigkeit von Seiten