Rating: 5.0/5. (8 Stimmen) Details
Bitte warten...

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.

 

Hinweis
Den einen oder anderen Performance Tipp habe ich bereits in meinem vorherigen Artikel gegeben. Nach Veröffentlichung der Installations- und Sicherheitsartikel zu IIS, PHP und WordPress folgt nun die Fortsetzung zum Thema Performance.

 

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.

Vererbung wiederherstellen

 

Best practice
Wende nach Möglichkeit alle Einstellungen auf Serverebene an. Abweichende Konfigurationen auf Site-Ebene sollten sparsam angewendet werden. Das vereinfacht die Verwaltung und vermeidet Fehler.

 

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.

 

Ein Webgarden besteht aus mehreren Worker Prozessen.

 

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.

 

Neue Caching Regel hinzufügen

 

Ü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.

Caching Process Model

 

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.

 

Erweiterte Einstellungen

 

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.

 

Der etag header wird standardmäßig durch IIS gesetzt.

 

Information
Googles PageSpeed Insights bewertet eine Webseite mit langer Cache-Vorhaltezeit positiv. Google empfiehlt die Aufbewahrung von 1 Jahr, respektive der Direktive Cache-Control: max-age=31536000.

 

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.

 

Besonderheit Reverse Proxy
Kommt ein Reverse Proxy zum Einsatz, kann dieser Server Daten u.U. effektiver zwischenspeichern. Microsoft hatte vor einiger Zeit das IIS-Modul Application Request Routing (ARR) entwickelt und zur freien Verfügung bereitgestellt. ARR unterstützt externe Cache Disks und bietet mitunter komplexe Szenarien des Cachings. Doch mittlerweile hat Microsoft den Fokus auf seine Cloud Produkte gesetzt, was sich leider negativ auf die Entwicklung des Moduls auswirkt. IIS ARR unterstützt im Backend kein HTTP/2 und das mitgelieferte Komprimierungsmodul ist veraltet.

 

Best practice
Meine Erfahrungen mit PHP und WordPress haben gezeigt, dass die nativen Caching Module des IIS anderen vorzuziehen sind. In Kombination mit PHP OpCache und einem WordPress-Cache-Plugin wie Autoptimize das CSS und JS zusammenführt, minimiert und als statische Dateien abspeichert, konnte ich bisher die besten Ergebnisse erzielen, ohne dass dabei veraltete Daten ausgeliefert oder Webseiten fehlerhaft dargestellt werden. Insbesondere die Größe der zusammengeführten, text-basierten Ressourcen lassen sich überdies durch Komprimierung enorm reduzieren und dann zwischenspeichern. Die Komprimierung wird Schwerpunkt meines nächsten Artikels.

 

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.

 

 


Mac

Mac

Seit über 20 Jahren beschäftige ich mich mit Themen aus dem Bereich IT. Mein Schwerpunkt liegt dabei auf Produkte aus dem Hause Microsoft. Dazu gehören neben Active Directory und Windows Server insbesondere Netzwerkdienste wie DNS, DFS und DHCP. Zudem bin ich ein großer Verfechter des Internet Information Service, also dem Windows Webserver. Berührungspunkte im Bereich Citrix XenApp sowie XenDesktop, als auch VMware runden meinen Erfahrungsschatz ab.

0 Kommentare

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.