HTTP Caching

この頁では、複雑なHTTP/1.1キャッシングについて、特に「キャッシュ期限モデル」と「キャッシュ検証モデル」について解説します。

キャッシュとは

キャッシュ、及びキャッシュに関する専門用語については、RFC 2616のsection 1.3の中で定義されています。 RFC 2616では、キャッシュの仕様をリソースの状態に矛盾が無いように細かく規定していますが、この中にはキャッシングを解説するにあたり頻繁に使われる専門用語がありますので、これを読むためにはそれらの用語の意味を確実に理解していなければいけません。

キャッシュ {cache}
プログラムがレスポンスメッセージをローカルに記録しておく場所であり、それらメッセージの保存、検索、削除を管理するサブシステムを指す。 キャッシュは、同様のリクエストが起きた際にレスポンス時間やネットワーク帯域幅の消費の軽減のために、キャッシュ可能なレスポンスを保存する。 どのようなクライアントもサーバもキャッシュを持つ事は出来るが、トンネルとして振る舞っているサーバはキャッシュを使用できない。
キャッシュ可能 {cacheable}
レスポンスは、もしキャッシュが後続のリクエストに答えるという目的での使用のために、レスポンスメッセージのコピーを保存する事が許されるならばキャッシュ可能であるという。 HTTP レスポンスのキャッシュ使用の決定の決まりは section 13にて定義されている。 例えリソースがキャッシュ可能でも、キャッシュは特定のリクエストのためにキャッシュされたコピーを使うかどうかについて制限を受ける可能性がある。
ファーストハンド {first-hand}
レスポンスが、オリジンサーバ、あるいは一つ以上経由したプロクシから不必要な遅れ無しに届くならば、それをファーストハンドであるという。 また、オリジンサーバで有効性を直接チェックされたレスポンスもファーストハンドであるという。
明示的有効期限 {explicit expiration time}
オリジンサーバが、エンティティの有効性の再確認無しにキャッシュを返すべきではないとしている時刻。
帰納的有効期限 {heuristic expiration time}
有効期限が指定されていない時に、キャッシュによって指定される有効期限。
経過時間 {age}
レスポンスの経過時間とは、それがオリジンサーバから送られてから、あるいはオリジンサーバによって十分に有効性が確認された時からの時間を指す。
有効期間 {freshness lifetime}
レスポンスが生成されてから有効期限までの時間の長さ。
新鮮である {fresh}
レスポンスは、その経過時間が有効期間を経過していないものを新鮮であるという。
新鮮でない {stale}
レスポンスは、その経過時間が有効期間を経過したものを新鮮でないという。
意味的に透過である {semantically transparent}
キャッシュは、パフォーマンスの向上以外に、リクエストしたクライアントにもオリジンサーバにも影響が無い時、特定のレスポンス関して、意味的に透過な方法で振る舞う。 キャッシュが意味的に透過な時、クライアントはオリジンサーバから直接処理された時に受け取るであろうリクエストと全く同じレスポンスを受け取る。 (ホップバイホップヘッダを除く)
バリディタ {validator}
キャッシュ内にあるリソースが、エンティティのコピーと同等かどうかがわかるとされているプロトコルエレメント。(例えば、エンティティタグや最終更新時刻など)

このうち、理解し難い用語は意味的に透過バリディタの二つでしょう。

意味的に透過とは、結局キャッシュがある時と無い時でその振る舞いが変わらない、すなわちクライアントやオリジンサーバからはまるでキャッシュが存在しないかのように見える時、そのキャッシュは意味的に透過と言う事ができます。

またバリディタとは、日本語に訳すとすれば正当性検証子であり、そのリソースの正当性、有効性を検証するためのものと言えるでしょう。

キャッシュはユーザエージェント単位でも利用されていますが、これを複数のコンピュータ間で共有するために使われるのがプロクシです。 プロクシがキャッシュとして働く場合の振る舞いについては、section 1.4 の中で記されています。

トンネルとして動作していない通信のすべてのパーティは、内部的なキャッシュやリクエスト処理に使用できる。 キャッシュの効果とは、もし連鎖上に連なるある一つがそのリクエストに適用できるキャッシュされたレスポンスを持っているなら、リクエスト/レスポンス連鎖を短縮する事である。 以下では、もし B が、UA や A がキャッシュしていないリクエストに対する O からの (C を経由した) 以前のレスポンスのキャッシュされたコピーを持っている場合の結果となる連鎖を説明している。

    request chain ---------->
 UA -----v----- A -----v----- B - - - - - - C - - - - - - O
    <--------- response chain

