Silverlight をインストールするには、ここをクリックします*
Japan変更|すべてのMicrosoft のサイト|サインイン
MSDN*
マイクロソフト サイトの検索:
|MSDN ライブラリ|デベロッパー センター|ダウンロード情報|開発ツール製品|コミュニティ|ご意見・ご要望|サイトマップ
MSDN Home > 連載コラム > Nothin' but ASP.NET > ページ出力キャッシュ - 第 1 部

ページ出力キャッシュ - 第 1 部

Rob Howard
Microsoft Corporation

March 20, 2002
日本語版最終更新日 2002 年 6 月 20 日

約 1 年前に私が書いたコラムでは、 ASP.NET のキャッシュ機能の概要を示しました。 今月のコラムでは、そのトピックをさらに進め、 ASP.NET キャッシュの 1 つの側面である、ページ出力キャッシュに注目します。

ページ出力キャッシュとは何でしょう?

Dictionary.com では、キャッシュを "次に同じデータにアクセスする速度が向上するようにデザインされた、 最近アクセスしたデータを保持するメモリ" と定義しています。

ソフトウェア ソリューションに当てはめてみると、 キャッシュは、クラス インスタンスやアプリケーション データなど、 頻繁にアクセスするリソースを保持するために使用する専用のメモリと言えます。 毎回リソースを作成し直すのではなく、リソースを一度作成し、何度でも使用できます。

キャッシュはすべてのアプリケーションで有用ですが、 Web アプリケーションに適用すると、 パフォーマンスが大きく向上する可能性があります。 キャッシュは、"待ち時間" (要求を行ってから応答を受け取るまでにかかる時間) を削減するのに役立ちます。 さらに、要求の処理と応答の生成に使用するサーバー リソースも削減します。

現在 Web アプリケーションで利用されている最も一般的なキャッシュの種類は、 標準 HTTP キャッシュ ヘッダーの使用です。 標準 HTTP キャッシュ ヘッダーは、 クライアントがローカル コピーを保持できる場合や、 ローカル コピーの有効期限が切れる時期など、 生成されるドキュメントの使用方法を指定します。 このような HTTP キャッシュには、多種多様の事例を可能にする意味合いが込められています。 HTTP キャッシュされたリソースには、3 種類のユーザーがあります。

  • 生成元サーバー : 本来、ドキュメントを生成するサーバー。
  • クライアント : たとえば、クライアント キャッシュをサポートする Internet Explorer やその他のブラウザ。 クライアントがドキュメントをキャッシュできる場合、 生成元サーバーからドキュメントをフェッチするのではなく、 ローカル コピーを表示できます。
  • ダウンストリーム クライアント : 生成元サーバーとクライアント間に配置され、 キャッシュされたリソースのコピーを保持する機能を持つネットワーク アプリケーション。 この主な例は、リバース プロキシ モードで使用する Microsoft Internet Security and Acceleration Server (ISA) です。 ISA をリバース プロキシ モードで使用すると、 ISA はサーバーからドキュメントをキャッシュします。 リバース プロキシによって、分散キャッシュされたアプリケーションがネットワークにまたがって存在できるようになるので非常に効果的です。 これは、コンテンツがリクエスタに近いので、要求の待ち時間をさらに削減します。

開発者が高レベルの OutputCache ディレクティブまたは低レベルの Response.Cache API のいずれかを使用すると、 ASP.NET はすべての ASP.NET ページに必要な HTTP キャッシュ ヘッダーを生成します。

ASP.NET は生成されるドキュメントに HTTP キャッシュ ヘッダーを簡単に追加すること以外に、 さらに一歩先まで踏み込んでいます。 高レベルの API または低レベルの API のいずれかを使って指定すると、 指定した要求に対して生成される応答を ASP.NET アプリケーションのキャッシュに追加できます。 次の要求では、ページ全体を再び実行するのではなく、 既に生成された応答をキャッシュから取得して、呼び出し元に送信します。 これは、応答を生成するために必要なすべての作業を省略するので、 結果として、これらのページのパフォーマンスを 2 倍から 3 倍増加できます。

