h07txt-ApacheTuning - Apache 2.x , PHP, MySQL Performance Tips

Author Enrico Kern, phantom@h07.org
Version 0.1, 05 April 2004
URL http://www.h07.org
Lang DE

Dieses Dokument beschreibt das Tunen und optimieren einer Apache 2.x Installation unter Linux oder Unix-aehnlichen Distributionen und behandelt auch die Optimierung f. dynamischen Content (PHP-Caching, MySQL Tuning).

top

Inhalt

 

1 Apache Tuning
1.1.1 Optimierung durch eigenes uebersetzen
1.1.2 Apache Uebersetzungsparameter
1.1.2 Auswahl des Thread-Workers (MPM-Worker vs MPM-Prefork)
1.1.3 Atomic-Operations optimieren
1.2 httpd.conf Performance Einstellungen
1.2.1 HostnameLookups
1.2.2 Symlinks
1.2.3 .htaccess Files und AllowOverride
1.2.4 Content-Negotation
1.2.5 Memory-mapping
1.2.6 NFS-Besonderheiten
1.2.7 Prozess-erstellung, Prozesskonfiguration
1.2.8 Benchmarks
2 Dynamische Inhalte
2.1 PHP und Apache2
2.2 PHP-Caching
2.3 MySQL-Tuning
2.3.1 Einstellen der max. gleichzetigen Verbindungen
2.3.2 der Table-Cache und Key-Buffer
2.4 Mysql & Apache Temp. Files

 

top

1 Apache Tuning

 
1.1.1 Optimierung durch eigenes uebersetzen

Wenn moeglich sollte es vermieden werden binaere Apache-Packete einzuspielen (z.B ueber das Packet-System der Distribution).

Diese Packete sind meistens dazu ausgelegt auf der groesstmoeglichen Anzahl von Systemkonfigurationen zu laufen. Damit werden natuerlich keine Optmierungen wie die Unterstuetzung von erweiterten CPU-Befehlen Ihres Prozessors verwendet.

downloaden Sie die neueste Apache2-Version von httpd.apache.org und entpacken Sie das Archiv:

tar xzvf httpd-2.0.49.tar.gz
cd httpd-2.0.49

1.1.2 Apache Uebersetzungsparameter
vervollstaendigen Sie die Konfiguration mit ./configure und den Parametern der folgenden Text-Abschnitte, alle dort erwaehnten parameter muessen an das configure script im Apache-Source-Ordner uebergeben werden. (zus. zu Ihren eigenen)

1.1.2 Auswahl des Thread-Workers (MPM-Worker vs MPM-Prefork)

Die wichtigste Veraenderung am Apache2 ist das neue Thread-Modell, dieses ermoeglicht es viele Anfragen mit wenigen Prozessen zu
behandeln. Standardmaessig benutzt Apache2 jedoch das aus der 1er Version bekannte pre-fork Modell welches fuer jede Verbindung
einen neuen Prozess erstellt und somit natuerlich unmengen an Resourcen verbraucht.

