【ASP.NET】IIS Application Initializationによる初回アクセスの高速化

# はじめに
ASP.NETでWeb APIを使用したアプリケーションがあり、4秒以内で結果を戻さないと呼び出し先でタイムアウトエラーになる処理となっています。
最初のアクセスユーザーが必ずエラーの犠牲になっていて、それ以降の人はエラーにならずに処理が行われます。
これを改善したい。

【2021/01/20追記】
上記とは別件で、ASP.NETのMVCで作成したアプリケーションがあり、とある一覧を表示します。このページの初回アクセスには1分30秒かかり2回目以降は10秒くらいになります。どうやら、ASP.NETの初回アクセス以外にもPostgreSQLの初回アクセスの遅さも加わっているようです。PostgreSQLの部分に関しては、Application Initializationの有効化してもタイミングが一致しないため、初回アクセスの遅さを回避できないことがありえます。
そのため、夜中にタスクスケジューラーでバッチ（VBScript）から一覧を表示するページを呼び出して、PostgreSQLの初回アクセスをするようにしています。もっとも一覧のクエリー自体の速度見直しも必要かも知れません。
[Slow on first query - Stack Overflow](https://stackoverflow.com/questions/44808994/slow-on-first-query)

# Application Initializationの有効化
ASP.NETは、初回アクセスが遅いことで有名です、これは初回のページの呼び出し時にアプリケーションの初期処理が実施されるからです。
Microsoftは、起動時にIISがアプリケーションを呼び出すことで、最初のユーザーがアクセスした時の体感速度を改善する仕組み「Application Initialization Module」を用意しました。
Application Initialization Moduleは、IIS7.5では外部モジュールとして提供されていたが、IIS8以降ではIIS標準機能となっています。ただしデフォルトでは有効化されていない機能なので、Application Initialization を有効化する必要があります。

Windows Server 2016 ではサーバーマネージャを起動して「サーバーの役割と追加」から「Application Initialization」にチェックを付けてインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/ece0fbf3-0852-a6d2-d1ed-6091863b6b36.png)

IIS標準機能なので最初から有効化されていると勘違いしていて、後述する設定をしたのに何も変わらないと思ったら有効化が必要でした。

# 設定
## アプリケーションプール
IISで対象のアプリケーションプールの詳細設定ダイアログを開いて設定を行います。
[開始モード]を[OnDemand]から[AlwaysRunning] に変更します。
「AlwaysRunning」にすることでアプリケーションプールを常に起動した状態にすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/4fdfee12-a601-ac33-e3f3-2d1e1b02143d.png)

下記２点の設定は運用に合わせて設定してください。
参照：[アプリプールのリサイクル後にasp.net mvc webappをウォームアップするにはどうすればよいですか？](https://qastack.jp/server/590865/how-can-i-warm-up-my-asp-net-mvc-webapp-after-an-app-pool-recycle)

* アイドルタイムアウトを無効にする
* 定期的なリサイクルを無効にする

## Webサイト
IISでサイトの対象のWebサイトの詳細設定ダイアログを開いて設定を行います。
[有効化されたプリロード]を[True]に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/90dc59e6-be7e-4bd7-9d74-0050490586f4.png)

## 初期ページ
初期処理時にどのファイルを呼び出すかを設定します。
Webアプリケーションの Web.config を編集します。system.webServer に次のようなタグを追加します。

```xml:web.config
  <system.webServer>
   ...
   <applicationInitialization
      doAppInitAfterRestart="false"
      skipManagedModules="false"
      remapManagedRequestsTo="Loading.html" />
      <add initializationPage="/Initializer.aspx" />
   </applicationInitialization>
  </system.webServer>
```

以下は、[Application Initialization <applicationInitialization>](https://docs.microsoft.com/en-us/iis/configuration/system.webserver/applicationinitialization/)のGoogle翻訳結果

**doAppInitAfterRestart**
アプリケーションの再起動が発生するたびに初期化プロセスが自動的に開始されることを指定します。これは初期化プロセスは、アプリケーションプールの再起動後に開始されることを指定するアプリケーションエレメントでpreLoadEnabled属性とは異なることに注意してください。デフォルトはfalse

**skipManagedModules**
初期化中にマネージモジュールをロードするかどうか。デフォルトはfalse

**remapManagedRequestsTo**
アプリケーションの初期化中に要求を再マッピングするページを指定します。(※ロード中に表示する静的ページを指定)

**initializationPage**
アプリケーションの再起動時に初期化されるアプリケーションを指定します。（複数指定可）

詳しい使い方は下記サイトを参考にして下さい
[IIS Application InitializationでASP.NETアプリの起動を高速化（ウォームアップ）](https://blog.nextscape.net/archives/2399)
[Use IIS Application Initialization for keeping ASP.NET Apps alive](https://weblog.west-wind.com/posts/2013/oct/02/use-iis-application-initialization-for-keeping-aspnet-apps-alive)

# 確認
IISリセットして、対象のWebサイトにアクセスして速くなっていればいいです。

これまでと速度差があまり感じなく本当に動作しているのか分かりにくい場合があります。initializationPageを設定したにも関わらず、IISログに出力されていないので心配になります。
これは、IIS applicationInitializationリクエストは内部で行われるため、IISのログには出力されないのです。

確認するには初期ページでリクエストのユーザーエージェントが「IIS Application Initialization Warmup」か「IIS Application Initialization Preload」となっていれば動作していることになります。
参照：[Azure Web App Application Initialization](http://www.zankavtaskin.com/2016/12/azure-web-app-application-initialization.html)

```csharp:Initializer.aspx
namespace SampleWebApp
{
    public partial class Initializer : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // ログ・ファイルへの出力ストリームを生成
            StreamWriter sw = new StreamWriter(
                Server.MapPath("~/init.log"), true, Encoding.GetEncoding("Shift_JIS"));

            // 日付、リクエストのユーザーエージェントを出力
            StringBuilder sb = new StringBuilder();
            sb.Append(DateTime.Now.ToString());
            sb.Append("\t");
            sb.Append(Request.UserAgent);
            sw.WriteLine(sb.ToString());
            sw.Close();
        }
    }
}
```

```xml:web.config
  <system.webServer>
   ...
   <applicationInitialization />
      <add initializationPage="/Initializer.aspx" />
   </applicationInitialization>
  </system.webServer>
```

IISResetする度に、initializationPage が呼ばれていることが確認できる。

```bash:init.log
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」のみにしています。

```xml:web.config
  <system.webServer>
   ...
   <applicationInitialization />
      <add initializationPage="/api/S0100/S0102" />
   </applicationInitialization>
  </system.webServer>
```

# 参照
* [[ASP.NET]Application InitializationでASP.NET Webアプリケーションを常時起動する](https://www.pine4.net/Memo/Article/Archives/522)
* [オート・スタートによるアプリケーションの初期化処理とメンテナンスページ](https://www.buildinsider.net/web/iis8/04)
* [ASP.NET MVC の初回実行時の表示が遅いのを解消する方法](https://webbibouroku.com/Blog/Article/iis-preload)
* [IISのオートスタート機能を使ってASP.NET Webアプリケーションの初回実行を行おう](https://qiita.com/k_fujita/items/e7bf6d5e6e723ec57b19)