高レベルの出力キャッシュのディレクティブを使用する ASP.NET のサンプル ページは、次のとおりです。 ここでは、 適切な HTTP キャッシュ ヘッダーを追加するだけでなく、 指定した制限時間に到達するまで応答を保存して、 複数の要求でその応答を再利用します。

<%@ OutputCache Duration="10" VaryByParam="none"%>

<Script runat="server">
  Public Sub Page_Load()
   CreatedStamp.InnerHtml = Now()
   ExpiresStamp.InnerHtml = Now().AddSeconds(10)
  End Sub
</Script>

<Font size="6">Output caching for 10 seconds...
  <HR size="1">
  Output Cache created: <Font color="red"><B id="CreatedStamp" runat="server">
    </B></Font>
  <BR>
  Output Cache expires: <Font color="red"><B id="ExpiresStamp" runat="server">
    </B></Font>
</Font>

上記のサンプル ページでは、 高レベルの OutputCache ディレクティブを、 必要な 2 つのパラメータ DurationVaryByParam と一緒に使用しています。 基本的に、これは以下のことを示しています。

  1. 生成される応答は、 HTTP キャッシュ ヘッダー : Cache-Control : public (誰でもキャッシュできることを意味します) と HTTP ヘッダー : Expires : (要求の時刻と有効期間値) を持ちます。
  2. 生成される応答は、ASP.NET キャッシュに追加され、 10 秒間 (有効期間値) またはファイル自体が変更されるまで有効です。 10 秒という期間内は、 このページに対するすべての要求がキャッシュされている応答を受け取ります。

このページを実行すると、最初の要求では現在時刻が表示されますが、 その後の 10 秒間に発生する要求はキャッシュから処理されるので、 時刻の値が更新されないことに気付きます。 10 秒後には、次の要求が再度ページを実行して、最初からやり直します。

ASP.NET 出力キャッシュ機能の説明に移る前に、 ここで何が行われているかを見てみましょう。

処理の流れ

図 1 は、ASP.NET がページを処理し、 出力をキャッシュするときに何が行われているかを概説しています。

図 1 : ASP.NET がページを処理し、出力をキャッシュするときに行われていることの概要

では、上図で何が行われているか見ていきましょう。

キャッシュを使用しない要求 :

  1. 要求が ASP.NET ページに送信されます。 要求は ASP.NET Http Runtime に渡されて、 登録されているモジュールで処理され、ページ ハンドラに渡されます。
  2. ページ ハンドラは、ディスク上にプリコンパイル済みのページ クラスを検出できません。 そのため、ページ ハンドラはファイルを取得して、そのファイルを解析のために ASP.NET エンジンに渡す必要があります。
  3. ASP.NET Page エンジンは、ファイルを解析して、ページ クラスを生成します。
  4. ページ クラスを .NET アセンブリにコンパイルして、ディスク上にキャッシュします。
  5. 要求されたページのクラスのインスタンスを作成します。
  6. 要求されたページ クラスから生成された応答を最初の呼び出し元に返します。

    出力キャッシュを使用しない次の要求 :

  7. 要求が ASP.NET ページに送信されます。
  8. ページ ハンドラがキャッシュされているクラスを検出して、インスタンスを作成します。
  9. 要求されたページ クラスから生成された応答を呼び出し元に返します。

    出力キャッシュを使用する要求 :

  10. 要求が ASP.NET ページに送信されます。
  11. 最初の要求では、手順 1 から手順 6 までが繰り返されます。
  12. 生成された応答が ASP.NET キャッシュに追加されます。
  13. 応答を呼び出し元に返します。

    出力キャッシュを使用する次の要求 :

  14. 要求が ASP.NET ページに送信されます。
  15. 要求が Http モジュールによって処理されるとき、 キャッシュ モジュールをパススルーします。 ページの応答が ASP.NET キャッシュ内部で検出される場合、 応答はキャッシュ API からコピーされ、呼び出し元に直接返されます。

