ETag
使い方
ETag ヘッダーは、リソースの各バージョンに一意なタグを割り当てます。リソースが変わると、ETag の値も変わります。クライアントはキャッシュしたレスポンスの ETag を保存し、以降のリクエストの If-None-Match リクエストヘッダーでその ETag を返します。ETag がまだ一致すれば、サーバーはボディのない 304 Not Modified を返し、クライアントはキャッシュ済みのコピーを再利用します。ETag がもう一致しなければ、サーバーは更新後のレスポンス全体を送ります。
ETag に基づく検証は、Last-Modified による日付ベースの検証より信頼できます。強いエンティティタグは、ある表現の正確な内容に結び付いた不透明な文字列であるのに対し、更新のタイムスタンプは 1 秒の粒度しかなく、時計のずれの影響も受けます。ETag と Last-Modified の両方が存在する場合、HTTP 標準の要求どおり、再検証では ETag が優先されます。
ETag は、安全でないメソッドにおける並行性制御も可能にします。If-Match を伴う PUT リクエストは、更新が期待されたバージョンにのみ適用されることを保証します。クライアントが最後に ETag を取得した後にリソースが変更されていた場合、サーバーは 412 Precondition Failed を返し、更新の喪失(lost update)問題を防ぎます。
ETag の値はサーバーが生成します。一般的な方法には、コンテンツのハッシュ、バージョンのカウンター、ファイルの更新時刻とサイズの組み合わせがあります。値は二重引用符で囲み、弱い比較を示すために任意で W/ を前置します。
注意
Google は、キャッシュの意向を示す方法として Last-Modified よりも ETag の使用を推奨しています。ETag は日付の書式に関する問題を避けられるからです。ETag と Last-Modified の両方が存在する場合、Google のクローラーは HTTP 標準の要求どおり ETag の値を使います。CMS のような他のアプリケーションもこれらのヘッダーに依存するため、両方のヘッダーを設定することは依然として推奨されます。個々の Google のクローラーやフェッチャーは、関連する製品の必要に応じてキャッシュを利用します。Googlebot は Google 検索のために URL を再クロールする際にキャッシュをサポートします。
強い ETag
強い ETag は、リソースの 2 つの表現がバイト単位で同一であることを保証します。同じ強い ETag を持つ 2 つのリソースは、バイトのレベルで交換可能です。強い ETag は範囲リクエストとすべての比較方法に適しています。
ETag: "abc123"
弱い ETag
W/ を前置した弱い ETag は、バイトレベルの同一性ではなく意味的な等価性を示します。同じ弱い ETag を持つ 2 つのリソースは意味の上で等価とみなされますが、内容が完全に同一とは限りません。弱い ETag は、空白の変化やボディ内のタイムスタンプの更新といった細かな違いを許容するため、生成が容易です。弱い ETag はバイト範囲リクエストには有効ではありません。
ETag: W/"v2.6"
例
サーバーがレスポンスとともに ETag を返します。クライアントは将来の再検証のためにこのタグを保存します。
HTTP/1.1 200 OK
ETag: "33a64df5"
Content-Type: text/html
Cache-Control: max-age=3600
クライアントが If-None-Match を使って再検証します。サーバーはそのタグを現在のリソースのバージョンと照合します。
GET /page HTTP/1.1
If-None-Match: "33a64df5"
リソースは変更されていません。サーバーはボディのない 304 を返し、クライアントはキャッシュを再利用します。
HTTP/1.1 304 Not Modified
ETag: "33a64df5"
Cache-Control: max-age=3600
楽観的並行性制御のために If-Match を使う PUT リクエストです。リソースがクライアントの保持する ETag となお一致する場合にのみ、更新が進みます。
PUT /api/document/42 HTTP/1.1
If-Match: "33a64df5"
Content-Type: application/json
ボディの細かな差異(埋め込まれたタイムスタンプなど)が許容されるリソースでの弱い ETag です。W/ の接頭辞が弱い比較のセマンティクスを示します。
ETag: W/"2024-11-22-v3"
トラブルシューティング
ETag に関連する問題は、再検証の失敗、想定外の完全ダウンロード、分散インフラをまたいだ並行性の衝突として現れます。
負荷分散されたサーバー間で ETag が一致しない。生成のアルゴリズムが inode 番号、タイムスタンプ、サーバー固有のデータを含む場合、ロードバランサーの背後にある異なるサーバーが同じリソースに対して異なる ETag を生成します。クライアントはサーバー A のレスポンスをキャッシュしたのに、サーバー B に対して再検証を行い、B が異なる ETag を返して完全なダウンロードを強いる、といったことが起こります。Apache では、既定の ETag に inode、size、mtime が含まれます。inode の構成要素を取り除いてください。
FileETag MTime Size
nginx では、ETag は最終更新時刻とコンテンツ長から導かれるため、同じファイルの内容を共有するサーバー間で一貫した値が生成されます。すべてのノードでファイルのデプロイのタイムスタンプが一致するようにしてください。
IIS は “filetime:changenumber” の形式で ETag を発行します。例えば “04c2528da58dc1:0” です。前半はファイルの最終更新時刻である Windows の FILETIME を 16 進で符号化したものです。ChangeNumber の接尾辞は IIS の構成状態を追跡します。IIS 7 以降は常に :0 を発行します。このカウンターを駆動していたメタベースのプロパティは IIS 6 で廃止され、非ゼロの値を露出させる applicationHost.config や web.config の設定は存在しません。したがって、ロードバランサーの背後にあるノードは同じファイルに対して一致する ETag を発行します。IIS 6 以前はメタベースの編集やサービスの再起動時に ChangeNumber を増加させるため、ノード間でずれが生じ、ノードをまたいだ再検証が 304 ではなく 200 を返します。各 IIS 6 のノードで cscript adsutil.vbs set w3svc/etag_changenumber 0 によって値をリセットするか、接尾辞を取り除いて ETag が FILETIME のみを反映するようにしてください。実環境で観測される非ゼロの接尾辞は、いずれも IIS 6 のオリジンを示しています。
弱い ETag によって 304 ではなく完全なレスポンスが返る。弱い ETag(W/”…”)は意味的な等価性を示しますが、バイト範囲リクエストはサポートしません。弱い ETag による If-None-Match のリクエストは、キャッシュの再検証には問題なく機能します。サーバーが 304 ではなく完全な 200 のレスポンスを返す場合、サーバー側の比較ロジックが誤っている可能性があります。サーバーが弱い比較関数を使って弱い ETag を比較しているか確認してください。この関数は照合の際に W/ の接頭辞を無視します。
クライアントが If-None-Match を送らない。ブラウザが If-None-Match を送るのは、キャッシュされたレスポンスに ETag ヘッダーが含まれている場合だけです。最初のレスポンスに ETag がないと、以降のすべての再検証が行われません。curl -I https://example.re/resource でオリジンのレスポンスを確認し、ETag ヘッダーが存在することを確かめてください。nginx や Apache のような静的ファイルのサーバーは ETag を自動的に生成します。アプリケーションサーバーや API のフレームワークでは、コード内で明示的に ETag を生成する必要があることがよくあります。
CDN が ETag を取り除くか改変する。一部の CDN は処理の過程で ETag の値を変えます。Cloudflare は Brotli 圧縮や画像の最適化といった変換を適用する際、強い ETag を弱い ETag に変換します。AWS CloudFront はオリジンからの ETag をそのまま通します。オリジンで ETag を確認し(curl -I https://origin.example.re)、CDN のエッジ(curl -I https://cdn.example.re)と比較してください。CDN が ETag を改変する場合でも、CDN がその対応関係を保持するため CDN を介した再検証は機能します。CDN によって改変された ETag でオリジンへ直接再検証すると失敗します。
Apache と nginx では ETag の生成方法が異なる。Apache は既定で inode-mtime-size を使い、“1a2b3c-4d5e-6f7a8b9c” のような ETag を生成します。nginx は 16 進の mtime-content-length を使い、“5f4dcc3b-d8” のような ETag を生成します。サーバー間を移行すると、キャッシュ済みのすべての ETag が無効になり、すべてのクライアントで完全なダウンロードが発生します。サーバーの移行後はキャッシュのウォームアップ期間を見込んでください。API のレスポンスについては、サーバーに依存しない値を生成するために、コンテンツのハッシュ(MD5 や SHA-256)から ETag を生成してください。
注意
Apache の既定の ETag 生成にはファイルの inode 番号が含まれます(FileETag INode MTime Size)。inode 番号はサーバーのファイルシステムの情報を漏らし、フィンガープリンティングやサーバー間の相関付けを可能にします。OWASP は inode に基づく ETag を情報漏えいのリスクとして指摘しています。Apache の設定で FileETag MTime Size を使い、計算から inode を取り除いてください。
注意
SEO とキャッシュに関する支援については、元 Google の SEO コンサルタント Search Brothers にご相談ください。