HTTP キャッシュヘッダーを使用したアプリケーションパフォーマンスの向上
最終更新日 2023年06月13日(火)
現代の開発者は、アプリケーションパフォーマンスとエンドユーザーエクスペリエンスを向上させるために使用できるさまざまな手法やテクノロジを備えています。 最も見落とされがちなテクノロジの 1 つに HTTP キャッシュの技術があります。
HTTP キャッシングは、最新のすべての Web ブラウザにわたって汎用的に採用されている仕様であるため、Web アプリケーションでのその実装は簡単です。 これらの標準の適切な使用によりアプリケーションは大きなメリットが得られ、応答時間の改善やサーバー負荷の削減が実現されます。ただし、誤ったキャッシングのために、ユーザーに古いコンテンツが表示されたり、デバッグしにくい問題が発生したりする場合があります。この記事では、HTTP キャッシングと、HTTP キャッシュヘッダーベースの方式を採用すべきシナリオについて詳細に説明します。
概要
HTTP キャッシングは、ブラウザが Web リソースのローカルコピーを保存して、次にそのリソースが必要になったときにより高速に取得できるようにする場合に発生します。アプリケーションは、リソースを処理する場合、目的のキャッシュ動作を指定するキャッシュヘッダーをレスポンスにアタッチできます。
項目が完全にキャッシュされている場合、ブラウザは、サーバーにまったくアクセスせず、単に独自のキャッシュされたコピーを使用することを選択できます。
たとえば、アプリケーションの CSS スタイルシートがブラウザによってダウンロードされた後、ユーザーのセッション中にそれを再びダウンロードする必要はありません。これは、JavaScript ファイル、画像、変更される頻度の低い動的コンテンツなどの多くのアセットタイプにも当てはまります。これらの場合、ユーザーのブラウザはこのファイルをローカルにキャッシュし、リソースが再度要求されるたびにそのコピーを使用すると有効です。 HTTP キャッシュヘッダーを使用しているアプリケーションは、このキャッシュ動作を制御し、サーバー側の負荷を軽減することができます。
直感的には、エンドユーザーのブラウザが HTTP キャッシュヘッダーの主要なコンシューマーであると考えられます。ただし、これらの HTTP キャッシュヘッダーは、ソースサーバーとエンドユーザーの間にあるすべての中間プロキシおよびキャッシュによって使用したり、処理したりできます。
HTTP キャッシュヘッダー
主なキャッシュヘッダーとして、Cache-Control
と Expires
の 2 つがあります。
Cache-Control
Cache-Control ヘッダーが設定されていないと、他のどのキャッシュヘッダーでも結果は生成されません。
Cache-Control
ヘッダーは、ブラウザでのキャッシングを実質的に ‘有効にする’ ため、設定すべき最も重要なヘッダーです。 このヘッダーが存在し、キャッシングを有効にする値が設定されている場合、ブラウザはそのファイルを指定されている期間だけキャッシュします。 このヘッダーがない場合、ブラウザは、以降の各リクエストでファイルを再要求します。
public
リソースは、エンドユーザーのブラウザだけでなく、他の多くのユーザーに対応している可能性のあるすべての中間プロキシもキャッシュできます。
Cache-Control:public
private
リソースは中間プロキシによってスキップされ、エンドクライアントのみがキャッシュできます。
Cache-Control:private
Cache-Control
ヘッダーの値は合成値です。つまり、リソースがパブリックまたはプライベートのどちらであるかを示すと共に、古くなったとみなされるまでキャッシュできる最長時間を示します。max-age
値には、リソースがキャッシュされるタイムスパン (秒単位) が設定されます。
Cache-Control:public, max-age=31536000
Cache-Control
ヘッダーがクライアント側のキャッシングを有効にし、リソースの max-age
を設定するのに対して、Expires
ヘッダーは、そのリソースが有効でなくなる特定の時点を指定するために使用されます。
Expires
Cache-Control
ヘッダーに付随している場合、Expires
は単純に、キャッシュされたリソースが有効であるとみなされなくなる日付を設定します。 この日付以降、ブラウザは、リソースの新しいコピーを要求します。それまでは、ブラウザのローカルにキャッシュされたコピーが使用されます。
Expires
と max-age
の両方が設定されている場合は、max-age
が優先されます。
Cache-Control:public
Expires: Mon, 25 Jun 2012 21:31:12 GMT
Cache-Control
と Expires
がブラウザに、次にネットワークからリソースを取得する時点を指示するのに対して、いくつかの追加ヘッダーはネットワークからリソースを取得する方法を指定します。これらのタイプのリクエストは、条件付きリクエストと呼ばれます。
条件付きリクエスト
条件付きリクエストは、ブラウザがサーバーにリソースの更新されたコピーがあるかどうかを問い合わせることができるリクエストです。 ブラウザが、保持しているキャッシュされたリソースに関する情報を送信すると、サーバーは更新されたコンテンツが返されるべきか、またはブラウザのコピーが最新であるかを判定します。後者の場合は、304 (Not Modified) の HTTP ステータスが返されます。
条件付きリクエストではネットワーク経由で呼び出しが起動されますが、変更されていないリソースの場合は空のレスポンス本体が生成されるため、そのリソースがエンドクライアントに戻されるコストが節約されます。バックエンドサービスもまた、多くの場合、リソースの最終更新日をそのリソースにアクセスすることなく非常にすばやく判定できるため、それ自体で処理時間が大幅に節約されます。
時間ベース
時間ベースの条件付きリクエストによって、コンテンツは確実に、要求されたリソースが、ブラウザのコピーがキャッシュされた後に変更されている場合にのみ転送されます。キャッシュされたコピーが最新である場合、サーバーは 304 のレスポンスコードを返します。
条件付きリクエストを有効にするために、アプリケーションは Last-Modified
レスポンスヘッダーを使用してリソースの最終更新時刻を指定します。
Cache-Control:public, max-age=31536000
Last-Modified: Mon, 03 Jan 2011 17:45:57 GMT
次にこのリソースを要求するとき、ブラウザは If-Modified-Since
リクエストヘッダーを使用して、この日付以降に変更されている場合にのみリソースのコンテンツを送信するよう求めます。
If-Modified-Since: Mon, 03 Jan 2011 17:45:57 GMT
そのリソースが Mon, 03 Jan 2011 17:45:57 GMT
以降に変更されていない場合、サーバーは 304
レスポンスコードと共に空の本体を返します。
コンテンツベース
ETag
(エンティティタグ) は、その値がリソースコンテンツのダイジェスト (MD5 ハッシュなど) である点を除き、Last-Modified
ヘッダーと同様に動作します。 これにより、サーバーは、リソースのキャッシュされたコンテンツが最新のバージョンと異なるかどうかを特定できます。
このタグは、最終更新日を特定することが困難な場合に役立ちます。
Cache-Control:public, max-age=31536000
ETag: "15f0fff99ed5aae4edffdd6496d7131f"
ブラウザの以降のリクエストでは、If-None-Match
リクエストヘッダーが、リソースの要求された最終バージョンの ETag 値と共に送信されます。
If-None-Match: "15f0fff99ed5aae4edffdd6496d7131f"
If-Modified-Since
ヘッダーと同様に、現在のバージョンが同じ ETag 値を持っている場合 (その値がブラウザのキャッシュされたコピーと同じであることを示す) は、304 の HTTP ステータスが返されます。
視認性
最新のブラウザには、堅牢なリクエスト/レスポンス可視化および内観ツールが含まれています。Chrome と Safari の両方にある Web Inspector では、Network
(ネットワーク) タブにレスポンスとリクエストヘッダーが表示されます。
さまざまなキャッシュヘッダーを使用するサンプルアプリケーションのコードは GitHub にあります。
これは、アプリケーションによって返される一連のデフォルトヘッダー (キャッシュディレクティブはなし) を示す初期リクエストの例です。
cache
クエリパラメータを追加すると、アプリケーションは Cache-Control
と Expires
の両方のヘッダーでのキャッシングを有効にします (どちらも後で 30 秒に設定されます)。
リクエストに etag
パラメータを追加すると、サンプルアプリは JSON コンテンツの ETag ダイジェストを指定するようになります。
より深い調査でも、ETag ベースの条件付きリクエストは期待どおりに動作します。初期のリクエストでは、ブラウザがサーバーからファイルをダウンロードします。
以降のリクエストでは、サーバーはブラウザの ETag チェックに 304 (Not Modified) の HTTP ステータスで応答しています。これにより、ブラウザは独自のキャッシュされたコピーを使用します。
ユースケース
静的アセット
通常の使用状況では、すべての開発者の出発点は、変更されないアプリケーション内のファイルに積極的なキャッシング方式を追加することです。 これには通常、画像、CSS ファイル、JavaScript ファイルなどの、アプリケーションによって処理される静的ファイルが含まれます。これらのファイルは一般に、各ページで再要求されるため、少ない労力で大きなパフォーマンス向上が得られます。
このような場合は、要求の時点から 1 年後までの max-age 値を持つ Cache-Control ヘッダーを設定する必要があります。Expires も同じ値に設定することをお勧めします。
1 年は 31536000 秒です。
Cache-Control:public; max-age=31536000
Expires: Mon, 25 Jun 2013 21:31:12 GMT
それより長い期間は RFC でサポートされておらず、無視される可能性があるため、一般に、これを超える値を設定することはお勧めしません。
動的コンテンツ
動的コンテンツには、まったく異なる処理が求められます。 リソースごとに、開発者はそれをどの程度キャッシュできるか、および古いコンテンツの処理がユーザーにどのような影響を与える可能性があるかを評価する必要があります。 2 つの例として、ブログ RSS フィードのコンテンツ (数時間に 1 回より頻繁には変更されない) と、ユーザーの Twitter タイムラインを起動する JSON パケット (数秒に 1 回更新される) が挙げられます。 これらの場合は、エンドユーザーに対して問題を発生させることなく可能であると思われる期間だけリソースをキャッシュすることが妥当です。
プライベートコンテンツ
プライベートコンテンツ (これは機密情報であり、セキュリティ対策に従う必要があるとみなされる) には、さらなる評価が必要です。 開発者は、特定のリソースのキャッシュ可能性を判定する必要があるだけでなく、ユーザーの制御の範囲外にある可能性のあるファイルをキャッシュする中間キャッシュ (Web プロキシなど) を置くことの影響も考慮する必要があります。 疑いがある場合は、これらの項目をまったくキャッシュしないことが安全なオプションです。
エンドクライアントのキャッシングが引き続き望ましい場合は、リソースをプライベートに (つまり、エンドユーザーのブラウザのキャッシュ内に) のみキャッシュするよう求めることができます。
Cache-Control:private, max-age=31536000
キャッシュの防止
きわめて安全なリソースや変わりやすいリソースには多くの場合、キャッシングは必要ありません。たとえば、ショッピングカートのチェックアウトプロセスに関連するすべてのものです。ただし、最新の Web ブラウザの多くは独自の内部アルゴリズムに基づいて項目をキャッシュするため、単にキャッシュヘッダーを省略してもうまく機能しません。このような場合は、項目をキャッシュしないよう明示的にブラウザに指示することが必要です。
public
や private
に加えて、Cache-Control
ヘッダーでは no-cache
と no-store
を指定できます。これは、どのような状況でもリソースをキャッシュしないようブラウザに指示します。
IE では no-cache
が使用され、Firefox では no-store
が使用されるため、両方の値が必要です。
Cache-Control:no-cache, no-store
実装
HTTP キャッシングの背後にある概念を理解したら、次のステップは、これをアプリケーションで実装することです。最新の Web フレームワークでは、これがきわめて簡単な作業になっています。
言語/フレームワーク | チュートリアル |
---|---|
Ruby/Rails |
|