ご覧のように、出力キャッシュを使用するかどうかにかかわらず、ASP.NET は非常に効果的に機能します。 可能性のあるパフォーマンスの増加は、一目瞭然です。 出力キャッシュを使用すると、ASP.NET はあまり動作せずに要求を満たすので、応答をより速く送信できるようになります。

ASP.NET 出力キャッシュを使用するために示した機能は強力です。 データベースに接続して、年間の製品販売高の計算など、優れた SQL クエリを実行する ASP.NET ページを想像できます。 ASP.NET 出力キャッシュをこのページに適用した場合、 最初の要求によって、一度だけ応答の作成にコストがかかります。 時間、キーまたはファイルに基づく依存関係により応答の期限が切れるまで、 または基本的なファイルが変更されるまで、 ページ全体や優れた SQL 呼び出しの実行にコストをかけることなく、 次の要求をキャッシュから満たすことができます。

高レベルの ASP.NET 出力キャッシュのディレクティブを使用しているので、 説明した機能は強力であるように思えますが、 それ以上に多くの使用可能な機能があります。 実際に ASP.NET 出力キャッシュをアプリケーションに適用する方法を見てみましょう。

ASP.NET 出力キャッシュを適用する

タイトルからわかるように、この資料は 2 部完結のシリーズの第 1 回目です。 第 1 回目では、ページ出力キャッシュで使用する高レベルの API に注目しています。 次の記事では、少し掘り下げて、Response.Cache の低レベルの API について見ていくことにします。

高レベルのキャッシュ API は、ページ ディレクティブを使用してアクセスできます。 ページ ディレクティブは、次の形式で表します。

<%@ [DirectiveName] [attribute]=[value] [attribute]=[value] … %>

出力キャッシュ用のディレクティブは OutputCache です。 このディレクティブを使用するときには必ず 2 組の属性と値の組が必要です。

  • Duration : ページが出力キャッシュされている時間 (秒単位)。 ページがまだキャッシュに存在しない場合、 要求の時刻を取得し、秒数を加えることによってこの時間が計算されます。 ASP.NET キャッシュ API について既に精通している場合、 キャッシュ内部の時間の依存関係を使用して、 新しい Cache エントリを単純に作成していることがわかります。 有効期間の値は、HTTP がヘッダーを期限切れにすることも設定します。
  • VaryByParam : この属性は、HTTP POST または HTTP GET が送信する名前と値の組に基づいて作成する、 ページのキャッシュ数を制御できるようにします。 既定値は、"None" です。 "None" は、ページを 1 種類だけキャッシュに追加することを意味するので、 すべての HTTP GET パラメータと HTTP POST パラメータは単純に無視されます。 "None" の反対の値は "*" です。 アスタリスクは、渡されるすべての名前と値の組を使用して、 ページのキャッシュを作成することを意味します。 ただし、パラメータに名前を付けることによって粒度を制御できます (複数のパラメータ名はセミコロンを使用して区切ります)。

VaryByParam についてもう少しお話しましょう。

VaryByParam を使用する HTTP GET または HTTP POST パラメータによる変更

たとえば、米国 50 州の天気予報を表示できる Web アプリケーションを構築するとします。 このアプリケーションは、Default.aspx という 1 つのページに完全にカプセル化されています。

Default.aspx では、州のドロップダウン リストをユーザーに示します。 ユーザーはドロップダウン リストから州を選択して、 そのドロップダウン リストの値を default.aspx に返します。 たとえば、State=WA や State=TX と表します。 説明を簡単にするために、 HTTP GET を使用してデータを送信すると仮定しましょう。 項目を選択後、 要求が "…default.aspx?State=WA" としてサーバーに送信されます。

注意     ここでの既定の動作では、HTTP POST を使用します。 名前と値の組は HTTP ヘッダーで送信されるので見えることはありません。

天気予報は一日に一度だけ更新され、 たとえば、複雑な SQL クエリなどにより、天気予報の生成にはコストがかかるとしましょう。

次のディレクティブを default.aspx に追加すると、 "default.aspx"、"default.aspx?State=WA"、"default.aspx?State=TX" など、 1 つ 1 つのページが互い独立して、3 時間キャッシュされます。

