IIS: Anwendungsinitialisierung durch Vorabladen von ASP.NET Webanwendung beschleunigen (Teil 1)

IIS: Anwendungsinitialisierung durch Vorabladen von ASP.NET Webanwendung beschleunigen (Teil 1)

Wird ein IIS Webserver neu gestartet, dauert es selbst bei kleinen Anwendungen zunächst einige Sekunden, bis die erste Seite geladen ist. Dies erscheint angesichts des Umfangs sowie der Komplexität von ASP.NET nicht verwunderlich. Spätestens wenn Datenbankzugriffe ins Spiel kommen lädt die erste Anfrage störend langsam – Wartezeiten die sich im Bereich einer halben Minute bewegen sind keine Seltenheit. Für die Nutzer ist dies schlecht, da kaum ein Anwender derart lange wartet – die Meisten sind nach 5 Sekunden weg.

Doch das muss nicht sein: Im folgenden Artikel erklären wir, wie die Ladezeit durch eine im Vorfeld durchgeführte Anwendungsinitialisierung stark reduziert werden kann. Dabei wird auf die Variante der Pseudo-Anfragen eingegangen. In einem weiteren Artikel werden wir auf codeseitiges Vorabladen eingehen. 

Warum ist die erste Anfrage an eine ASP.NET Anwendung langsam? 

Das Problem liegt im Aufbau des Anwendungsstartes: Wird eine Anwendung gestartet – ob aufgrund eines Serverneustartes oder Deployments spielt dabei keine Rolle – initialisiert der IIS die Anwendung noch nicht. Die Module sind noch nicht geladen und auch Verbindungen zu Datenbanken sind noch geschlossen. Diese Vorgänge werden erst bei der ersten HTTP-Anfrage durchgeführt – oder anders ausgedrückt: Wenn ein Nutzer die Seite im Browser öffnet.

Wir werden im folgenden ein deutlich sinnvolleres Ablaufmodell konfigurieren: Der IIS soll die Anwendung bereits beim Start möglichst weitgehend initialisieren, um die Ladezeit zu reduzieren. Am einfachsten funktioniert dies über den Aufruf von Seiten, die häufig genutzt und/oder recht rechenintensiv sind – beispielsweise weil verschiedene Datenzugriffe erst initialisiert und in den Cache geladen werden müssen.

Komplett vermeiden lassen sich die Ladezeiten dadurch natürlich nicht. Versucht beispielsweise ein Nutzer während des Startes der Anwendung auf sie zuzugreifen, muss er bis zum Abschluss des Vorganges warten. Für diesen Fall wollen wir dem Nutzer aber immerhin eine gesonderte Info-Seite anzeigen. Dies hat zwei Vorteile: Zunächst ist der Nutzer beschäftigt, da er den Hinweistext ließt. Bis er damit fertig ist, kann er unsere Anwendung vielleicht schon direkt verwenden. Und noch wichtiger: Er sieht überhaupt etwas. Durch die Infoseite weiß er, dass etwas passiert. Im Gegensatz zu einer ansonsten sichtbaren weißen Seite mit kreiselnder Ladeanzeige.

Anwendungsinitialisierung für den IIS installieren

IIS 8 (Windows Server 2012)/IIS 8.5 (Windows Server 2012 R2)

In diesen beiden neueren Versionen ist die Anwendungsinitialisierung bereits ab Werk integriert. Man muss die gleichnamige Rolle lediglich über den Server-Manager installieren. Sie ist im Pfad Webserver (IIS) > Anwendungsentwicklung zu finden:

Nach der Installation steht sie sofort zur Verfügung, ein Neustart ist nicht notwendig.

IIS 7.5/Windows Server 2008 R2

Die Anwendungsinitialisierung ist hier noch kein fester Bestandteil des Betriebssystemes. Allerdings kann es über das Application Initialization Module für den IIS 7.5 nachgerüstet werden. Informationen zum IIS 7.0 unter Server 2008 (R1) konnte ich nicht finden, dies müsste im Bedarfsfall in einer Nicht-Produktivumgebung getestet werden.

Die Anwendungsinitialisierung  aktivieren

Nachdem die Anwendungsinitialisierung wie zuvor beschrieben über den Server-Manager oder das Erweiterungsmodul installiert wurde, müssen wir die Funktion zunächst auf Anwendungsebene im IIS aktivieren. Dies können wir wahlweise über der grafische Oberfläche oder die Konfigurationsdatei applicationHost.config im Verzeichnis  %WINDIR%\system32\inetsrv\config durchführen. Wir werden jeweils beide Wege beschreiben, sofern möglich.

Zunächst öffnen wir die erweiterten Einstellungen der Anwendung oder Seite im IIS-Manager über einen Rechtsklick auf diese > Website verwalten > Erweiterte Einstellungen. Wichtig ist, die Eigenschaft Vorabladen aktiviert auf True zu setzen. Da wir später Änderungen am dazugehörigen Anwendungspool vornehmen müssen, nutzen wir die Gelegenheit außerdem, um dessen Namen oben einzusehen:

