Skalierung einer PHP Applikation

12. Mai 2007

Die Kosmonauten, mit denen ich zu Beginn meiner Freelancer-Zeit schon einmal zusammen gearbeitet habe, kontaktierten mich zu Beginn des letzten Wochenendes mit der eiligen Bitte, mir die Performance einer von ihnen entwickelten Partyseite einmal genauer anzuschauen.

Der Grund war, dass die frisch gelaunchte Seite Eraffe.de ab einer Last von 250 Usern die gleichzeitig online waren, extrem langsam wurde bzw. den Server fast komplett lahm legte. Die Seite ist in PHP mit Mysql Anbindung umgesetzt und lief auf Mysql 5.0 und einem Apache 2.2 mit PHP 5.2. Zugrunde lag eine Datenbank mit Daten von ca. 15.000 Usern.

Da sowohl die Entwickler als auch das Team der Community davon ausgingen, dass das Problem mit einer Erweiterung der Server Architektur relativ einfach zu lösen war, waren zum Zeitpunkt meiner Beratung schon neue Server unterwegs. So konnten wir über 4 zusätzliche leistungsstarke Server verfügen, von denen drei als Appserver (Apache 2.2, PHP 5.2) und einer als reiner DB-Server vorgesehen waren.

Meine Arbeit bestand darin, den Entwickler sowie den Serveradmin der Kosmonauten zu beraten und teilweise helfend in die Programmierung einzugreifen. Erste Anhaltspunkte waren die per Skript gemessenen Parsezeiten von einzelnen Programmteilen und die hohe CPU Last die Mysql zog.

Nachdem ich den Code kurz überflogen hatte um mir einen Überblick zu verschaffen begannen wir damit,eine Kopie der Seite auf einen externen Server zu legen. Dieser war noch etwas leistungsschwacher, aber durchaus geeignet um die Anwendung zu Testzwecken darauf laufen zu lassen. Auf dem Liveserver war dies nicht möglich, da sich auch Nachts noch eine größere Anzahl Benutzer darauf befand.

Server Software: Als ersten Schritt wechselten wir von Apache 2.2 auf den weniger speicherintensiven Lighttpd und die neusten Mysql/PHP Versionen sofern noch nicht aktuell. Zudem wurde auf PHP-Ebene Opcode-Caching durch APC aktiviert.

Code Profiling: Um den Flaschenhals im Skript zu finden, nutzten wir zunächst die PHP Extension Xdebug. Xdebug wird einfach auf dem Server installiert und man kann per Profiler genau sehen, wo sich der oder die Flaschenhälse im PHP Teil der Anwendung befinden. Xdebug benötigt auch noch zusätzliche Serverressourcen, deshalb sollte man ihn nicht ständig laufen lassen. Ist aber auch gar nicht nötig, da man innerhalb kürzester Zeit große Datenmengen zum Profiling erhält. Diese Daten können dann mit WinCacheGrind oder KCacheGrind (KDE) analysiert werden und zeigen bis zur letzten ausgeführten Funktion genaue Daten zur Parsezeit und Speicherintensität an.

Query Analyse: Das Profiling ergab, dass sich unser Flaschenhals bei den Mysql Queries befand. Um die langsamen Queries rauszufiltern kann man am Mysql Server das Slow Query Log aktivieren.

Daraus ergab sich, dass bei unserer Anwendung die Queries zur Auslese der sich auf der Seite befindlichen User (das typische Onlineuser Skript) sowie einige Schreibbefehle (sowohl INSERT als auch UPDATE) mit mehreren Datumsfunktionen die langsamsten waren.

Bei fast allen Queries lies sich noch einiges optimieren, dennoch blieb die Anwendung langsam.

Um das zu messen muss man natürlich Lasttests auf der Seite fahren, möglich durch Apache-Benchmark, Siege oder ein Windows-Tool zur Trafficanalyse.

Caching: Das Naheliegendste war nun, eine Form von Caching zu aktivieren. Da im Projekt Smarty eingesetzt wurde, bot sich das Smarty eigene Caching an, erwies sich aber als die schlechtere Lösung. Um die erste Nacht zu überstehen, deaktivierten wir auf dem Liveserver dann die Onlineuser-Anzeige und benutzten dateibasiertes Caching der Mysql Ergebnisse von einigen anderen Funktionen, deren Abfragen sich als langsam erwiesen. Am Ende der Nacht war zumindest die Startseite und einige andere häufig benutzte Funktionen der Seite wieder benutzbar.

Im Endeffekt erwies sich dann das mir bis dato unbekannte memcached als die beste Lösung zum Speichern von aus der DB gelesenen Daten. Im Laufe des Tages bauten wir es in jede Funktion ein, wo dies Sinn machte [ PHP.net/memcache ].

Damit war mein Job so gut wie erledigt, doch ein paar weitere Tipps konnte ich dem Entwicklerteam noch auf den Weg geben:

  • Keine Superklassen, die alles können (und womöglich noch statisch aufgerufen werden) benutzen.
  • Keine Angst vor einzelnen Funktionen. Sie sind schneller als statisch aufgerufene Methoden die bunt in eine Klasse geworfen werden.
  • Programmteile nur unmittelbar an der Stelle an der Sie benötigt werden nachladen (gilt für Klassen, Objekte, Funktionen, Konstanten..).
  • PHP Optimierung (Kommentare durchsehen), Lighttpd Optimierung, Apache Optimierung, Mysql Optimierung ist weiterhin nötig!

Innerhalb von zwei Tagen konnten wir so die Seite auf eine angenehme Geschwindigkeit bringen und die Last auf den Servern deutlich verringern, auch wenn der Code noch nicht optimal ist.

Eingetragen unter: Programmierung, Software, Webtipps


Kalender

Mai 2007
M D M D F S S
« Apr   Jun »
 123456
78910111213
14151617181920
21222324252627
28293031  

Neuste Einträge