<%@ OutputCache Duration="10800" VaryByParam="State" %>

とても簡単でしょう?

さて、ページが少し複雑になるとしましょう。 州を選択する機能だけではなく、 特定の都市の選択機能も追加します。 州や都市のパラメータに基づくページ出力キャッシュに対して、ディレクティブを次のように変更します。

<%@ OutputCache Duration="10800" VaryByParam="State;City" %>

VaryByParam は、 HTTP GET または HTTP POST によって送信されるパラメータに基づいて、 同じファイルを複数キャッシュできるようにします。 "*" はあまり頻繁にアクセスされないページの出力キャッシュを作成する可能性があるので、 アスタリスクの使用を避けることをお勧めします。 VaryByParam を具体的にすればするほど、 頻繁にキャッシュから送信できることを覚えておいてください。 たとえば、州のみを指定すると、キャッシュには 51 種類のページ (50 州のページとパラメータを持たないページ) が存在します。 都市のパラメータを追加し、 州ごとに平均 15 の都市があると想定すると、 キャッシュ ページは突如 751 ページになります。 キャッシュによってメモリが圧迫されると、キャッシュは適切な作業を行います。 つまり、自動的に項目を削除します。 しかし、VaryByParam を使用するときに何が行われているかを理解しておく必要があります。

VaryByHeader を使用する HTTP ヘッダーによる変更

これで、州と都市の両方による 3 時間のドキュメント出力キャッシュをサポートする天気予報のページを構築しました。 さらに、そのページで複数の言語をサポートする必要も生じたとしましょう。 たとえば、海外からの観光客や訪問者が米国に訪れる前に、このページを使用する場合を考えます。

ページのテキストを表示するのに使用する言語は、 Accept- Language HTTP ヘッダーの値によって決まります。 たとえば、フランス語は FR-FR、英語は EN-EN などと表します。

ページのアプリケーション ロジックを適切に記述すると、 その言語に基づいてコンテンツを表示できます。 その後、別の高レベルの出力キャッシュのディレクティブを使用することにより、 HTTP ヘッダーの Accept-Language に基づいて、 出力キャッシュごとに異なるページをキャッシュするように指示できます。

<%@ OutputCache Duration="10800" VaryByParam="State;City" 
  VaryByHeader="Accept-Language" %>

VaryByHeader も VaryByParam と同様に、 セミコロンで区切られたリストになることがあります。 出力キャッシュは、パラメータと HTTP ヘッダーに基づいて、そのページの複数のキャッシュを作成します。

VaryByCustom を使用するブラウザの種類による変更

この天気予報アプリケーションは、これまで数か月間立派に動作してきました。 天気予報の会社では、アプリケーションの使用パターンが気になってきました。 会社が気が付いた傾向の 1 つとして、 大多数のユーザーが Internet Explorer を使用していますが、 モバイル デバイス (WML 対応の電話など) を使用して、サイトにアクセスしようとしているユーザーも多く存在します。 このような傾向がわかったので、 アプリケーションを拡張して、より適切に Internet Explorer をサポートし、 さらにスマート ナビゲーション機能をサポートすることを決定します。 そのため、アプリケーションにコードを追加して、WML デバイスをサポートします。

アプリケーションにアクセスしているブラウザの種類に基づいて、 出力キャッシュされたページを変更できるようになりました。 この機能をサポートするには、次の属性と値の組をディレクティブに追加します。

<%@ OutputCache Duration="10800" VaryByParam="State;City" 
  VaryByHeader="Accept-Language" VaryByCustom="browser" %>

VaryByCustom 属性には、いくつかの特別な使用方法があることに注意してください。 そのため、この属性を VaryByBrowser とは呼びません。 これについて以下に説明します。

VaryByCustombrowser に設定すると、 Internet Explorer 6 や Netscape Navigator 4 など、 呼び出し元ブラウザのメジャー バージョン番号によってキャッシュされる応答が異なります。 ブラウザの種類が異なれば、ページの異なるバージョンのキャッシュを受け取ります。

