図書館のホームページで今日の開館時間を変更したのに、一部の訪問者には以前の時刻が表示されているとしましょう。サーバーの元の内容が更新されていないのか、ブラウザーが保存した応答を再利用したのか、中間のサーバーが古い版を返したのかを、まず分けて考えます。画面を何度も再読み込みしたという事実だけでは、どの段階に原因があるかは分かりません。
HTTPキャッシュは受け取った応答を保存し、条件が合えば再び使う仕組みです。本稿では公開の開館時間表という架空の例を用いて、Cache-Controlの保存・再検証の指示とETag・304応答を結び付けます。以下の通信記録は理解のために簡略化した例であり、実在する図書館や特定のサーバーを測定した結果ではありません。あらゆるキャッシュをなくす処方よりも、どの応答をいつ再利用できるべきかを説明することが目的です。
1. 保存するか、確認して使うか、誰と共有するか
キャッシュ方針を読むときは、三つの問いを別々に書きます。応答を保存してよいか、次のリクエストで保存済み応答をそのまま使ってよいか、複数の利用者で共有してよいか、です。保存が許されても常に再利用できるとは限らず、新しさを確認しても他の利用者と共有できるとは限りません。HTTPキャッシュ標準RFC 9111はこれらを個別のディレクティブで定義します。以下はフィールド名を別の引数として付けないレスポンスディレクティブについての説明です。
| 判断する問い | 関連するレスポンスディレクティブ | 区別する意味 |
|---|---|---|
| 応答を保存してはいけないか | no-store | 後の再利用に向けた保存を禁止する指示 |
| 再利用の前に確認が必要か | no-cache | 保存できるが再利用前に検証の成功が必要 |
| 複数の利用者で共有できるか | private | 共有キャッシュの保存を除外し、プライベートキャッシュの保存までは禁止しない |
例えば同じURLの公開時間表を頻繁に変更する場合、保存済み応答があっても使用前に確認するという要件を検討できます。個人ごとの画面は、公開時間表とはまず共有条件が異なります。privateは共有キャッシュへの指示で、ログインや権限の確認を代行しません。目的の異なる設定を一文にまとめると、速度の改善中に情報の提供先を取り違えるおそれがあります。
方針表にはディレクティブだけでなく理由も記します。時間表は更新確認が必要な公開情報か、ロゴは内容が固定されたファイルか、同じURLに個人別の情報を載せるか、といった内容です。この表は全サイトに適用する完成済みの設定ではなく、実際の応答を分類する出発点です。ブラウザーやCDNなど、どの保存先に適用するかも分けて考えます。
2. 名前だけでno-cacheとno-storeを選ばない
no-cacheという名前は保存禁止と読まれがちですが、応答のno-cacheは保存済み応答を再利用する前に検証するという意味です。変更がないとサーバーが確認すれば、保存した本文を再び使える余地があります。一方、no-storeはその応答を保存しないように指示します。何を保持するかと次のリクエストで何を確認するかは別なので、二つの設定に同じ動きを期待してはいけません。
キャッシュでfreshとは、方針上、追加の検証なしで再利用できる状態を指します。その間に運営者が内容を変えていないという保証ではありません。max-ageで再利用可能な期間を定めれば、その期間内に元の内容を修正しても、保存済み応答が選ばれ続ける場合があります。許容できる更新遅延を先に決め、その要件を指示に結び付けます。任意の長い保存時間を性能改善の正解としてコピーしないようにします。
MDNのHTTPキャッシュ案内は、新しい応答にno-storeを付けても、同じURLですでに保存されている古い応答は削除されないと説明しています。古い応答がまだ有効で新しい応答を取得しに行かなければ、新しいヘッダーも届いていない可能性があります。no-storeを全キャッシュの消去やプライバシー保護の十分条件とは考えず、既存応答の有効期間と管理可能なキャッシュの更新手順も確認します。
3. ETagと304で一度の再訪問を追う
架空の時間表URL /hours.jsonが、最初は18時閉館を知らせるとします。次は必要な部分だけを残した説明用のHTTP/1.1形式です。実際の応答に必要な他のヘッダーは省略しています。hours-v1はこの例でサーバーが決めた表現の識別子であり、時刻を計算する式や実際のファイルハッシュを意味しません。
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache
ETag: "hours-v1"
{"closes":"18:00"}
ブラウザーがこの応答を保存した場合、次はETagの値を条件付きリクエストのIf-None-Matchで送れます。サーバーが選択した現在の表現と条件が一致し、変更されていないと判断すれば、304 Not Modifiedを返します。下のブロックはリクエストと対応する応答を続けて記した簡略記録です。304の後に新しい時間表JSONを付けて送る例ではありません。
GET /hours.json HTTP/1.1
Host: library.example
If-None-Match: "hours-v1"
HTTP/1.1 304 Not Modified
Cache-Control: no-cache
ETag: "hours-v1"
MDNのETag解説にあるように、この場合クライアントは保存した本文を使います。304に新しい本文はありませんが、リクエストと検証の通信は発生します。運営者が閉館時刻を19時に変更し、サーバー側の表現も変わったなら、古い条件に対して新しい本文と識別子を含む200応答を返す流れを期待できます。no-cacheを使っても毎回304になるわけではなく、データベース照会がなくなるわけでもありません。
確認すべきものは状態コード一つよりも、内容と識別子の対応です。内容が変わったのにサーバーが以前のETagを使い続ければ、誤った検証結果になる可能性があります。逆に内容が同じなのに識別子が変わり続ければ、本文を再送する回数が増える場合があります。生成方法と選択した表現との関係をサーバー側で確かめる必要があり、ETagが見えるだけで実装が正しいとは結論できません。
4. 頻繁に変わる文書と新しい名前の資産を分ける
時間表のように同じURLで内容が変わる文書と、内容が変われば新しいファイル名を付けるロゴやスクリプトでは、更新戦略が異なる場合があります。例えばlogo.a1.pngからlogo.b2.pngへ変える設計なら、新しい文書が新しいURLを参照する必要があります。ファイル名に識別子を入れながら既存ファイルの内容を上書きすると、内容とURLを一緒に管理するという約束が崩れます。
新しいファイルをアップロードしただけでは、訪問者が新しいURLをリクエストするわけではありません。受け取ったHTMLが古いロゴURLを参照し続けていれば、古い資産を再利用することがあります。そのため資産のキャッシュ設定だけでなく、それを参照する文書の更新経路も見ます。文書と資産を同じ保存時間にそろえるよりも、それぞれどの程度遅れて変わってよいかを説明できるようにします。
デプロイ中は古い文書と新しい文書が一時的に共存することもあります。新しい資産の準備前に新しい文書を公開したり、古い資産を早く削除しすぎたりすると、別の不具合が起きます。この架空の例では、新しい文書が参照するファイルの存在と、古い文書が参照するファイルへのアクセスを両方確認できます。ファイル名の変更はキャッシュ設計の道具であり、あらゆるデプロイ順序やロールバックの問題を自動的に解決するものではありません。
5. Networkパネルで仮説を一つずつ確かめる
直接確認するときは、まずキャッシュを許可した状態で基準の記録を残します。正確なURLとクエリ、画面に出る時間表の版、応答の状態、リクエストのIf-None-Match、応答のCache-Control・ETagを一緒に見ます。同じ名前でもリクエストヘッダーとレスポンスヘッダーは異なる主体の条件なので、どちらを読んだか記します。Chrome DevToolsのNetwork文書は、Headersでこれらを分けて見る方法を案内しています。
| 架空の試験状況 | 一緒に見る記録 | 早まって下してはいけない結論 |
|---|---|---|
| 同じ時間表を再度リクエスト | 条件付きリクエスト、状態、画面の時刻 | 304なので通信は一切なかった |
| 元の時刻を修正してリクエスト | 新しい本文、ETag、実際のリクエストURL | 保存成功と表示されたので全利用者に反映された |
| ブラウザーキャッシュを無効にしてリクエスト | 初回ダウンロードと通常の再訪問の差 | この結果が普段の再訪問速度と同じだ |
| 戻る操作で戻る | 新しいリクエストの有無と復元された画面 | 画面が出たので必ず新しいHTTP検証があった |
次に、ブラウザーキャッシュの無効化を別の条件として試験します。ChromeのDisable cache設定はDevToolsが開いている間に適用されるので、ツールを開いておいた状態と設定値を一緒に記録します。初回のダウンロードを調べるには便利ですが、通常の再訪問で保存済み応答がどう使われるかを見る試験とは異なります。設定を有効にしたか記録しないと、常に新しく取得する結果からキャッシュが働いていないと誤解する場合があります。再び許可した条件に戻って比較できるよう、実験条件を残します。
観察中に多くの修正とリクエストを同時に行わない方が、原因を絞りやすくなります。まず内容を変えない再訪問を確認し、その後に時刻の一項目だけを変え、応答の変化を見ます。文書URL、サーバー設定、ファイル名を同時に変えると、どの変更が結果を生んだか区別しにくくなります。予想した200や304が返らなければ、設定を追加する前に実際のリクエストと応答を読み直します。
6. 他の保存経路と完了条件も確認する
ブラウザーのHTTPキャッシュを無効にしても、サーバー前段のCDNやアプリケーションの他の保存経路まで消えるとは考えません。サービスワーカーがリクエストを処理するか、中間サーバーがどの応答を選ぶか、取得済みのデータをアプリが画面に保持するかも、別々に調べられます。同じURLだからといって、全ての層が同じ複製を使うと思わないことが大切です。
戻る操作も、新しいHTTPリクエストとは分けて確かめます。以前のセッションの画面をブラウザーが復元する場合、no-cacheが付いていても、期待した再検証リクエストが見えないことがあります。これを直ちにサーバーがヘッダーを無視した証拠にすると、原因を取り違えます。新規移動、再読み込み、戻る操作のそれぞれで実際にリクエストがあったか記録すれば、異なる現象を一つの問題にまとめずに済みます。
時間表の例の完了条件は、単純なキャッシュヒット率ではありません。未変更時に保存済み応答を正しく利用するか、変更後に許容時間内で新しい情報が見えるか、共有してよい相手かを確かめます。変更時刻と観察時刻、条件と実際の本文を残せば、他の人も同じ状況を再現しやすくなります。速度の数字がよくなっても、誤った開館時間が長く表示されるなら、当初の要件を満たす改善ではありません。
運用に適用するときは、応答の種類ごとの方針と更新・切り戻しの手順を一緒に検討します。公開文書のキャッシュ指示を個人別応答にコピーしたり、検証が成功しただけでアクセス権限の確認も終わったと解釈したりしないようにします。ヘッダーは保存と再利用の規則を伝える道具です。何が保存され、どんな確認を経て現在の画面に届いたかを説明できれば、古い画面の問題をより正確に診断できます。