Neben Stabilität und Sicherheit spielt die Performance von Webanwendung und Webserver eine wichtige Rolle. Die eigene Homepage kann noch so schön gestaltet sein, Besucher sind schnell genervt, wenn sich die Seiten nur langsam aufbauen. Je schneller ein Webserver Seiten ausliefert, desto eher bleiben die Besucher bei der Stange. Die sichtbaren Inhalte sollten im besten Fall unter 2 Sekunden erscheinen.
In dieser Beitragsserie erläutere ich Maßnahmen, die Performance des IIS, mit Fokus auf PHP und WordPress, zu verbessern. Dieser Teil hat den Schwerpunkt auf Tuning der Arbeitsprozesse und dem IIS-Cache.
Die Konfigurationsdateien des IIS
Mit dem Versionswechsel von IIS 6 zu 7 wurde das Konzept des Webservers komplett überarbeitet. Neben dem modularen Aufbau, umfangreichen Verbesserungen sowie der Einsatz neuer Techniken, besitzt der IIS darüber hinaus die Fähigkeit, den eigenen Funktionsumfang durch zusätzliche Module wie Url Rewrite, CORS oder ARR zu erweitern.
Die Serverkonfiguration wird zentral in der Datei C:\Windows\System32\inetsrv\config\applicationHost.config, davon abweichende Einstellungen in der web.config im Verzeichnis der jeweiligen Webanwendung gespeichert. Viele Einstellungen sind nicht direkt über die grafische Oberfläche des IIS Managers erreichbar. Dafür gibt es zu viele Parameter. Die Microsoft-Entwickler haben dem IIS Manager aus diesem Grund einen Konfigurationseditor mitgegeben, über den jede Konfiguration unkompliziert eingesehen und bearbeitet werden kann.
Einstellungen können sowohl auf Server- als auch auf Site-Ebene gesetzt werden. Einstellungen auf Serverebene werden auf alle untergeordneten Webseiten angewandt. Ändert man auf Site-Ebene die Konfiguration, wird die Vererbung unterbrochen. Sie kann reaktiviert werden indem die betreffende Einstellung aus der korrelierenden Konfigurationsdatei web.config oder im Editor über „Revert To Parent“ gelöscht wird.
Tuning der Application Pools (Worker, Arbeiterprozesse)
Erhöhe die Queue Length auf einen Wert von 10.000 oder mehr. Das Webmanagement (http.sys) reiht Anfragen in eine Queue, der App Pool arbeitet diese Anfragen ab. Ist die Queue voll, beantwortet der IIS alle nachfolgenden Requests mit 503 – Service unavailable. Normalerweise wird man diese Grenze nicht erreichen. Jedoch ist es kein Fehler den Wert hochzusetzen, sollte es einmal einen Ansturm geben oder Angreifer versuchen, über offen gehaltene Verbindungen den Webserver unter Druck zu setzen.
Das Festlegen von mehr als einem Prozess pro App Pool aktiviert einen sogenannten Web Garden (Webgarten). Dabei werden Web Requests auf die angegebene Anzahl von Arbeiterprozesse verteilt. Auf Servern mit mehreren CPUs kann das bei lange ladenden Webseiten zu Verbesserungen führen. Jedoch muss auch die Webanwendung dahingehend kompatibel sein. Ich habe mit Webgärten etwas experimentiert, konnte aber in Kombination mit PHP keine Verbesserung feststellen. Tatsächlich hat sich durch den Management Overhead die Performance eher leicht verschlechtert. Belasse daher den Standardwert von Maximum Worker Processes auf 1. Auf NUMA-aware Servern ist der optimale Wert 0.
Setze Idle Time-out (minutes) auf 0. Dadurch verhinderst du ständiges Neuladen und Kompilieren der PHP-Skripte. Setze stattdessen auf zeitbasiertes Recycling. Starte den Prozess einmal in der Nacht durch, um den Speicher zu bereinigen. Der erste Aufruf wird dann wieder etwas länger dauern. Das lässt sich aber durch Vorabladen umgehen. Weiter unten erkläre ich wie man das einrichtet.
Separate App Pools einzurichten ist sinnvoll und können unter bestimmten Bedingungen die Leistung verbessern. Sie tragen zudem zur Stabilität und Sicherheit bei. Wenn mehrere Webseiten auf einem Server gehostet werden, laufen sie isoliert und blockieren sich nicht gegenseitig, wenn ein PHP-Skript zu lange läuft oder gar in einen Fehler rennt. Wie die Separierung funktioniert, habe ich im Beitrag Webserver Sicherheit erklärt.
Output Caching
Webseiten lassen sich durch Zwischenspeicherung enorm beschleunigen. Es gibt verschiedene Arten des Cachings. Der serverseitige IIS-Cache hält Daten, die oft abgerufen werden, auf dem Server im Arbeitsspeicher vor. Aktiviert wird der Cache auf Server- oder Siteebene über den Punkt „Output Caching“ => „Edit Feature Settings…“.
Der IIS speichert statische Dateien wie HTML, Stylesheets oder Javascript automatisch zwischen. Ändert sich eine Datei, wird der Cache geflusht. Dynamische Daten ändern sich mit jedem Request und werden nicht zwischengespeichert. Semi-dynamische Daten, also Daten welche sich gelegentlich ändern, können über dieses Feature gut zwischengespeichert werden. Die besten Kandidaten für dynamischen Output Cache sind Seiten, die Daten dynamisch generieren, die sich aber wahrscheinlich nicht von Anfrage zu Anfrage aufgrund der URL oder der Header-Informationen ändern. So sind z. B. Fotogalerien, die die Größe von Bildern für die Anzeige in einer Webseite dynamisch ändern, hervorragend geeignet, da das Zwischenspeichern der Daten dem Server die erneute Verarbeitung der Bildgrößenänderung bei jeder Anfrage erspart.
Über Regeln lässt sich das Zwischenspeichern statischer Dateien anpassen. Darüber lassen sich aber auch bestimmte Dateien von der Zwischenspeicherung ausnehmen.
Über „Add…“ fügt man eine neue Regel hinzu. Unter Angabe der Dateierweiterung wird definiert, wie gecacht werden soll. Es gibt den User-mode Cache, der sich in den Worker Prozessen befindet, und der Kernel-mode Cache in http.sys. Der Kernel Cache liegt in der Bearbeitungsreihenfolge ganz am Anfang, ist sehr schnell, hat aber diverse Beschränkungen. So wird Inhalt welcher eine Authentifizierung erfordert, nicht im Kernel gespeichert. Greift der Kernel Cache, werden alle anderen Caching-Module nicht mehr durchlaufen. Zudem verbraucht man beim Kernel-mode Cache mehr Ressourcen. Überprüfen lässt sich der Kernel Cache über netstat http show cachestate
oder im Performance Monitor.
Die Ladezeit kann bei umfangreichen Seiten durch Caching enorm reduziert werden. Ob der serverseitige Cache überhaupt aktiv wird, entscheiden zwei Faktoren: wie häufig und in welchem Zeitrahmen auf eine Ressource zugegriffen wird. In der Standardeinstellung muss zweimal innerhalb von 10 Sekunden eine Datei aufgerufen worden sein. Diese Werte lassen sich im Configuration Editor im Bereich system.webServer/serverRuntime den eigenen Anforderungen anpassen.
Caching Profile
Der IIS cacht nicht nur serverseitig, er setzt auch die cache-control-Direktive damit u.a. Proxy Server oder Browser Dateien zwischenspeichern. Durch den richtigen Einsatz dieser Direktive verhindert man das wiederholte Anfordern von Ressourcen und beschleunigt das Navigieren durch die Webanwendung. Diese Einstellungen werden in Caching Profilen zusammengefasst und können nur im Konfigurationseditor feingranular definiert werden. Die Profile befinden sich im Abschnitt system.webServer/caching.
Unter Location finden sich folgende fest definierte Werte. Sie geben vor an welcher Stelle die policy-Einstellung wirkt.
- Client: durch den Browser zwischenspeichern (cache-control: private)
- Downstream: zwischengelagerte Proxies und Browser können den Inhalt cachen, der IIS nicht (cache-control: public)
- Server: nur der IIS speichert die Daten (cache-control: no-cache)
- None: Objekte werden nicht zwischengespeichert (cache-control: no-cache)
- ServerAndClient: Nur IIS und Browser speichern die Daten, Proxy sind davon ausgenommen. (cache-control: private)
- Any: überall werden die Dateien zwischengespeichert (cache-control: public)
Die Cache Policy definiert die Art des Caching.
- CacheUntilChange: Objekte verbleiben im Cache, solange sich die Ressource nicht ändert.
- CacheForTimePeriod: Objekte verbleiben für die angebene Dauer im Cache die in duration festgelegt wurde.
- DisableCache: Deaktiviert den jeweiligen Cache.
- NoCache: Objekte werden nicht zwischengespeichert.
Variablen in der Abfrage (Query String) werden häufig genutzt, um bspw. eine bestimmte Version eines Stylesheet-Objektes anzugeben: https://www.prival.de/css/main.css?version=1.2.3. Cache Module haben mitunter Probleme mit solchen Query Strings und fügen sie nicht in den Zwischenspeicher. Um das zu vermeiden, lässt sich im Caching Profil angeben, wie diese Dateien zu verarbeiten sind. Dazu nutzt man die Eigenschaft varyByQueryString und definiert die Variable anhand er die Dateien zwischenspeichern soll. varyByQueryString=”version” würde demnach die Dateien anhand ihrer Version unterscheiden und separat zwischenspeichern. Wildcards sind erlaubt und erfordern keine explizite Angabe der Abfragezeichenkette: varyByQueryString=”*”
Ähnlich verhält es sich mit HTTP Request Headern. Über varyByHeaders kann der Cache basierend auf den HTTP-Headern, die vom Client an den Server gesendet werden, variieren. So ließe sich bspw. anhand der Browsersprache Dokumente anhand der Sprache unterschiedlich zwischenspeichern. Der Kernel-mode Cache unterstützt das varyByHeaders Attribut, aber nicht varyByQuerystring.
In der GUI verbergen sich diese Parameter in den erweiterten Einstellung.
In den Konfigurationsdateien spiegeln sich die Caching Profile wie folgt wider (Beispiel).
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> … <caching enabled="true" enableKernelCache="true" maxCacheSize="4096" maxResponseSize="524288"> <profiles> <add extension=".woff2" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Downstream" varyByQueryString="*" /> <add extension=".woff" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Downstream" varyByQueryString="*" /> <add extension=".jpg" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Client" /> <add extension=".gif" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Client" /> <add extension=".jpeg" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Client" /> <add extension=".png" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Client" /> <add extension=".webp" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Client" varyByQueryString="size" /> <add extension=".html" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Any" /> <add extension=".js" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Any" varyByQueryString="*" /> <add extension=".css" policy="CacheUntilChange" kernelCachePolicy="CacheUntilChange" duration="30.00:00:00" location="Any" varyByQueryString="version" /> </profiles> </caching> … </system.webServer> </configuration>
Der Client (Browser, Proxy) kann also über die cache-control-Direktive angewiesen werden statische Dateien zu speichern. Der browserseitige Cache wird global im Configuration Editor im Zweig system.webServer/staticContent gesetzt. Dies kann server- wie auch site-basiert im Unterzweig clientCache erfolgen.
Je nach Einstellung des Control Mode greifen andere Mechanismen. Bei UseMaxAge wird das Attribut cacheControlMaxAge herangezogen. In diesem Beispiel würde der Cache 365 Tage (365.00:00:00) gültig bleiben bis der Cache verworfen wird oder die Ressource sich ändert. Während DisableCache den Cache deaktiviert zieht UseExpires den Wert httpExpires heran. Diese Einstellung ist veraltet und sollte nicht mehr verwendet werden. Interessant ist noch setEtag. Hierbei setzt der IIS einen eindeutigen Identifier. Ändert sich die Ressource erhält sie eine neue ID. Anhand des Etag können Browser eigenständig entscheiden, ob eine Ressource neugeladen werden muss. Die Einstellung ist standardmäßig aktiviert und es empfiehlt sich, diese aktiviert zu lassen.
IIS nutzt die Cache Control-Mechanismen für alle statischen Dateien, die in MIME Types konfiguriert sind, es sei denn es gibt Caching Profile mit anderslautenden Regeln. Im Konfigurationseditor sind die statischen Dateien im Abschnitt system.webServer/staticContent aufgelistet.
Aber aufgepasst. Die IIS-seitige cache-control-Direktive kann auch von Webanwendungen bspw. durch Headerinformationen im HTML-Dokument oder dem PHP-Compiler (bspw. durch die Einstellung session.cache_limiter) übergangen werden. Das kann unter ungünstigen Bedingungen zu fehlerhaftem Zwischenspeichern führen. Das lässt sich aber schnell überprüfen, indem man den Response Header der Ressourcen mithilfe der Entwicklungswerkzeuge der Browser untersucht.
Zusätzliche Caching Module
Für PHP und IIS gibt es zusätzliche Caching Module wie memCache oder WinCache. Klare Sache, die können noch mehr Performance herauskitzeln. Dazu müssen sie aber gut auf die Webanwendung abgestimmt sein. Eine Einschränkung ist zudem der Wartungsaufwand. Beim Updaten von PHP können diese Module hinderlich sein. In der Regel muss man einige Zeit auf die Veröffentlichung aktualisierter, der neuesten PHP-Version angepasster, Cache Module warten. Gerade bei sicherheitsrelevanten PHP-Updates ist das ein klarer Nachteil.
Webseite vorabladen (Preload)
Der PHP Interpreter braucht einige Zeit bis er die Skripte einer Webanwendung kompiliert hat. Da empfiehlt es sich die Webseite vorabzuladen. Microsoft hat dem IIS eine Preload-Funktion mitgegeben, zu finden in den erweiterten Einstellungen einer Site. Das funktioniert jedoch nicht mit FastCGI-Modulen. Ein Task in der Windows Aufgabenplanung kann aushelfen. Darüber startet man die Powershell (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe) und ruft über die Optionen (Arguments) den Befehl zum Absetzen einer Webanfrage auf: -NoProfile -NonInteractive -Command “Invoke-WebRequest -Uri ‘https://www.privalnetworx.de/index.php’ -UserAgent ‘PowerShell/Preload’ -DisableKeepAlive -TimeoutSec 90 | Out-Null”. Startzeit und Intervall festlegen, speichern und fertig ist die hauseigene Preload-Funktion.
0 Kommentare