すべてのレスポンスがキャッシュ可能であるわけではなく、いくつかのリクエストではキャッシュの動作への特別な要求を行う修飾子を含む事ができる。 キャッシュの動作やキャッシュ可能なレスポンスに対する HTTP の要求は section 13 で定義されている。

プロクシのキャッシングは、何度も利用されるリソース程、効果が大きく現れます。 ただし、CGIな どによって動的に生成されるリソースや、ニュースサイトのようなリアルタイム性が要求されるサイトのリソースは、リソースがキャッシュ可能で無いかもしれ ませんし、またもしキャッシュ可能であっても、それがキャッシュに残っているせいで最新のリソースを利用する事ができないなどの不都合が起こる可能性があ るので、キャッシュの利用者や管理者は注意が必要です。

キャッシュの正当性

「正当なキャッシュ」、つまりキャッシュとして使えるものを決定するためのメカニズムについて、section 13.1.1 に示されています。

正当なキャッシュは、以下の状況の一つに合うリクエスト (section 13.2.5, 13.2.6, 13.12 参照) に適した、キャッシュが持っている最新のレスポンスをもってリクエストに答えなければならない

  1. オリジンサーバがレスポンスの再検証によってオリジンサーバで返されたものとの等価性が確認されている (section 13.3)。

  2. "十分に新鮮" (section 13.2 参照) である。 既定の場合、これはクライアント、オリジンサーバ、キャッシュの最低限の新鮮度要求に合う事を意味する (section 14.9 参照)。 オリジンサーバがそう指定する場合、それはオリジンサーバ単独の新鮮度要求である。

    保存されたレスポンスがクライアントとオリジンサーバの両方の最も限定的な新鮮度要求から "十分に新鮮" で無い場合、そのようなレスポンスが禁じられている (例えば "no-store" キャッシュ指示子や、"no-cache" キャッシュリクエスト指示子等;section 14.9 参照) ので無ければ、深く考慮された環境ではキャッシュが適切な Warning ヘッダを伴ってそのレスポンスを返してもよい

  3. 適切な 304 (Not Modified), 305 (Proxy Redirect) かエラー (4xx5xx) レスポンスメッセージである。

キャッシュがオリジンサーバと通信できない時、レスポンスがキャッシュから正しく対応させる事ができるならば、正しいキャッシュは上記のものを返すべきであり、そうでなければ通信失敗があった事を示すエラーか警告を返さなければならない

つまり、あるデータがキャッシュとして使える場合には次の二通りあります。

  1. オリジンサーバから取得してから、十分に新鮮であるもの
  2. オリジンサーバに有効なリソースが存在する場合に、それがオリジンサーバにあるものと同一であると検証済みのもの

前者を期限{Expiration} モデル、後者を検証{Validation} モデルと言います。

キャッシュ期限モデル

あまりに古くなった情報はその価値が失われます。 従って、十分に新鮮であるもののみをキャッシュとして使うというものがキャッシュ期限モデルであり、section 13.2 に記述されています。

HTTP キャッシングは、キャッシュがオリジンサーバにリクエストを送るという事を完全に避ける事ができる時に最善に動作する。 リクエストを避けるための第一のメカニズムは、レスポンスが以降のリクエストを満足させるために使用する事ができるという事を含め、オリジンサーバが将来において明確な期限切れになる時間を提供する事である。 言い換えれば、キャッシュはサーバに先に接続する前に新鮮なレスポンスを返す事ができる。

我々が期待する事は、有効期限が切れる前にエンティティは意味的に重要な方法では変更無いであろう、という確信を持って、サーバがレスポンスに将来の明確な有効期限を割り当てているだろう事である。 これは普通、サーバが期限を注意深く選んでいる間は、意味的な透過性を維持する。

期限メカニズムは、キャッシュから取得したレスポンスのみに適用され、リクエストしているクライアントに直ちに転送されたファーストハンドのレスポンスには適用されない。

期限メカニズムとは、オリジンサーバがキャッシュの有効期限を定め、それを越えたキャッシュはオリジンサーバにそのキャッシュとしての正当性・有効性を確認しない限りはキャッシュとして使えないというものです。

