前回の記事ではHTTPの基礎について解説し、HTTPでは、クライアントとサーバーが、主にヘッダーを用いて情報交換をしていることをお伝えしました。
本記事では、HTTPのキャッシュを制御するためのヘッダーについて解説し、読者がCDNの設定を理解できることを目指します。
HTTP キャッシュとは
HTTPキャッシュは、クライアントがサーバーから一度受け取ったリソースを再利用するための仕組みです。キャッシュを活用することで次のようなメリットが得られます。
- レスポンスの高速化: キャッシュされたリソースは、サーバーから再度ダウンロードする必要がないため、ページの表示速度が改善します。
- データ転送量の削減: サーバーとクライアントの間で送信されるデータ量が減るため、データ転送量が削減されます。
- サーバー負荷の軽減: 同じリソースへのリクエストがキャッシュから提供されるため、サーバーの負荷が軽減されます。
キャッシュの動作はHTTPヘッダーによって制御されます。これには、リソースがいつキャッシュされるべきか、キャッシュの有効期限はいつまでかなどの情報が含まれます。
HTTP キャッシュの動作
まずは、HTTPキャッシュの動作について見ていきましょう。 というのも、HTTPのキャッシュはこれまで見てきたDNSキャッシュとは異なり、キャッシュの有効期限が切れてもできるだけキャッシュを使おうとする性質があるからです。
次の図はstyle.css
をクライアントが受け取り、5分間キャッシュを使用する様子を表しています。
この図で注目すべきところは、「5. style.cssに変更がないかサーバーに確認」以降です。HTTP キャッシュでは、有効期限が切れたキャッシュのことを古くなったキャッシュ(Stale Cache)と呼び、有効期限が切れてもしばらく保持します。逆に、有効期限内のキャッシュは、新鮮なキャッシュ(Fresh Cache)と呼ばれています。
そして、次のリクエストの際は、サーバーに変更がないかを確認します。「変更なし」とだけ記載されたレスポンスを受け取った場合、クライアントはそのままキャッシュを利用します。これにより、レスポンスの高速化と無駄なデータ転送を抑制できます。
変更がある場合は、サーバーは新しいリソースを返却します。
HTTPキャッシュに関連するヘッダー
このセクションでは、HTTPキャッシュの動作が具体的にどのようなヘッダーで実現されるのかを見ていきます。
まずは、HTTPキャッシュで使われるヘッダーについて見ていきます。
Cache-Control
リソースのキャッシング方法を指定します。キャッシュの設定を確認したい時は、レスポンスヘッダーのCache-Controlの内容を見ることになります。
Cache-Controlで使われる代表的な値(ディレクティブと言います)は次の通りです。
- no-store: キャッシュに一切保存しない。
- no-cache: キャッシュを使用する前に再検証する。
- private: 個人のブラウザでのみキャッシュする。
- public: 共有キャッシュ(例: CDN)に保存する。
- max-age=[秒]: 指定した秒数の間、キャッシュを新鮮として扱う。
- s-maxage=[秒]: 共有キャッシュ(例: CDN)での新鮮さの最大時間を指定。
勘違いしやすい点は、no-cache
はキャッシュしてはいけないことを意味するのではなく、キャッシュしても良いが使用する前に再検証をさせることを意味しています。キャッシュしてはいけない場合は、no-store
を使用します。
例えば、次のCache-Controlがレスポンスについている場合、5分(300秒)の間、クライアントは結果をキャッシュします。1時間経過した後、再度このキャッシュを使いたい場合は再検証を行います。
Cache-Control: public, max-age=300
s-max-ageは、max-ageと同じ機能ですが、CDNなどの共有キャッシュに特化しています。 CDNは、CDNの設定とオリジンサーバーからのs-max-ageを見てキャッシュの動作を決定します。 s-max-ageがない場合は、max-ageが見られるケースが多いです。
Cache-Controlは、リクエストとレスポンスの両方に付与することができます。しかしながら、一般的にはレスポンスに付与されたものが考慮されます。というのは、せっかくCDNを導入してもクライアントからのCache-Control: no-chae
に従ってしまうと毎回サーバーに更新確認をしなければならず、オリジンサーバーに負荷がかかってしまうからです。
Expires
キャッシュの有効期限を示します。指定された日時を過ぎると、キャッシュは古くなったと見なされます。
次の場合、2023年10月20日の7時に有効期限が切れます。
Expires: Wed, 20 Oct 2023 07:00:00 GMT
Expiresは、時刻で指定されるため、クライアントとサーバーで時刻が同期していないと適切に動作しないという欠点があります。
また、Expiresは、Cache-Controlと重複した機能を提供していますが、両方のヘッダーがある場合はCache-Controlが優先されるため、実際のところはほとんど無視できると言っても良いでしょう。
ExpiresはCache-Controlヘッダーより前に策定されたので、現在ではCache-Controlヘッダーに対応していないクライアントとの互換性のために付与させるという位置づけです。
Expiresはレスポンスヘッダーのみで使用されます。
If-None-Match
古くなったキャッシュの再検証に使用されます。
If-None-Matchは、Etagと一緒に使用されます。ETag(エンティティタグ)ヘッダーは、サーバーが生成し、特定のリソースのバージョンを一意に識別するために使用されます。
レスポンスに付与されているEtagの値をクライアントは覚えておき、キャッシュの再検証の時にIf-None-MatchにEtagの値を入れて送信します。
サーバーは、クライアントから受け取ったIf-None-Matchの値とサーバー側のコンテンツのEtagの値を比較します。同一であれば、コンテンツに変更がなく、キャッシュは再利用できるので、ステータスコード 304 Not Modifiedを返却します。 値が一致しない場合は、ステータスコード 200 OKとともに新しいコンテンツを返却します。
HTTP キャッシュの動作で見た図を書き直すと次のようになります。
「6. 304 Not Modified」が返却されているところは、サーバー側のコンテンツが変わっていれば、200 OKとなります。
If-None-Matchはリクエストヘッダーのみで使用されます。
If-Modified-Since
If-None-Matchと同様に古くなったキャッシュの再検証に使用されます。
If-Modified-Sinceは、Last-Modifiedと一緒に使用されます。Last-Modifiedヘッダーは、サーバーが生成し、コンテンツが最後に変更された時刻を示します。
レスポンスに付与されているLast-Modifiedの値をクライアントは覚えておき、キャッシュの再検証の時にIf-Modified-SinceにLast-Modifiedの値を入れて送信します。
サーバーは、クライアントから受け取ったIf-Modified-Sinceの値とサーバー側のコンテンツの最終更新時刻を比較します。該当時刻以降に更新がなければ、キャッシュは再利用できるので、ステータスコード 304 Not Modifiedを返却します。 更新があれば、ステータスコード 200 OKとともに新しいコンテンツを返却します。
If-None-Matchと一緒に使われる時は、If-None-Matchが優先されます。
If-Modified-Sinceはリクエストヘッダーのみで使用されます。
まとめ
本記事では、HTTPのキャッシュについて解説しました。
HTTPのキャッシュは、可能な限りキャッシュを再利用しようとするため、新鮮なキャッシュ(Fresh Cache)と古いキャッシュ(Stale Cache)があり、古いキャッシュも再検証によって有効活用されることを学びました。
次回の記事では、CDNの最も重要な要素の1つであるキャッシュキー(Cache Key)について解説します。
CDN入門の記事一覧ページはこちらです。