はじめに
ASP.NETでWeb APIを使用したアプリケーションがあり、4秒以内で結果を戻さないと呼び出し先でタイムアウトエラーになる処理となっています。
最初にアクセスユーザーが必ずエラーの犠牲になっていて、それ以降の人はエラーにならずに処理が行われます。
これを改善したい。
Application Initializationの有効化
ASP.NETは、初回アクセスが遅いことで有名です、これは初回のページの呼び出し時にアプリケーションの初期処理が実施されるからです。
Microsoftは、起動時にIISがアプリケーションを呼び出すことで、最初のユーザーがアクセスした時の体感速度を改善する仕組み「Application Initialization Module」を用意しました。
Application Initialization Moduleは、IIS7.5では外部モジュールとして提供されていたが、IIS8以降ではIIS標準機能となっています。ただしデフォルトでは有効化されていない機能なので、Application Initialization を有効化する必要があります。
Windows Server 2016 ではサーバーマネージャを起動して「サーバーの役割と追加」から「Application Initialization」にチェックを付けてインストールします。
IIS標準機能なので最初から有効化されていると勘違いしていて、後述する設定をしたのに何も変わらないと思ったら有効化が必要でした。
設定
アプリケーションプール
IISで対象のアプリケーションプールの詳細設定ダイアログを開いて設定を行います。
[開始モード]を[OnDemand]から[AlwaysRunning] に変更します。
「AlwaysRunning」にすることでアプリケーションプールを常に起動した状態にすることができます。
下記2点の設定は運用に合わせて設定してください。
参照:アプリプールのリサイクル後にasp.net mvc webappをウォームアップするにはどうすればよいですか?
- アイドルタイムアウトを無効にする
- 定期的なリサイクルを無効にする
Webサイト
IISでサイトの対象のWebサイトの詳細設定ダイアログを開いて設定を行います。
[有効かされたプリロード]を[True]に変更します。
初期ページ
初期処理時にどのファイルを呼び出すかを設定します。
Webアプリケーションの Web.config を編集します。system.webServer に次のようなタグを追加します。
<system.webServer>
...
<applicationInitializationdoAppInitAfterRestart="false"skipManagedModules="false"remapManagedRequestsTo="Loading.html"/><addinitializationPage="/Initializer.aspx"/></applicationInitialization></system.webServer>以下は、Application Initialization のGoogle翻訳結果
doAppInitAfterRestart
アプリケーションの再起動が発生するたびに初期化プロセスが自動的に開始されることを指定します。これは初期化プロセスは、アプリケーションプールの再起動後に開始されることを指定するアプリケーションエレメントでpreLoadEnabled属性とは異なることに注意してください。デフォルトはfalse
skipManagedModules
初期化中にマネージモジュールをロードするかどうか。デフォルトはfalse
remapManagedRequestsTo
アプリケーションの初期化中に要求を再マッピングするページを指定します。(※ロード中に表示する静的ページを指定)
initializationPage
アプリケーションの再起動時に初期化されるアプリケーションを指定します。(複数指定可)
詳しい使い方は下記サイトを参考にして下さい
IIS Application InitializationでASP.NETアプリの起動を高速化(ウォームアップ)
Use IIS Application Initialization for keeping ASP.NET Apps alive
確認
IISリセットして、対象のWebサイトにアクセスして速くなっていればいいです。
これまでと速度差があまり感じなく本当に動作しているのか分かりにくい場合があります。initializationPageを設定したにも関わらず、IISログに出力されていないので心配になります。
これは、IIS applicationInitializationリクエストは内部で行われるため、IISのログには出力されないのです。
確認するには初期ページでリクエストのユーザーエージェントが「IIS Application Initialization Preload」となっていれば動作していることになります。
参照:Azure Web App Application Initialization
namespaceSampleWebApp{publicpartialclassInitializer:System.Web.UI.Page{protectedvoidPage_Load(objectsender,EventArgse){// ログ・ファイルへの出力ストリームを生成StreamWritersw=newStreamWriter(Server.MapPath("~/init.log"),true,Encoding.GetEncoding("Shift_JIS"));// 日付、リクエストのユーザーエージェントを出力StringBuildersb=newStringBuilder();sb.Append(DateTime.Now.ToString());sb.Append("\t");sb.Append(Request.UserAgent);sw.WriteLine(sb.ToString());sw.Close();}}}<system.webServer>
...
<applicationInitialization/><addinitializationPage="/Initializer.aspx"/></applicationInitialization></system.webServer>IISResetする度に、initializationPage が呼ばれていることが確認できる。
2020/04/05 16:51:00 IIS Application Initialization Warmup
最後に
これにより4秒以内でおさまるようになりました。ただ通常より少し遅いかなって時がありますが、それでも1秒か2秒程度なので問題ありません。
今回の場合、Web APIなので初回アクセスはWeb APIのページを指定しています。
applicationInitializationの設定の「doAppInitAfterRestart」と「skipManagedModules」が説明が分かりにくいですね。
「doAppInitAfterRestart=”true”」にするとエラーになったので、これは「false」にします。デフォルトは「false」です。
参照したサイトだと「skipManagedModules=”true”」となっていることがありますが、今回は「false」でも問題ありませんでした。デフォルトは「false」です。
デフォルト設定の場合、記述する必要はないのでシンプルに「initializationPage」のみにしています。
<system.webServer>
...
<applicationInitialization/><addinitializationPage="/api/S0100/S0102"/></applicationInitialization></system.webServer>