Mit Apache2 gibt es sog. Multi-Processing Modules (MPM`s) wir werden uns in der Konfiguration fuer eines entscheiden muessen.

* MPM-Worker

Das Worker-Modul benutzt mehrere Kind-Prozesse wo jedes Kind viele Threads erstellt. Jeder Thread behandelt eine Verbindung.
Dieses Modul ist vorallen fuer High-Traffic Server zu empfehlen weil es besser mit dem zur verfuegung stehenden Speicher umgeht
als das prefork Modul.

* MPM-Prefork

Das Prefork-Modul benutzt mehrere Kind-Prozesse wo jeder "EINEN" Thread hat. Jeder Prozess behandelt eine Verbindung, das heist
bei 100 Clients hat man 100 Prozesse mit 100 Threads. Auf vielen Systemen ist dieses Modul schneller als der Worker
jedoch verbraucht es unmengen speicher was diesen Vorteil vernichtet. Dieses Modul wird jedoch gebraucht wenn
nicht threadsichere Apache-Module oder Bibliotheken benutzt werden. Wenn man es nicht wirklich umbedingt benoetigt sollte
man auf keinen Fall Prefork einsetzen.

fuer den worker uebergeben Sie dem Konfigurationsscript:

--with-mpm=worker

fuer das Prefork-Modull:

--with-mpm=prefork (NICHT EMPFOHLEN)

1.1.3 Atomic-Operations optimieren

Unter Linux (x86) und Solaris (Sparc/x86) bringt die Option --enable-nonportable-atomics auch noch einen kleinen schub.
Diese Option veranlasst Apache dazu bessere atomic operations (z.B integer inkrementierung) durchzuführen. Auf SPARC
Plattformen ist das messbar, unter der normalen x86-Architektur faellt das kam auf, da es lediglich alte 386er Instructions
und 486er Instructions gibt. Wobei 486er welche mit --enable-nonportable-atomics aktiviert werden immer noch
schneller sind als der 386er standard wert.


Apache kann nun uebersetzt werden (nach ./configure mit den eigenen und optimierungsparametern) was mit make && make install
geschieht. Fuer detalierte Informationen ueben den Uebersetzungs- Vorgang sollten Sie sich http://httpd.apache.org/docs-2.0/
bedienen.

top

1.2 httpd.conf Performance Einstellungen

Die wohl weitreichensten Einstellungen kann man (wie sollte es auch anders sein) in der httpd.conf vornehmen welche sich im etc Verzeichnis des Installationsprefix befindet, z.B /usr/local/apache2/etc/httpd.conf

1.2.1 HostnameLookups

HostnameLookups sollte auf jeden Fall auf "off" stehen. Dies verhindert das Apache fuer jede IP den Hostname aufloest,
was immens viel Zeit spart. Fuer statistiken kann mittlerweile jedes Stats-Programm selber aufloesen.

HostnameLookups off

1.2.2 Symlinks

Wenn Symlinks nicht wirklich gebraucht werden (wozu es eigentlich auch kaum Gruende gibt) sollte man auf keinem Fall "Options FollowSymLinks" und/oder "SymLinksIfOwnerMatch" auf Directory-Direktiven angeben. Ist so eine Option gesetzt veranlasst das Apache fuer jede
Anfrage Dateisystem-Operationen auszufuehren was bei enorm vielen zugriffen bei langsamen Festplatten immense Performance-
Einbussen bringt.

1.2.3 .htaccess Files und AllowOverride

Wenn bei Directory-Direktiven die Option "AllowOverride all" angegeben wurde, prueft Apache bei jeder Anfrage ob eine .htaccess Datei vorhanden ist, wenn nicht notwendig also abschalten.

1.2.4 Content-Negotation

Die direktive "DirectoryIndex" sollte auf keinen fall mit Wildcards bestueckt sein wie index.* index etc. sondern eine Liste aller index-Dateien enthalten die vorkommen koennen z.B "DirectoryIndex index.html index.cgi index.php" ansonsten prueft Apache jedesmal in einem komplizierten Syscall ob entsprechende Dateien vorhanden sind.

1.2.5 Memory-mapping

Unter umstaenden mapt Apache Dateien in den Speicher, z.B beim parsen von ServerSide Includes. Auf einem Multiprozessor
System sollte das deaktiviert werden da Apache die Dateien so meist schneller liefern kann.

1.2.6 NFS-Besonderheiten

Wird Apache in einer NFS-Umgebung betrieben sollte auf keinen Fall MemoryMapping aktiviert sein da Apache sich unter
umstaenden aufhaengen kann wenn 2 Prozesse gleichzeitig auf eine Datei zugreifen.

1.2.7 Prozess-erstellung, Prozesskonfiguration


Das ist der wohl wichtigste Teil der Konfiguration und auch der dynamischste von allen. Die Einstellungen des MPM-Workers
sind sehr Systembezogen und koennen nur richtig eingestellt werden wenn immens getestet wird.

Hierzu ein einleitender Auszug aus der Apache 2 Dokumentation der bitte sorgfaeltig zu lesen ist.

"Ein einzelner Steuerprozess (der Elternprozess) ist für den Start der Kindprozesse verantwortlich. Jeder Kindprozess erstellt eine
feste Anzahl von Server-Threads, wie durch die ThreadsPerChild- Direktive angegeben, sowie einen "Listener-Thread", der auf
Verbindungen wartet und diese an einen Server-Thread zur Bearbeitung weiterreicht, sobald sie eintreffen.

Der Apache versucht immer, einen Vorrat von freien oder unbeschäftigten Threads zu verwalten, die zur Bedienung
hereinkommender Anfragen bereit stehen. Auf diese Weise brauchen Clients nicht auf die Erstellung eines neuen Threads oder Prozesses
zu warten, bevor ihre Anfrage bedient werden kann.

Die Anzahl der Prozesse, die anfangs gestartet wird, wird mit der Direktive StartServers festgelegt. Dann, während des Betriebes,
berechnet der Apache die Gesamtzahl der unbeschäftigten Threads und forkt oder beendet Prozesse, um diese Anzahl innerhalb der durch
MinSpareThreads und MaxSpareThreads angegebenen Grenzen zu halten. Da dieser Prozess sehr selbstregulierend ist, ist es nur selten
notwendig, die Voreinstellung dieser Direktiven zu ändern.

Die maximale Anzahl Clients, die gleichzeitig bedient werden kann (d.h. die maximale Gesamtzahl der Threads in allen Prozessen),
wird mit der Direktive MaxClients festgelegt. Die maximale Anzahl der aktiven Kindprozesse ergibt sich aus MaxClients dividiert durch
ThreadsPerChild.

Zwei Direktiven legen harte Limits für die Anzahl der aktiven Kindprozesse fest und können nur geändert werden, indem der Server
komplett gestoppt und dann wieder neu gestartet wird. ServerLimit stellt die obere Grenze für die Anzahl der aktiven Kindprozesse dar
und muss größer oder gleich dem Quotienten aus MaxClients und ThreadsPerChild sein. ThreadLimit ist die obere Grenze für die
Anzahl der Server-Threads und muss größer oder gleich ThreadsPerChild sein. Sofern für diese Direktiven keine
Voreinstellungen verwendet werden, sollten sie vor allen anderen worker-Direktiven platziert werden."

klingt etwas unverstaendlich wird mit dem herumexperimentieren jedoch einfacher zu verstehen.

Kleines Beispiel:

ThreadLimit 60
ServerLimit 30
StartServers 2
MaxClients 1800
MinSpareThreads 30
MaxSpareThreads 60
ThreadsPerChild 60

diese Konfiguration kann 1800 Clients gleichzeitig bedienen. 2 Apache-Instanzen werden beim hochfahren gestartet wovon
jeder mindestens 30 und maximal 60 Kinder haben kann. Jeder Kindprozess kann maximal 60 Threads erstellen, wobei jeder
Thread 60 Clients verwaltet.

Rechnung dazu: 30*60=1800

Teilen Sie Also soviele Threads und Childs ein wie Sie benoetigen, dazu mus spaeter bei groesseren Clients auch das ServerLimit
erhoeht werden. Dieses gibt an wieviele Apache-Server maximal gestartet werden koennen (hier 30).

1.2.8 Benchmarks

Apache bringt bereits ein simples Benchmark-Tool mit welches vollkommen ausreicht um kleine Pruefungen vorzunehmen.
Das Apache-Benchmark genannte Programme finden Sie im bin Verzeichnis des Apache-Prefixes z.B
/usr/local/apache2/bin/ab

Um z.B 1000 gleichzeitige Clients zu simulieren die jeweils 100 requests abschicken (gesamt: 100.000 requests) tippen wir
/usr/local/apache2/bin/ab -n 100 -c 1000 hostname/document.html ein.

Der Benchmark auf einen Server mit der in 1.2.7 erwaehnten MPM-Beispiel Konfiguration auf eine php-datei die phpinfo();
aufruft sieht folgendermassen aus:

  
  	Server Software:        Apache/2.0.49
	Server Hostname:        beispielhost.de 
	Server Port:            80

	Document Path:          /info.php
	Document Length:        41604 bytes

	Concurrency Level:      1000
	Time taken for tests:   1.614566 seconds
	Complete requests:      100
	Failed requests:        47
   	(Connect: 0, Length: 47, Exceptions: 0)
	Write errors:           0
	Total transferred:      7372639 bytes
	HTML transferred:       7317892 bytes
	Requests per second:    61.94 [#/sec] (mean)
	Time per request:       16145.660 [ms] (mean)
	Time per request:       16.146 [ms] (mean, across all concurrent requests)
	Transfer rate:          4458.78 [Kbytes/sec] received

	Connection Times (ms)
	   	           min  mean[+/-sd] median   max
	Connect:      230  237   5.1    238     245
	Processing:    91  572 240.2    543    1331
	Waiting:       59  455 168.0    466     803
	Total:        332  810 239.1    781    1572

  	

von 1000 requests konnten 47 nicht verarbeitet werden was so wohl an der spawn-time neuer Prozesse liegt oder das keine
File-Diskreptoren auf dem Client mehr verfuegbar waren.

Ein weiteres Tool um seinen Apache zu benchmarken ist httperf (http://www.hpl.hp.com/personal/David_Mosberger/httperf.html ) auf das hier aber aufgrund der komplexen benutzung nicht
eingegangen werden soll.

 

 

top

2 Dynamische Inhalte

2.1 PHP und Apache 2

 

Seit Apache 2 als Stable verfuegbar ist fragt man sich warum PHP nicht fuer diese Version empfohlen wird, bietet sie doch soviele
Vorteile.

Das PHP-Team raet davon ab PHP in Apache 2.0-Produktiv Umgebungen einzusetzen. Das liegt am neuen Thread-Modell welches in Apache
uebernommen wurde und dazu dient effektiver viele Anfragen zu bearbeiten.

Die Zend-Core von PHP ist zwar threadsafe, aber viele Bibliotheken auf die PHP benutzt sind es nicht.

Lange Rede kurzer Sinn. Wenn man diese Zeilen liest faengt man nun wahrscheinlich an nachzudenken ob PHP ueberhaupt Sinnvoll ist
unter Apache 2, ich sag ja. Auch wenn es nicht von den Entwicklern empfohlen wird so laeuft PHP mit eigentlich allen Features mittlerweile Reibunglos unter Apache 2. Es lohnt auf keinen Fall nur wegen PHP auf eine Apache 1.3 Umgebung umzuschalten.

2.2 PHP Caching

Caching Technologien auf dem eigenen Server können eine Menge ausmachen. Solche Cache-Software wirbt mit teilweise 400%iger Beschleunigung des Codes - was natürlich sehr attraktiv ist.

Und tatsaechlich bringen diese Beschleuniger etwas.

Das ganze funktioniert recht simpel: Jedes PHP Skript muss vor der Ausführung (logischerweise) kompiliert werden. Die Cache-Software hält
das Skript im Kompilierten Zustand in einem Cache und stellt bei erneutem Aufruf des Skript die bereits kompilierte Version direkt zur
Verfügung. Der gesamte Kompilierungs-Overhead muss somit nicht wiederholt werden.

Die zwei besten Beschleuniger sind der ZendOptimizer von Zend Inc. und der nicht-kommerzielle (OpenSource) MMcache.

- http://www.zend.com/store/products/zend-optimizer.php
- http://turck-mmcache.sourceforge.net/

Der ZendOptimizer bringt ein einfaches Installations-Script mit sich welches den Optimizer selber installiert, und die MMcache Version
ist einfach zu kompilieren und ueber die php.ini einzubinden.

Ich empfehle MMcache weil diese Version Frei ist und mehr Einstellungsmoeglichkeiten bietet als der ebenfalls "frei erhaeltliche" ZendOpti.

2.3 MySQL-Tuning

Erfahrungsgemaess steigt MySQL bei einem Besucheransturm auf eine dynamische Seite am ersten aus. Dies liegt daran das ein Grossteil
der Installationen die Standard-Konfiguration nicht veraendert was u.a schon die Einschraenkung bringt das MySql nur 100 gleichzeitige
Verbindungen erlaubt.

Die folgenden Tips sollten einen erheblichen Performance-Schub bringen.


Hinweis:

Alle Konfigurationsparameter entweder in /etc/my.cnf in der Sektion mysqld eintragen z.B

[mysqld]
set-variable = max_connections=2500

oder als Parameter uebergeben zB.

safe_mysqld --max_connections=2500 &

2.3.1 Einstellen der max. gleichzetigen Verbindungen:

Wenn Sie stark frequentierte Webseiten haben die bei jedem Aufruf selects durchfuehren oder persistente Verbindungen zur
Datenbank halten ist MySQL in der std. Konfiguration mit 100 gleichzeitigen Verbindungen schnell am Ende.

MySQL ist auch auf schwachen Rechnern in der Lage viele Verbindungen zu verabrieten.

Die Variable max_connections sollte mit dem Wert "1000" oder "2500" auf einen akzeptablen Wert gebracht werden oder jedoch
dem was Ihre Websiten an gleichzeitigen Benutzern entspricht.

2.3.2 der Table-Cache und Key-Buffer:

key_buffer_size und table_cache sind die wohl wichtigsten performance-bezogenen Variablen. Diese geben an wieviel
Speicher MySQL zur verfuegung steht um Tabellen und Abfrage Keys zu speichern.

Auf Systemen mit 2GB-Ram oder mehr und vielen Datenbanken sind folgende Einstellungen zu empfehlen:

key_buffer=128M und table_cache=512 sowie sort_buffer=15M


Fuer kleinere Systeme (256MB + Ram) oder wenig Datenbanken ist ein:

key_buffer=64M und ein table_cache=256 (fuer => 256MB RAM) oder table_cache=128 (fuer < 256MB Ram) ausreichend. Desweiteren
reicht hier auch ein kleiner sort_buffer von 4 oder 8MB.

Fuer Systeme mit 128MB Ram oder Weniger sollten sie den table_cache nicht einstellen sondern die Standard-Werte
der small.cnf verwenden , jedoch den key_buffer auf 16 oder 32M stellen.

Kleinere Systeme zu verwalten macht fuer einen stark frequentierten Server keinen Sinn.

 

2.4 Mysql & Apache Temp. Files

Sowohl der Apache als auch der MySQL-Server legen temporaere Dateien auf der Festplatte ab. Meistens handelt es sich um
PHP-Sessions oder Auslagerungen grosser Suchanfragen bei einem belasteten System.

Diese meist kleinen Dateien kann man sinnvoll in einem tmpfs (shared memory filesystem) ablegen. Auch Kleinvieh macht mist.

dazu mountet man z.B ein 150MB grosses tmpfs mit:

/bin/mount -t tmpfs /dev/shm /tmp/ramtmp/ -o size=150m

und uebergibt mysql mit --tempdir=/tmp/ramtmp/ dieses Verzeichnis,
PHP kann man den Session-Speicherpfad in der php.ini (/etc) angeben.

session.save_path = /tmp/ramtmp/

so long live the fish ;)