キャッシュの性質と格納場所 private vs shared 【読書メモ】『Web配信の技術』
はじめに
Web配信の技術 ―HTTPキャッシュ・リバースプロキシ・CDNを活用する 田中祥平(著者)
扱う範囲
第一章から第三章の中盤(3.9)まで
Web配信とは何か、なぜ最適化が必要か
私たちが普段Webサイトを見たり、動画を視聴したり、アプリを使ったりするとき、その裏側では必ず「配信」という技術が動いています。シンプルに言えば、配信とはサーバーにあるコンテンツ(HTML, CSS, JavaScript, 画像, 動画など)を、利用者のクライアント(ブラウザやアプリ)に届けることです。
Webサービスが大規模化し、コンテンツがリッチになるにつれて、この「配信」をいかに効率よく、速く、安定して行うかが重要になってきました。ユーザー体験の向上はもちろん、サーバー負荷の軽減やコスト削減にも直結します。
その最適化の中心的な役割を担うのが「キャッシュ」技術です。この記事では、Web配信の基本的な仕組みを振り返りつつ、キャッシュの種類や性質、そしてHTTPヘッダーを使ったキャッシュ制御(特に Cache-Control
)について、読書メモの内容を元に整理・解説していきます。
Web配信を支える要素:クライアント/サーバーからISP, RTT, IXまで
Web配信の基本は、クライアントがサーバーにリクエストを送り、サーバーがレスポンスとしてコンテンツを返す、というシンプルなモデルです。しかし、実際にはその間には様々な要素が関わっています。
-
クライアントとサーバーの関係性:
- 単純な「ブラウザ vs Webサーバー」だけではありません。例えば、CDN(後述)は、エンドユーザーから見ればサーバーですが、オリジンサーバーから見ればクライアントとして振る舞います。マイクロサービスアーキテクチャでは、サーバー間の通信も発生します。このように、クライアント/サーバーは固定的なものではなく、役割 (Role) として捉えることが重要です。
- コンテンツの源流となるサーバーをオリジンサーバー、そこからデータが流れて最終的に受け取るクライアントをエンドクライアントや下流、オリジン側を上流と表現することもあります。
-
標準仕様 (RFC):
- Web配信技術は、HTTPなどの標準仕様 (RFC: Request for Comments[1]) に基づいて成り立っています。キャッシュの挙動もRFCで定義されており、この仕様を理解することが最適化の第一歩です。RFCで使われる
MUST
(しなければならない),SHOULD
(する必要がある),MAY
(してもよい) といったキーワードは、仕様の要求レベルを示します。
- Web配信技術は、HTTPなどの標準仕様 (RFC: Request for Comments[1]) に基づいて成り立っています。キャッシュの挙動もRFCで定義されており、この仕様を理解することが最適化の第一歩です。RFCで使われる
-
配信経路の要素:
- ISP (Internet Service Provider): 私たちがインターネットに接続するために契約する事業者(プロバイダ)のことです。ユーザーとサーバーはそれぞれISPを経由してインターネットに接続しており、データはこのISP網を通って送受信されます。ISP間の接続品質や経路が、配信速度に影響を与えます。ISPには階層構造があることも知られています。
- RTT (Round-Trip Time): リクエストが送信元から宛先に届き、その応答が送信元に戻ってくるまでの往復時間のことです。物理的な距離やネットワークの混雑度、経由する機器の数などが影響します。特にTCP通信では、データを送ったらACK(確認応答)を待つため、RTTが長いと通信全体の遅延が大きくなります。
- IX (Internet Exchange): 複数のISPが相互にトラフィックを交換するための接続点です。ISP同士が直接接続するよりも効率的な経路を提供し、RTT短縮に貢献します。
これらの要素が複雑に絡み合い、配信パフォーマンスに影響を与えています。配信最適化とは、これらの要素を理解した上で、ボトルネックを解消していくプロセスと言えます。
キャッシュ戦略の基本:なぜキャッシュが重要か、種類と役割
配信最適化において最も効果的な手段の一つがキャッシュです。キャッシュとは、一度取得したコンテンツを一時的に保存しておき、次回以降のリクエスト時に再利用する仕組みです。
なぜキャッシュが重要なのか?
- 高速化: オリジンサーバーまで毎回コンテンツを取りに行く必要がなくなり、ユーザーに近い場所(あるいはユーザー自身の端末)から応答できるため、表示速度が劇的に向上します。理想的なのは、クライアントがローカルに持っているキャッシュを使うことです。ネットワーク通信が発生しないため、最も高速です。
- 負荷軽減: オリジンサーバーへのリクエスト数が減るため、サーバーの負荷が軽減され、安定稼働に繋がります。
- 帯域削減: 同じデータを何度も転送する必要がなくなり、ネットワーク帯域の消費を抑え、コスト削減に繋がります。
キャッシュは、その格納場所によって大きく3種類に分類できます。
-
クライアント側のキャッシュ (ローカルキャッシュ)
- 場所: エンドユーザーのPCやスマートフォンのブラウザ内(ブラウザキャッシュ)、あるいはアプリ内など。
- 役割: 同じユーザーが同じコンテンツに再度アクセスした際に、ネットワーク通信なしで即座に表示します。最もユーザーに近いキャッシュです。
- 例: ブラウザが画像やCSS、JSファイルを保存しておく。Service Worker を使ってより高度なキャッシュ制御を行うことも可能です。
-
配信経路上のキャッシュ
- 場所: ユーザーとオリジンサーバーの中間に位置するネットワーク上。
- 役割: 複数のユーザーからのリクエストに対して、地理的・ネットワーク的に近い場所からコンテンツを代理応答します。
- 代表例: CDN (Content Delivery Network)。世界中に分散配置されたキャッシュサーバー群です。CDNは、ユーザーに最も近いエッジサーバーからコンテンツを配信することで、RTTを短縮し、高速な配信を実現します。
-
オリジン側のキャッシュ (ゲートウェイキャッシュ)
- 場所: オリジンサーバーの手前(あるいはオリジンサーバーと同じデータセンター内など)。
- 役割: オリジンサーバーへのアクセスを減らし、動的コンテンツの生成コストなどを削減します。CDNからのアクセスを集約するオリジンシールドのような役割を担うこともあります。
- 代表例: リバースプロキシ (Nginx, Varnishなど) によるキャッシュ。
これらのキャッシュは排他的なものではなく、組み合わせて利用されることが一般的です(例:ブラウザキャッシュ + CDN + リバースプロキシ)。
キャッシュの性質と格納場所:private vs shared、どこに何を置くべきか?
キャッシュ戦略を考える上で、格納場所だけでなく、キャッシュするコンテンツの性質を理解することが非常に重要です。コンテンツの性質は、private と shared の2つに分類されます。
-
private キャッシュ:
- 性質: 特定のユーザーだけが利用することを想定したコンテンツ。ユーザーごとに内容が異なる可能性があります。
- 例: ログイン後のマイページ、個人設定が反映されたAPIレスポンスなど。
-
shared キャッシュ:
- 性質: 複数のユーザー間で共有されても問題ないコンテンツ。誰が見ても同じ内容です。
- 例: サイトロゴ、CSSファイル、JavaScriptライブラリ、ニュース記事本文など。
この private
/ shared
という性質と、前述のキャッシュの格納場所を考慮して、どこに何をキャッシュすべき(あるいはすべきでない)かを判断する必要があります。
-
ローカルキャッシュ (ブラウザなど):
- そのユーザー専用の領域なので、原理的には
private
なコンテンツもshared
なコンテンツも両方格納可能です。 -
注意点: ただし、「ローカル = 安全」とは限りません。例えば、家族共用のPCのブラウザキャッシュに、個人情報を含む
private
なコンテンツを安易にキャッシュすると、意図しない情報漏洩に繋がる可能性があります。ローカルキャッシュに何をどこまで保存させるかは慎重に検討が必要です。
- そのユーザー専用の領域なので、原理的には
-
経路上のキャッシュ (CDN) / ゲートウェイキャッシュ (Proxy):
- これらは複数のユーザーからアクセスされる共有のキャッシュサーバーです。
-
原則:
shared
なコンテンツを格納するための場所であり、private
なコンテンツは格納すべきではありません。もしprivate
なコンテンツ(例:ユーザーAの個人情報ページ)がCDNにキャッシュされてしまうと、全く別のユーザーBがアクセスした際にユーザーAの情報が見えてしまう、といった重大なインシデント(キャッシュ汚染、キャッシュポイズニングとも呼ばれます)に繋がる可能性があります。 -
(例外と注意): 「基本的には」
private
は格納すべきでない、と書いたのは、認証情報を伴うリクエストでも特定の条件下で共有キャッシュを許可する仕組みもあるからです(後述のCache-Control: public
など)。しかし、これは非常に高度な制御であり、誤ると深刻な問題を引き起こすため、安易に利用すべきではありません。
まとめ: キャッシュ戦略の基本は、「複数ユーザーで共有できるもの (shared) は積極的に共有キャッシュ (CDN, Proxy) やローカルキャッシュを活用し、特定ユーザー向けのもの (private) は共有キャッシュには絶対に格納せず、ローカルキャッシュへの格納も慎重に検討する」 ということです。
(補足) 用語の整理: 「privateキャッシュ」「sharedキャッシュ」という言葉が、「キャッシュの性質」を指す場合と、「キャッシュが格納される場所の種類(ブラウザキャッシュ=privateキャッシュ、CDN=sharedキャッシュ)」を指す場合があります。この記事では、混乱を避けるため、性質として private
/shared
を、格納場所として「ローカル」「経路上」「ゲートウェイ」という言葉を使っています。
HTTPによるキャッシュ制御の基礎:キャッシュされる条件、ステータスコードとキャッシュ
Webにおけるキャッシュは、主にHTTPプロトコルの仕組みによって制御されます。RFC 7234[2] で仕様が定められています。
重要なのは、HTTPキャッシュはオプショナルな機能でありながら、特定の条件を満たせばデフォルトでキャッシュされるという点です。つまり、「キャッシュさせたい」場合だけでなく、「キャッシュさせたくない」場合に明示的な制御が必要になります。
キャッシュが(デフォルトで)行われる主な条件
- リクエストメソッド: GETなど、キャッシュ可能と定義されているメソッドであること。
- ステータスコード: レスポンスのステータスコードがキャッシュ可能と定義されていること (後述)。
-
Cache-Control: no-store
がない: リクエスト・レスポンスのCache-Control
ヘッダーにno-store
が含まれていないこと。 -
共有キャッシュ格納時の制約:
-
Cache-Control: private
がレスポンスに含まれていないこと。 - リクエストに
Authorization
ヘッダーが含まれていないこと(ただし、public
などで明示的に許可されている場合を除く)。
-
-
キャッシュ期間の指定: レスポンスに
Expires
ヘッダーやCache-Control: max-age
(またはs-maxage
) が含まれている、またはデフォルトでキャッシュ可能なステータスコードである、またはCache-Control: public
が含まれていること。
(書籍のフロー図にあるように) これらの条件をすべて満たす場合に、レスポンスはキャッシュに格納される可能性があります。
デフォルトでキャッシュ可能なステータスコード
すべてのHTTPステータスコードがキャッシュ対象となるわけではありません。デフォルトでキャッシュ可能(キャッシュストアに保存される可能性がある)とされている主なステータスコードは以下の通りです。
-
2xx (成功):
200 OK
203 Non-Authoritative Information
204 No Content
-
206 Partial Content
(Rangeリクエスト成功時)
-
3xx (リダイレクト):
300 Multiple Choices
301 Moved Permanently
-
4xx (クライアントエラー):
404 Not Found
405 Method Not Allowed
410 Gone
414 URI Too Long
-
5xx (サーバーエラー):
501 Not Implemented
意外に思われるかもしれませんが、404 Not Found
などもデフォルトでキャッシュ対象です。これは、存在しないリソースへのアクセスが繰り返されるのを防ぐためです。
これらの条件やステータスコードを理解しておくことが、意図したキャッシュ制御を行うための基礎となります。
Cache-Control徹底解説:主要ディレクティブの使い分け
HTTPヘッダーの中でも、キャッシュ制御において最も重要なのが **Cache-Control**
ヘッダーです。サーバーからのレスポンスだけでなく、クライアントからのリクエストにも付与でき、キャッシュの挙動を細かく指示(ディレクティブ[3])できます。
ここでは、主要なディレクティブとその使い分け、特に注意すべき点について解説します。
no-store
キャッシュの格納を制御する: - 意味: 「このレスポンスを、いかなるキャッシュ(ローカル、経路上、ゲートウェイ)にも一切保存してはならない (MUST NOT)」という最も強力な指示です。
- 意図: 機密情報や、一時的にもディスク上に残したくない情報など、絶対にキャッシュされたくない場合に使用します。
-
使い方: レスポンスヘッダーに指定します。
Cache-Control: no-store
no-cache
, must-revalidate
キャッシュの利用を制御する: これらは「キャッシュへの保存」ではなく、「保存されたキャッシュを利用する際」の挙動を制御します。
-
no-cache
:-
意味: 「キャッシュへの保存は許可する (MAY) が、それを利用する前には必ずオリジンサーバーに有効性を確認(再検証)しなければならない (MUST)」という指示です。オリジンサーバーが「変更なし (
304 Not Modified
)」と応答すればキャッシュを利用し、「新しいデータ (200 OK
)」を返せばそれを利用します。 - 意図: 常に最新の情報を見せたいが、更新がなければ無駄なデータ転送は避けたい(帯域を節約したい)場合に使います。HTMLファイルなどによく使われます。
-
使い方: レスポンスヘッダーに指定します。
Cache-Control: no-cache
-
注意点: 名前から「キャッシュしない」と誤解されやすいですが、「そのままでは使わない (No Cache without validation)」という意味合いです。保存自体は禁止しません。本当に保存させたくない場合は
no-store
を使います。
-
意味: 「キャッシュへの保存は許可する (MAY) が、それを利用する前には必ずオリジンサーバーに有効性を確認(再検証)しなければならない (MUST)」という指示です。オリジンサーバーが「変更なし (
-
must-revalidate
:-
意味: 「キャッシュが古くなった (stale) 後は、オリジンサーバーへの再検証なしに利用してはならない (MUST NOT)」という指示です。もしオリジンサーバーへの再検証に失敗した場合(ネットワークエラーなど)、キャッシュは利用されず、エラー(通常
504 Gateway Timeout
)を返さなければなりません。 - 意図: キャッシュの有効期限切れ後に、古い情報が表示されることを厳格に防ぎたい場合に使います。金融情報など、鮮度が極めて重要な情報に適しています。
-
使い方: レスポンスヘッダーに指定します。
max-age
などと組み合わせて使うことが多いです。Cache-Control: max-age=3600, must-revalidate
-
no-cache
との違い:no-cache
はキャッシュが新しくても古くても常に再検証を要求しますが、must-revalidate
はキャッシュが古くなった後の再検証を義務付けます。(ただし、キャッシュが新しい間も再検証を許可しないわけではありません)。また、再検証失敗時の挙動(エラーを返す)が厳格に規定されています。
-
意味: 「キャッシュが古くなった (stale) 後は、オリジンサーバーへの再検証なしに利用してはならない (MUST NOT)」という指示です。もしオリジンサーバーへの再検証に失敗した場合(ネットワークエラーなど)、キャッシュは利用されず、エラー(通常
private
, public
(と、その注意点)
キャッシュの共有範囲を制御する: これらは、キャッシュをどこに保存してよいかを制御します。
-
private
:- 意味: 「レスポンスは特定のユーザー専用 (private) であり、共有キャッシュ (CDN, Proxy) には保存してはならない (MUST NOT)」という指示です。ローカルキャッシュ(ブラウザキャッシュ)への保存は許可されます (MAY)。
- 意図: ユーザーごとに内容が異なるコンテンツ(マイページなど)が、意図せず共有キャッシュに保存され、他のユーザーに見えてしまうのを防ぐために使います。
-
使い方: レスポンスヘッダーに指定します。
Cache-Control: private
-
public
:- 意味: 「レスポンスは複数のユーザー間で共有可能 (public) であり、共有キャッシュ (CDN, Proxy) に保存してもよい (MAY)」という指示です。
-
使い方: レスポンスヘッダーに指定します。
Cache-Control: public, max-age=86400
⚠️ private
と public
に関する重要な注意点
しかし、private
と public
の使用には注意が必要です。
-
デフォルトの挙動:
Cache-Control
でprivate
もpublic
も指定されていない場合、そのレスポンスは(他の条件を満たせば)共有キャッシュ (shared) として扱われる可能性があります。つまり、「共有キャッシュさせたいからpublic
を指定する」というのは、多くの場合不要です。 -
強制キャッシュ機能:
private
とpublic
には、「通常はキャッシュされないような状況でも、キャッシュを強制する」という副作用があります。例えば、- 通常、リクエストに
Authorization
ヘッダーが付いていると共有キャッシュには保存されません。しかし、レスポンスにpublic
が指定されていると、Authorization
ヘッダーがあっても共有キャッシュに保存される可能性があります。 - デフォルトではキャッシュされないステータスコード(例:
201 Created
など)でも、private
やpublic
が指定されているとキャッシュされる可能性があります。
- 通常、リクエストに
-
安易な使用への警鐘:
- 「キャッシュさせたくないから
private
を指定する」のは誤りです。private
は共有キャッシュへの保存を防ぐだけで、ローカルキャッシュへの保存は許可します。本当にキャッシュさせたくない場合はno-store
を使うべきです。 - 前述の通り、共有キャッシュさせたい場合に安易に
public
を指定すると、本来キャッシュすべきでない認証情報付きのリクエストまでキャッシュしてしまうリスクがあります。特別な意図(例: 認証付きリクエストでもキャッシュさせたい公開情報など)がない限り、public
を明示的に指定する必要性は低いと言えます。
- 「キャッシュさせたくないから
「絶対にキャッシュさせない」ための組み合わせ
「このレスポンスをとにかくキャッシュさせたくない、常に最新の情報を取得させたい」という非常に強い意図を示すために、実務では複数のディレクティブを組み合わせることがあります。
Cache-Control: private, no-store, no-cache, must-revalidate
Discussion