定義
同じデータを繰り返し受け取るとき、毎回ボディ全体をダウンロードしないために使う HTTP 機能である。サーバーがレスポンスボディのハッシュ(またはバージョン)を ETag ヘッダーとして付けると、クライアントは次のリクエストに If-None-Match: <etag> を載せて「これはそのままか?」と尋ねる。サーバーは内容がそのままならボディなしで 304 Not Modified だけを返し、クライアントは手元のキャッシュ副本を再利用する。
ETag は特定バージョンのリソースを識別する短い文字列タグ、条件付きリクエスト は「条件が合うときだけボディをよこせ」というリクエストを意味する。
なぜ必要か
症状: リストや詳細のような読み取りレスポンスをユーザーが頻繁に再リクエストするのに、内容が変わっていなくても毎回数 KB〜数十 KB の JSON をまるごとダウンロードし直す。モバイルや大量トラフィックではこれは無駄だ。
ETag/304 は「変わったか?」だけを尋ね、変わっていなければ ダウンロードバイト数をゼロ にする。ただし必ず知っておくべき限界がある。サーバーは 304 を返すためであっても 毎回クエリしてシリアライズしハッシュを計算 しなければならないので、ETag は ネットワーク転送だけを節約しサーバー演算は節約できない。サーバーの仕事まで減らすには、ボディを作る前に安価な指標で「変更なし」を先に判定する仕組みが別途必要だ。
動作原理
- body-hash ETag: レスポンスボディをハッシュ化してタグにする。実装は単純だがサーバーが毎回ボディを全部作らねばならない(転送のみ節約)。
- strong vs weak:
"abc"はバイトまで同一、W/"abc"は意味的に同一。ネゴシエーションキャッシュにはたいてい weak で十分だ。 - cheap validator: サーバー演算まで節約するには
max(updated_at) + countのような安価な指標を先に照会し、同じならボディ生成そのものをスキップする。
実務での適用
サーバー演算まで節約するバージョンは loadItems() の前に SELECT max(updated_at), count(*) で validator を先に照会し、同じなら loadItems() をスキップして 304 を返す。
トレードオフ
- 得られるもの: 再訪問・無変更時にダウンロードバイト数ゼロ。クライアントキャッシュと組み合わせると体感が速い。
- 得られないもの: body-hash 方式ではサーバーのクエリ・シリアライズコストがそのまま残る。
- 過剰投資リスク: ページが既に ISR/CDN の静的キャッシュでボディを覆っているなら、別途 ETag 層が与える利得は小さい。導入前にそのルートのキャッシュ戦略を先に見る。
使ってはいけない場合
- リクエストごとに内容が変わるリソース(リアルタイムフィード) — 304 がほとんど出ず無意味。
- 既に静的/ISR キャッシュがボディを覆うルート — ETag 層は重複投資。
- レスポンスが非常に小さく転送節約分が微々たる場合。
よくある間違い
- ETag はサーバー演算も節約すると誤解する(帯域だけを節約する)。
- リクエストごとに変わる値(タイムスタンプなど)をボディに混ぜて ETag が常に変わり 304 が出ない。
- strong/weak を区別せずプロキシ・圧縮と衝突する。
- validator なしで body-hash だけを使いながら「サーバー負荷が減る」と期待する。
関連概念
- service-worker-stale-while-revalidate — クライアント側キャッシュ層との組み合わせ
- cache-generation-stale-write-guard — キャッシュ世代/無効化で stale write を防止