有効期限には、オリジンサーバによって明示的に示される明示的有効期限の他に、帰納的有効期限があります。 帰納的有効期限について、13.2.2 をご覧下さい。

オリジンサーバは明示的有効期限を常に提供するわけではないので、HTTP キャッシュは典型的に、本当の有効期限を見積もるために (Last-Modified の時間のような) 別のヘッダ値を使うようなアルゴリズムを使って、帰納的有効期限を割り当てる。 HTTP/1.1 仕様書では詳細なアルゴリズムを提供しないが、その結果への最悪の場合の制約を課す。 帰納的有効期限は意味的な透過性を損なうかもしれないので、それらは慎重に使用されるべきであり、我々はオリジンサーバが可能な限り明確な有効期限を提供するという事を推奨する。

帰納的有効期限とはキャッシュによる自主的な推測の有効期限なので、これを使用した場合、例えばオリジンサーバのリソースは既に変化しているにもかかわらず、キャッシュは古いリソースをクライアントに返してしまうかもしれません。 従って、オリジンサーバはできるだけ明示的有効期限を示すべきだし、キャッシュも帰納的有効期限の使用には慎重にならなければいけません。

キャッシュの有効期限を示すためには、HTTP ヘッダを使います。 HTTP/1.0 において、キャッシュを制御するための HTTP ヘッダは、PragmaExpires があります。 受信者は、Pragma: no-cache というヘッダを受信した場合、そのレスポンスをキャッシュしてはいけません。 また Expires の値は、リソースが古くなったとみなす HTTP日付で、リソースの有効期限を表しています。

HTTP/1.1 ではこれらの他に、より細かくキャッシュ制御できるように Cache-Control ヘッダが新設されました。 Cache-Control については、section 14.9 に定義されています。

Cache-Control 一般ヘッダフィールドは、リクエスト/レスポンス連鎖上のすべてのキャッシングメカニズムが従わなければならない指示を記述するために使用される。 キャッシュがリクエストやレスポンスに不利になるように干渉させないような振る舞いを指定する。 これらの指示は、常に既定のキャッシングアルゴリズムを上書きするものである。 キャッシュ指示は、リクエスト中にある指示があっても、それと同じ指示がレスポンスで与えられるという事を暗に意味しない、という意味において単向性である。

HTTP/1.0 キャッシュは、Cache-Control を実装してせず、Pragma: no-cache (section 14.32 参照) しか実装していないかもしれない事に注意せよ。

キャッシュ指示は、リクエスト/レスポンス連鎖上のすべての受信者に適用できるため、プロキシやゲートウェイアプリケーションを、そのアプリケーションの重要性に関係なく通り抜けなければならない。 特定のキャッシュのために cache-directive を記述するのは不可能である。

 Cache-Control   = "Cache-Control" ":" 1#cache-directive

 cache-directive = cache-request-directive
      | cache-response-directive

 cache-request-directive =
        "no-cache"                          ; Section 14.9.1
      | "no-store"                          ; Section 14.9.2
      | "max-age" "=" delta-seconds         ; Section 14.9.3, 14.9.4
      | "max-stale" [ "=" delta-seconds ]   ; Section 14.9.3
      | "min-fresh" "=" delta-seconds       ; Section 14.9.3
      | "no-transform"                      ; Section 14.9.5
      | "only-if-cached"                    ; Section 14.9.4
      | cache-extension                     ; Section 14.9.6

  cache-response-directive =
        "public"                               ; Section 14.9.1
      | "private" [ "=" <"> 1#field-name <"> ] ; Section 14.9.1
      | "no-cache" [ "=" <"> 1#field-name <"> ]; Section 14.9.1
      | "no-store"                             ; Section 14.9.2
      | "no-transform"                         ; Section 14.9.5
      | "must-revalidate"                      ; Section 14.9.4
      | "proxy-revalidate"                     ; Section 14.9.4
      | "max-age" "=" delta-seconds            ; Section 14.9.3
      | "s-maxage" "=" delta-seconds           ; Section 14.9.3
      | cache-extension                        ; Section 14.9.6

 cache-extension = token [ "=" ( token | quoted-string ) ]

ある指示子がどんな 1#field-name パラメータも無く現れた時、その指令はリクエストやレスポンス全体に適用される。 ある指示子が 1#field-name パラメータを伴って現れたら、指定されたフィールド (群) のみに適用され、リクエストやレスポンスの残りの部分には適用されない。 このメカニズムは拡張性を持っている。 HTTP プロトコルの将来のバージョンの実装は、これらの指示子に HTTP/1.1 で定義されていないヘッダフィールドを適用する事ができる。

