Seit den Weihnachtsfeiertage läuft dieses Blog ja nun auf einem neuen (V)Server, der deutlich leistungsstärker als die vorherige Version ist. Das liegt nicht nur an der schnelleren Maschine, sondern auch an der Tatsache, dass man es bei so einem Umzug ja nochmal mit den ganzen Konfigurationsdateien zu tun bekommt.
Das angesammelte Wissen würde ich gerne mit euch und meinem zukünftigen serverinstallierenden Ich teilen ;-)
Apache Worker statt Prefork benutzen
Standardmäßig ist Apache in der „prefork“ Version installiert. Das bedeutet, dass Apache für die Bearbeitung von Anfragen jeweils einen eigenen Prozess startet. Stabil und zuverlässig und heutige Server mit riesigen Mengen Arbeitsspeicher sollten auch mit hunderten Prozessen kein Problem mehr haben, ja wären da nicht die Einschränkungen eines Vservers. In /proc/user_beancounters gibt es eine Größe namens kmemsize, die den zur Verfügung stehen Kernelspeicher angibt. Langer Rede kurzer Sinn: hier gibt’s meistens nicht genug von um hunderte Prozesse zu starten und wenn kmemsize regelmäßig überschritten wird, dann killt der Host Prozesse im Vserver bis nur noch init übrig bleibt. Nach außen hin ist euer Vserver dann praktisch tot und muss neugestartet werden.
Deshalb habe ich den Apache auf „worker“ umgestellt (apt-get install apache2-mpm-worker), so dass statt Prozessen Threads für die Anfragen gestartet werden. Das ist in meinen Benchmarktests zwar minimal langsamer, aber kmemsize überschreitet so nicht mehr das Limit.
Die richtige Einstellung von MaxClients
Vermutlich um auch auf den kleineren Vserver Varianten nichts zu überlasten sind die Werte für MaxClients, etc im Apache auf 10 voreingestellt. Das bedeutet, dass maximal 10 Anfragen gleichzeitig bearbeitet werden können. Auf einem Server, der mehr als eine belebte Domain hostet ist das natürlich viel zu niedrig gegriffen und mit 3 GB Ram und oben erwähnter „worker“ Installation bleibt auch noch genug übrig um mit höheren Werten zu experimentieren. Derzeit verwenden wir folgende Einstellung:
<IfModule mpm_worker_module>
StartServers 2
MaxClients 150
MinSpareThreads 25
MaxSpareThreads 50
ThreadsPerChild 25
MaxRequestsPerChild 0
</IfModule>
Das bedeutet es starten 2 Server Prozesse, die jeweils 25 Threads haben dürfen. Bis zu 150 Threads können gestartet werden und es werden immer zwischen 25 und 50 Threads in Bereitschaft bleiben um Anfragen zu beanworten. Mit dieser Einstellung kommt es jedenfalls zu keinen Verzögerungen beim Aufruf der verschiedenen Domains mehr.
Apache mod_deflate
Bevor ich zu den Einstellungen für PHP komme, noch kurz mod_deflate erwähnen. Damit ist es möglich statischen Content automatisch zu komprimieren. Das bietet sich speziell für Javascript und Stylesheets an. Überhaupt ist Kompression eine gute Idee, denn der Download einer Datei dauert oft viel länger als die Bereitstellung und je schneller der Client bedient ist, desto schneller kann ein neuer abgefertigt werden.
Derzeitige Konfiguration:
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml
<Files *.css>
SetOutputFilter DEFLATE
</Files>
<Files *.js>
SetOutputFilter DEFLATE
</Files>
</IfModule>
Apache mod_fcgid
Aus Sicherheitsgründen lassen wir PHP5 schon seit längerem als CGI mit mod_fcgid und suexec laufen. Vorteile davon sind die erhöhte Sicherheit durch abgetrennte PHP-Prozesse für jede Domain, eigene PHP-Konfigurationen pro Domain (z.B. verschiedene Module, Versionen, etc) und keine Probleme mehr mit den Zugriffsrechten auf die per FTP hochgeladenen Dateien (PHP läuft unter dem gleichen Benutzer wie FTP). Nachteile sind die etwas komplizierte Konfiguration (jeder virtuelle Host braucht eine eigenen Wrapper für PHP) und kleine Performanceeinbußen gegenüber PHP als Apachemodul.
Sicherheit ging bei uns nach mehreren Hacks auf installierte Blogs vor. Der Verdacht war damals, dass eine alte WordPressversion es irgendwie geschafft hat alle anderen Versionen ebenfalls zu „infizieren“ (Werbelinks im Footer & RSS). Klar kann man das auch über Safemode und Open-Basedir regeln, aber angeblich funktioniert Apache als „worker“ auch nicht richtig mit PHP in Modulversion. Ergo mod_fcgid. Installationsanleitungen gibt’s dazu zu Hauf im Netz, brauche ich also nicht zu wiederholen. Die Konfiguration für das Modul lautet bei uns:
<IfModule mod_fcgid.c>
AddHandler fcgid-script .fcgi
IPCConnectTimeout 20
IPCCommTimeout 120
DefaultMaxClassProcessCount 15
</IfModule>
Die Konfiguration für die Wrapper lautet wie folgt:
PHP_FCGI_CHILDREN=0
PHP_FCGI_MAX_REQUESTS=5000
Setzt man den ersten Wert auf 0 oder gar nicht verwaltet PHP selbst die Anzahl der Prozesse pro Wrapper und bekommt das in meinen Tests gut genug hin. Nach 5000 Zugriffen bzw. einer gewissen Idle-Zeit wird so ein Prozess dann wieder abgeschossen. PHP-Prozesse brauchen allerdings fast noch mehr Speicher als der Apache … also Vorsicht, evtl. muss man hier doch niedrigere Maximalwerte (DefaultMaxClassProcessCount) setzen um bei einem Vserver nicht die kmemsize zu überschreiten.
Andere Optimierungen
Bei WordPress bewirkt WP-Supercache absolute Wunder. Nachteile konnte ich bisher noch nicht feststellen. Natürlich sollte man bei MySQL auch noch den Querycache aktiviert haben und einen Opcode-Cache für PHP installieren. Bei uns läuft hier Eaccelerator … wegen Fcgid nicht ganz so effizient wie mit PHP als Modul, aber es läuft damit trotzdem spürbar schneller. Auf einem Vserver sollte man hier darauf achten Eaccelerator nicht zu viel Shared Memory zuzuweisen … bei uns muss der Cache mit 4 MB (eaccelerator.shm_size = „4“) auskommen, sonst ist schnell die Grenze vom Vserver erreicht und die PHP-Prozesse beenden sich wahllos und geben 500-er Fehler zurück.
Benchmarks
Das Endergebnis sieht vielversprechend aus. Konnte die vorherige Konfiguration den Server nur zu 30% auslasten, geht das jetzt auch bis 100% ;-)
Apache-Bench (-c 50 -n 1000, jeweils schnellster von 3 Testläufen) vom alten Vserver aus sagt für Zugriffe auf das Stylesheet:
Server Hostname: www.sebbi.de
Server Port: 80Document Path: /wp-content/themes/sebbi_illustrative/style.css
Document Length: 15706 bytesConcurrency Level: 50
Time taken for tests: 1.411070 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 16134940 bytes
HTML transferred: 15821840 bytes
Requests per second: 708.68 [#/sec] (mean)
Time per request: 70.553 [ms] (mean)
Time per request: 1.411 [ms] (mean, across all concurrent requests)
Transfer rate: 11165.99 [Kbytes/sec] receivedConnection Times (ms)
min mean[+/-sd] median max
Connect: 0 14 3.7 16 24
Processing: 7 52 15.7 52 297
Waiting: 3 17 3.7 18 48
Total: 7 67 15.2 68 303Percentage of the requests served within a certain time (ms)
50% 68
66% 68
75% 68
80% 68
90% 69
95% 69
98% 73
99% 85
100% 303 (longest request)
Blogstartseite (mit WordPress Supercache):
Server Hostname: www.sebbi.de
Server Port: 80Document Path: /
Document Length: 53536 bytesConcurrency Level: 50
Time taken for tests: 4.719902 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 54115708 bytes
HTML transferred: 53787752 bytes
Requests per second: 211.87 [#/sec] (mean)
Time per request: 235.995 [ms] (mean)
Time per request: 4.720 [ms] (mean, across all concurrent requests)
Transfer rate: 11196.63 [Kbytes/sec] receivedConnection Times (ms)
min mean[+/-sd] median max
Connect: 0 28 164.3 20 3024
Processing: 16 192 208.2 95 1782
Waiting: 3 40 71.6 25 703
Total: 16 220 261.5 116 3136Percentage of the requests served within a certain time (ms)
50% 116
66% 123
75% 208
80% 385
90% 471
95% 693
98% 913
99% 1039
100% 3136 (longest request)
Ein anderes Blog ohne Supercache (und nur 200 Requests, sonst dauert es so lange):
Server Hostname: www.cappellmeister.com
Server Port: 80Document Path: /
Document Length: 66123 bytesConcurrency Level: 50
Time taken for tests: 23.789681 seconds
Complete requests: 200
Failed requests: 0
Write errors: 0
Total transferred: 13302000 bytes
HTML transferred: 13224600 bytes
Requests per second: 8.41 [#/sec] (mean)
Time per request: 5947.420 [ms] (mean)
Time per request: 118.948 [ms] (mean, across all concurrent requests)
Transfer rate: 546.04 [Kbytes/sec] receivedConnection Times (ms)
min mean[+/-sd] median max
Connect: 0 3 8.1 0 31
Processing: 324 5296 4250.2 4102 22791
Waiting: 314 5233 4251.1 4055 22759
Total: 324 5300 4252.1 4102 22808Percentage of the requests served within a certain time (ms)
50% 4102
66% 5804
75% 7321
80% 7959
90% 10646
95% 15701
98% 18581
99% 20025
100% 22808 (longest request)
Im Vergleich dazu andere Blogs und Webseiten (ebenfalls -c 50 -n 200):
www.basicthinking.de/blog/:
Requests per second: 5.43 [#/sec] (mean)stylespion.de/:
Requests per second: 10.05 [#/sec] (mean)www.smashingmagazine.com/:
Requests per second: 44.17 [#/sec] (mean)www.techcrunch.com/:
Requests per second: 45.54 [#/sec] (mean)www.nerdcore.de/wp/:
Requests per second: 68.75 [#/sec] (mean)www.spiegel.de/:
Requests per second: 79.86 [#/sec] (mean)www.netzpolitik.de/:
Requests per second: 81.92 [#/sec] (mean)www.spreeblick.com:/
Requests per second: 119.82 [#/sec] (mean)www.golem.de/:
Requests per second: 147.00 [#/sec] (mean)www.heise.de/:
Requests per second: 155.53 [#/sec] (mean)www.lawblog.de/:
Requests per second: 200.94 [#/sec] (mean)
Große Webseiten und Blogs nutzen wohl ganz offensichtlich auch irgendwelche Caches für ihren dynamischen Content, einige wie Basicthinking verzichten jedoch darauf. Naja, jedenfalls bleibt festzustellen, dass man mit einem gut konfigurierten Vserver für 30 Euro im Monat durchaus mit den großen mitspielen kann ;-)