Alternativ suchen wir in der applicationHosts.config nach dem Knoten system.applicationHost in der Struktur configuration/configSections. Darin muss der Unterknoten zur jeweiligen Webanwendung ermittelt werden. Möchte man eine vollständige Seite bearbeiten wie im folgenden Beispiel, wählt man die Anwendung mit dem Attribut path=“/“ aus. Er muss eine Eigenschaft namens preloadEnabled=“true“ haben bzw. bekommen, falls diese noch nicht existiert:

<application path="/" applicationPool="Testpool" preloadEnabled="true">

Nun muss noch der Startmodus des Anwendungspools geändert werden. Dazu klicken wir im IIS Manager links auf Anwendungspools, machen einen Rechtsklick auf den Pool der Anwendung – in diesem Beispiel Testpool und klicken auf Erweiterte Einstellungen. Im sich nun öffnenden Fenster nach der Eigenschaft Startmodus suchen, diese muss auf AlwaysRunning stehen:

Wer stattdessen lieber mit der applicationHosts.config arbeiten möchte, sucht in system.ApplicationHost nach applicationPools. Hier ist beim jeweiligen Pool auf das Setzen des Attributes  startMode=“AlwaysRunning“ zu achten:

<add name="Testpool" managedRuntimeVersion="v4.0" startMode="AlwaysRunning" />

Die hier gezeigten Schritte müssen für jede Seite bzw. Anwendung sowie dem jeweils dazugehörigen Anwendungspool durchgeführt werden, um die Anwendungsinitialisierung nutzen zu können.

Initialisieren einer Anwendung

Ab sofort arbeiten wir auf Anwendungsebene, da sich die folgenden Einstellung pro Anwendung unterscheiden können. Eine grafische Oberfläche gibt es für diese Änderungen leider nicht, sodass zwingend mit der jeweiligen Web.config im WWW-Root der Anwendung gearbeitet werden muss.  Diese öffnen wir nun und suchen nach dem system.webServer knoten. Dort wird ein neuer Knoten zur Anwendungsinitialisierung eingefügt:

<applicationInitialization
remapManagedRequestsTo="StartupNotice.html"
skipManagedModules="true" >
<add initializationPage="/" />
</applicationInitialization>

Was bewirkt dieser Knoten? Mit remapManagedRequestsTo definieren wir eine statische HTML-Datei (Pfad relativ zum vom WWW-Root), die dem Nutzer angezeigt wird, während sich die Anwendung initialisiert. initializationPage gibt einen relativen URL-Pfad an, zu dem der IIS beim Starten eine Pseudo-Anfrage schicken soll. Dadurch wird die Seite bei allen weiteren Aufrufen durch die eigentlichen Nutzer deutlich schneller geladen. Man kann an dieser Stelle auch mehrere Seiten in jeweils einem add-Element angeben:

<applicationInitialization
remapManagedRequestsTo="StartupNotice.html"
skipManagedModules="true" >
<add initializationPage="/SlowController/Action" />
<add initializationPage="/AnotherSlowController/ComplexAction" />
</applicationInitialization>

Praxistest

Um zu testen ob unsere Optimierung wirklich funktioniert, genügt es, den Anwendungspool zu recyceln. Dabei startet der IIS alle Arbeitsprozesse neu, d.H. unsere Anwendung muss komplett neu initialisiert werden. Damit das Ergebnis nicht durch das noch laufende Neuladen des IIS verfälscht wird, warten wir in beiden Testfällen nach dem recyceln, bis sich die CPU-Last danach wieder legt:

Nun ist die CPU im Leerlauf und die Anwendung wurde vom IIS initialisiert. Die Aktualisierungsgeschwindigkeit des Taskmanagers steht in den Diagrammen auf Hoch, um ein möglichst genaues Bild der verursachten Last zu erhalten. Unsere Testanwendung soll mehrere Razor-Ansichten rendern und ausgeben. Das Laden dieser Testseite ist mit 700 ms (davon 23 ms Generierungszeit) erfreulich zügig und verursacht kaum sichtbare CPU-Last:

Zum Vergleich der erste Aufruf unserer Testseite ohne die hier gezeigten Optimierungen. Auch hier wird die gleiche Seite zum ersten mal nach dem Recyceln des App-Pools aufgerufen. Wie erwartet wird die Anwendung erst bei der ersten Anfrage initialisiert, sodass die Prozessorlast an dieser Stelle deutlich höher ist:

Vollständig geladen ist die Seite erst nach 6,5 Sekunden – Der Nutzer muss hier also rund 10x länger auf den Seiteninhalt warten! Noch extremer wird der Unterschied bei umfangreichen Anwendungen, die beispielsweise Datenbanktreiber initialisieren müssen. Hier kommen schnell 20 Sekunden und mehr zusammen, die weniger als 1 Sekunde gegenüberstehen.

Fazit

Die erste HTTP-Anfrage an eine ASP.NET Anwendung kann durch die Anwendungsinitialisierung drastisch beschleunigt werden, wodurch das Nutzererlebnis deutlich verbessert wird. In den folgenden Artikeln werden wir diesen Ansatz ausweiten, damit vor allem datenbanklastigere Anwendungen möglichst effektiv davon profitieren.

Leave a Reply