しばやん雑記

Azure Serverless とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure で ASP.NET の Session State を利用する際のベストプラクティスを確認した

最近は移行絡みで Azure に ASP.NET アプリケーションを Session State 付きでデプロイすることが多いので、Azure 上で Session State を使う際のベストプラクティスを確認しておきました。

この記事で触れるのは .NET Framework の ASP.NET であって、.NET 6 で動作する ASP.NET Core 向けではないことに注意してください。ASP.NET Core 向けなら何も考える必要なく、パッケージをインストールして設定を追加すれば全く問題なく利用できます。

ASP.NET の場合は Session State が IIS と密接に関係しているため若干複雑になっています。

多少長くなったので目次記法を使いました。興味のある部分だけピックアップしてください。

注意したい ASP.NET Session State 特有の動作

まずは ASP.NET の Session State を使う上で注意したい動作をいくつかピックアップしました。最近はこの辺りの情報が少なくなっているので、余計に注意が必要な状況です。

Session に書き込まない限りはクッキーは発行されない

これは既に知っている方も多そうですが、ASP.NET では Session.SessionID を使って現在のセッション ID を取得することが出来ますが、これだけではクッキーが発行されないのでアクセスの度に値が変化します。

クッキーが発行される条件は Session に対して値を書き込む必要があるので、Session.SessionID を参照する場合はダミーでも良いので値を書き込んでおきます。

Session State を無効化してもデータストアへのアクセスは行われる

ASP.NET Web Forms ではページ単位、ASP.NET MVC の場合は Controller 単位などで、簡単に Session State の有効・無効・読み取りのみが設定できます。設定方法は違えど、これらは全て HttpContext に用意された SetSessionStateBehavior を呼び出しているだけです。

ここで SessionStateBehaviorDisabled を設定すると、データストアへのアクセス自体が完全に行われなくなることを期待していると思いますが、実際には既にクッキーが発行されていると有効期間の延長のためにアクセスが行われます。読み込み自体は行われませんが、それなりにコストはかかります。

個人的には Disabled の場合は期間延長自体も欲しくないのですが、この辺りの実装は Session State Module 側で行われているので対応が難しいです。

非同期 Session State Module をインストールした際の設定変更

先ほどの動作と大きく関係するのが非同期 Session State Module をインストールすると、組み込みと若干動作が変わることです。具体的には静的ファイル含むリクエストが Session State Module を通るようになります。

該当モジュールの preConditionmanagedHandler から integratedMode に変更されたことで、ネイティブ実装の StaticFileHandler でも有効化されています。この変更については Issue が上がっています。

正直な話、これは非同期 Session State Module の不具合だと思います。Session State を ASP.NET などのマネージドコード以外で使う必要が、殆ど存在しないからです。

静的ファイルを Blob や CDN から配信している場合には影響しませんが、同じ IIS から配信している場合には直撃するので、設定の変更や CDN でのキャッシュを強く推奨します。

Session State Store に Redis / Cosmos DB を利用する

ASP.NET では Session State Server と SQL Server が組み込みで用意されていますが、Azure 上では Redis と Cosmos DB というより優れた選択肢が用意されているので、基本的にはこのどちらかを使うようにします。

既に Redis をキャッシュ用に使っている場合は、そこに相乗りさせた方がコストは最適化出来ます。Cosmos DB はオートスケールによって 100 RU から開始出来るのと、Serverless プランが用意されているので、開発用から本番利用まで広く使えます。

Redis を使うケース

既に Redis の Session State Provider を使っているケースは多いと思います。Redis のマネージドサービスは多いので Azure 以外でも利用できるのが特徴です。

基本はドキュメント通りに設定しておけば問題ないですが、.NET Framework かつアクセスの多いアプリケーションの場合は、スレッドプールが枯渇してエラーとなることがあります。

Azure Cache for Redis のドキュメントにスレッドプール周りでの重要な情報が載っているので、こちらを参照して予めスレッドプールを拡大させておくのがお勧めです。

.NET 6 などと異なり .NET Framework はデフォルトのスレッドプールが小さいので問題となりやすいです。

Cosmos DB を使うケース

比較的新しい Session State Provider なのが Cosmos DB です。Cosmos DB の低レイテンシでグローバル分散というメリットを出来ますし、TTL も組み込まれているのでキャッシュやセッションに向いています。

こちらもパッケージをインストールして接続に必要な情報を appSettings に追加するだけで利用可能です。

Cosmos DB 固有の設定として接続方式が Direct / Gateway、そしてプロトコルが Https / TCP というように選択可能になっていますが、Direct と TCP の組み合わせを強く推奨しています。

非同期 Session State Module の新機能を有効化する

Cosmos DB の Session State Provider がリリースされたタイミングで、非同期 Session State Module にいくつか新機能が追加されています。詳細は以前書いたのでそちらを参照してください。

非同期 Session State Module に追加された機能で重要なのが aspnet:AllowConcurrentRequestsPerSession になります。デフォルトの Session State Module は同一セッションに対する並列実行を許可していないため、同じセッション ID を持ったリクエストが複数来るとブロッキングするようになっています。

この挙動は現代の SPA など API を多用する場合にはパフォーマンス上の問題になりやすいのと、セッションへのアクセスが排他であることを前提にしたコードが少ないため、並列実行の許可をお勧めしています。

前述した preCondition の設定変更と合わさると、この挙動は大変なパフォーマンス問題に繋がります。

CDN でキャッシュ可能なコンテンツは積極的にキャッシュする

Session State Module の preCondition 設定を修正すると、静的ファイルは Session State Module の影響を受けませんが、IIS から毎回配信するのはコストが高いため可能な限り CDN でキャッシュさせます。

Web.config にある staticContent を設定すると、静的ファイルの Cache-Control ヘッダーなどを一括で設定可能です。ASP.NET で処理されるリクエストとは別扱いなので安全です。

これは ASP.NET 固有ですが、System.Web.Optimization を使った CSS / JavaScript のバンドル化を行っていると、その部分は ASP.NET のパイプラインで実行されるようになるので、どう頑張っても Session State Module を必ず通ります。

デバッグ実行中はバンドルされないので、本番にデプロイしてから発覚しやすい問題です。

バンドル結果はメモリ内にキャッシュされているので高速ですが、無駄に Session State が構築されてしまうのでバンドルされたファイルは、必ず CDN やブラウザにキャッシュさせておきましょう。

そもそも Session State を使う必要があるか検討する

今更感ある話題を最後に持って来ていますが、Azure で動かす際に本当にその Session State は必要なのかという点から考え直すのがお勧めです。

案外 Session State は有効化していたけどアプリケーションでは使っていなかったというパターンも出てくると思います。地味に障害の原因となりやすい部分なので、不要なものは削っていきましょう。