cache-control 指示子は、これらの一般的なカテゴリの中に分類する事ができる。

Cache-Control はリクエストにもレスポンスに使われます。 Cache-Control の指示子は、通常リクエスト/レスポンス連鎖全体に対して有効ですが、指定の仕方によっては接続単位の制御も可能であり、すなわち "クライアントがキャッシングサーバに"、"オリジンサーバがキャッシングサーバに"、あるいは "キャッシングサーバがクライアントに" 等と、様々な場面でキャッシュの振る舞いを制御する事ができるようになります。

HTTP/1.1キャッシュはHTTP/1.0キャッシュに優先します。 従って、Cache-ControlExpires に優先します。

キャッシュ検証モデル

キャッシュサーバが、クライアントからリクエストを受けたリソースについて、それをキャッシュとして使用できるかを判断する際に、それがオリジンサーバにあるものと同一であるかどうか検証し、これを満たすものをキャッシュとして使うのがキャッシュ検証モデルであり、section 13.3 に記述されています。

キャッシュがクライアントのリクエストへのレスポンスとして使いたいような新鮮で無いエントリを持っている時、そのキャッシュされたエントリがまだ使用可 能かどうかを確かめるために最初にオリジンサーバ (かあるいは新鮮なレスポンスを持っている中間キャッシュ) へチェックしなければならない。 我々はこれをキャッシュエントリの "検証{validating}" と呼ぶ。 もしキャッシュされたエントリで良ければ我々は完全なレスポンスの再転送によるオーバーヘッドを望まないし、キャッシュされたエンティティが適切で無けれ ば余分なラウンドトリップによるオーバーヘッドを望まないので、HTTP/1.1 プロトコルは条件付きメソッドの使用をサポートしている。

条件付きメソッドをサポートするための重要なプロトコルの機能は、"キャッシュバリディタ" に関するものである。 オリジンサーバが全体のレスポンスを生成する時、それにある種のバリディタを付けられ、キャッシュエントリと共に保存される。 クライアント (ユーザエージェントやプロクシキャッシュ) がキャッシュエントリに持つリソースに条件付きリクエストを作る時、リクエストに関するバリディタを追加する。

この時サーバはそのバリディタと現在のエンティティのバリディタを調べ、もしそれらが一致すれば (section 13.3.3 参照) 、エンティティボディを含まない特別なレスポンスコード (通常は 304 (Not Modified)) を返す。 そうで無ければ、完全なレスポンス (エンティティボディを含む) を返す。 従って、もしバリディタが一致すれば完全なレスポンスを転送する事は避けられるし、一致しなければ余分なラウンドトリップを避けられる。

キャッシュ検証モデルでは、キャッシュサーバがオリジンサーバに条件付きリクエストを行う事でリソースの検証を行います。 この時、もしリソースが変更されていれば、キャッシュを新しいものと入れ替えます。 この検証のために使うものがバリディタですが、バリディタに使われるものには Last-Modified ヘッダの値と、エンティティタグがあります。 エンティティタグに付いて、section 3.11 をご覧下さい。

エンティティタグは、同一の要求リソースからの二つ以上のエンティティを比較するために使用される。 HTTP/1.1 では、ETag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), If-Range (section 14.27) 各ヘッダフィールドで、エンティティタグを使う。 それらがキャッシュバリディタとして、どのよう使われ、比較されるかの定義は、section 13.3.3 にある。 エンティティタグは、それ自体は読んでも意味のわからない{opaque} 引用符で括られた文字列から成り、weakness インジケータが前方に付く場合もある。

 entity-tag = [ weak ] opaque-tag
 weak       = "W/"
 opaque-tag = quoted-string

"strong entity tag" では、もしそれらがオクテット文字によって同等の場合にのみ、リソースの2つのエンティティが共有できる

"W/" プレフィクスによって示される "weak entity tag" では、エンティティが等価であり、意味論においてそれぞれ重要な変更がなく互いを代わりに使う事が出来る場合のみ、リソースの 2 つのエンティティが共有できるweak エンティティタグは、弱い比較の時のみ使用される。