ここまで説明したように、 Page Output Cache API のデザインは、 高レベルの API を簡単に使用できるように考慮されています。 ただし、 VaryByParamVaryByHeader、および VaryByCustom の制約を受けます。 対処したい別の事例があったらどうしましょうか? ユーザーがサイトの顧客かどうかによって出力を変更したい場合どうしましょうか? たとえば、株価情報のサイトを構築する場合に、 使用料を支払っている顧客には NASDAQ レベル 2 の相場を提供しますが、 使用料を支払っていない購読者には (20 分程度) 古い相場を提供する場合があります。 依然として、このレベルの機能も実現できます。 この機能は、VaryByCustom で名前を取得することによって実現します。 カスタム設定によって変更できます。

独自の変更 :VaryByCustom のオーバーライド

上記では、VaryByCustom の既定値が browser であると述べましたが、 test のように、別の値を配置すると、何が起こるでしょう? 実はコードを記述してこの新しい値を処理するまで、何も起こりません。

VaryByCustom を処理するコードの実装は、 オーバーライド可能であることがマークされます。 オーバーライドは、HttpModule または global.asax のいずれかの内部で行う必要があるので、 global.asax に手を加えることが最も簡単な方法です。 このオーバーライドを実行する Visual Basic® .NET の関数は、次のとおりです。

Overrides Public Function GetVaryByCustomString(context As HttpContext, _
  arg As String) As String
  [implementation details]
End Function

出力キャッシュがコードを実行して VaryByParam を処理するときに、 内部関数ではなく、オーバーライドされた GetVaryByCustomString を使用します。 この関数は、2 つのパラメータを受け取ります。

  • Context : 現在の要求の HttpContext のインスタンス。
  • Arg : VaryByCustom="[value]" で設定された文字列の値。

Context は、 Session、Cache、Request や Response など、すべての組み込みクラスのインスタンスへのアクセスを提供します。 arg リストには、任意の数の値を指定できますが、 これらの値を区切る方法 (つまり、セミコロンの使用) は、文字列を分割する必要のある開発者が決定します。

引数に基づいて、スイッチ ロジックを使用して (以下に示す VaryByCustom="browser" によって実現されるブラウザのマイナー バージョンとメジャー バージョンによる変更を参照)、 ページの指定したキャッシュ インスタンスに限定される要求を一意に識別できる文字列を返します。

Overrides Public Function GetVaryByCustomString(context As HttpContext, _
  arg As String) As String
  If (arg = "minorversion") Then
    return "Version=" + context.Request.Browser.MinorVersion.ToString()
  End If
End Function

まとめ

ASP.NET ページ出力キャッシュによって、Web アプリケーションは高速になります。 ページ出力キャッシュの使用は、高レベルの API と低レベルの API で考慮されたデザインによって簡単になります。

次回の "Nothin' but ASP.NET コラム" では、 ASP.NET 出力キャッシュの説明を続けて、 Reponse.Cache の低レベルのキャッシュ API の使用に注目します。 高レベルの API は出力キャッシュの事例の 90 パーセントを解決しますが、 低レベルの API によって、いくつか興味深い事例を実行できるようになります。

関連情報

HTTP メッセージに HTTP キャッシュ ヘッダーなどが含まれているときのさまざまなアプリケーションの動作方法など、 HTTP キャッシュについて詳しく知りたい場合、 Ari Luotonen の著書『Web Proxy Servers』(Prentice Hall, ISBN: 0136806120) をぜひお勧めします。

ASP.NET に関するすべての事項についての優れた情報源は、 公式の ASP.NET Web サイト http://www.asp.net/ です。 ASP.NET Web サイトで、多くの役立つ情報、 ダウンロードへの制御、ユーザーの一覧、ホスティング企業などが見つかります。


Rob Howard は .NET Frameworks チームの ASP.NET プログラム マネージャです。 彼がコードやコラムを書いていないときは、いつも家族と過ごすか東ワシントン川でフライ フィッシングを楽しんでいます。



Microsoft