エンティティタグは、特有のリソースと関連付けられた全てのエンティティの全てのバージョンの中で一意{unique} でなければならない。 与えられたエンティティタグの値は、異なる URI へのリクエストから得られたエンティティのために使う事ができる。 異なる URI へのリクエストから得られたエンティティに同じエンティティタグの値を使っているからといって、それらのエンティティの同等性を暗に意味するものではない。

バリディタには、強いバリディタ弱いバリディタの二種類があり、section 13.3.3 に記述されています。

オリジンサーバやキャッシュの両方でそれが同じエンティティを表すかどうかを決定するために二つのバリディタを比較するため、通常はエンティティ(エン ティティボディかエンティティヘッダ) が何らかの理由で変わっていたら、それに関するバリディタも同様に変更しているだろうという事を期待できる。 これが真である場合、我々はこのバリディタを "強いバリディタ" と呼ぶ。

しかしながら、サーバがエンティティの意味的に重要な変更時にのみバリディタを変更し、重要でない側面にはバリディタを変更したがらない場合があるかもしれない。 リソースの変更時に常に変更されないバリディタは "弱いバリディタ" である。

エンティティタグは通常 "強いバリディタ" であるが、このプロトコルでは "弱い" エンティティタグを付けるためのメカニズムを提供している。 強いバリディタはエンティティが少しでも変更した時に変更されるもので、弱いバリディタはエンティティの意味が変更した時に変更される、と考える事ができる。 または、強いバリディタは特定のエンティティの識別子{identifier} の一部であり、弱いバリディタは意味的に等しいエンティティのセットの識別子の一部である、と考える事もできる。

(中略)

バリディタの "使用" は、クライアントがリクエストを生成し検証用ヘッダフィールドにバリディタを含む時、あるいはサーバが二つのバリディタを比較する時のどちらかである。

強いバリディタはいかなる状況にも使う事ができる。 弱いバリディタはエンティティの正確な等価性に頼らない状況においてのみ使う事ができる。 例えば、完全なエンティティの条件付き GET のためにどちらかの種類を使う事ができる。 しかし、別の方法ではクライアントが内部的に一致しないエンティティとなってしまうかもしれないので、サブレンジ回収のためには強いバリディタのみを使う事ができる。

クライアントは、単純な (非サブレンジ) GET リクエストの発行には弱いバリディタも強いバリディタも使う事ができる。 その他のリクエストの形式には弱いバリディタを使ってはならない

HTTP/1.1 では、バリディタをエンティティの比較に使いますが、これも強いバリディタと弱いバリディタによって、強い比較弱い比較があります。

HTTP/1.1 プロトコルがバリディタに定義している機能は比較のみである。 二つのバリディタ比較機能があり、これは比較状況が弱いバリディタの使用を許すかどうかに依存する。

強い比較機能
等しさを検討するため、両方のバリディタはあらゆる方法で同一でなければならなく、両方のバリディタは共に弱くない。
弱い比較機能
等しさを検討するため、両方のバリディタはあらゆる方法で同一でなければならない。 ただし、これらのどちらかもしくは両方は結果に影響する事なく "弱い" とマークされている。

明確に弱いと印されていなければエンティティタグは強い。 section 3.11 ではエンティティタグのための構文を与えている。

例えば、オリジンサーバ中のエンティティタグが"abcde"の時、キャッシュ中のリソースのエンティティタグが

となります。

同一性が肯定された場合、キャッシュは自身が保持するリソースを返します。 また、クライアントが条件付きリクエストを行っていた場合は、304 (Not Modified) を返し、オリジンサーバのリソースが変更されていない事を伝えます。

同一性が否定された場合、キャッシュはオリジンサーバからリソースを取得し、それをクライアントに返します。 その際、キャッシュはリソースを新しいものに入れ換え、今後再び同一性検証が行われる場合は、新しいキャッシュに対して行われなければいけません。

なお、再検証中にオリジンサーバが5xxレスポンスを返した場合について、section 13.8に記述されています。

もしキャッシュがエントリの正当性再検証を試みる一方で 5xx レスポンスを受信したら、リクエストしているクライアントにこのレスポンスを転送するか、あるいはまるでサーバが応答に失敗したかのように動作するかどちらかを行う事ができる。 後者の場合、キャッシュされたエントリが "must-revalidate" cache-control 指示子 (section 14.9 参照) を含んでいなければ、以前に受信したレスポンスを返す事ができる

参照文献

Webリソース

書籍