ナビゲーションをスキップ
部 IV 章 23

キャッシング

序章

この20年間で、私たちがウェブアプリケーションを体験する方法は変化し、よりリッチでインタラクティブなコンテンツが提供されるようになりました。しかし、残念ながら、このようなコンテンツには、データストレージと帯域幅の両方においてコストがかかっています。ほとんどの場合、使用するネットワークが劣化していたり、デバイスに十分な容量がなかったりすると、多くの人がウェブ製品を完全に体験することが難しくなります。キャッシュは、このような問題の解決策であると同時に原因でもあります。数多くの選択肢を使いこなすことで、ハイエンドデバイスだけでなく、ローエンドデバイスから製品にアクセスする次の10億人のユーザーにも対応した構築が可能になります。

キャッシュは、JavaScript、CSSファイル、基本的な文字列値などの単純な静的アセットから、より複雑なJSON APIレスポンスまで、以前にダウンロードしたコンテンツの再利用を可能にする技術です。

その核心は、キャッシュによって特定のHTTPリクエストを回避し、アプリケーションがユーザーに対してより応答的で信頼性が高いと感じられるようにすることです。各リクエストは通常、主に2つの場所にキャッシュされます。

  • コンテンツデリバリーネットワーク (CDNs) は通常、サードパーティの会社で、ユーザーがアプリケーションにアクセスしている場所にできるだけ近い場所でデータを複製することを主な目的としています。ほとんどのCDNは、いくつかのデフォルトの動作を持っていますが、主にヘッダーを使用することによって、キャッシュの方法を指示できます。
  • ブラウザは、体験を最適化するためにあなたが定義したHTTPヘッダーを尊重するか、またはいくつかの内部デフォルトを適用します。その上、ブラウザは、単純な文字列をcookiesに、複雑なAPI応答をIndexedDBに、あるいはリソース全体を サービスワーカーキャッシュストレージ に保存するなどの手動キャッシュ戦略を利用することも可能です。

この章では、ブラウザとCDNの間で使用されるHTTPヘッダーに主に焦点を当て、サービスワーカーのキャッシュ戦略についても簡単に触れます。

CDNキャッシュの採用

コンテンツデリバリーネットワーク(CDN)は、通常、データのコピーを保存する複数の場所に分散したサーバーのグループです。これにより、サーバーはエンドユーザーへもっとも近いサーバーに基づいてリクエストを処理できます。

図23.1. トップCDNの採用。

2021年のウェブ全体では、Desktopに使用されているCDNはCloudflareがもっとも普及しており、総ページ数の14%、次いでGoogleが6%となっています。CloudflareとGoogleがもっとも普及していますが、この2つ以外にも、Fastly、Amazon CloudFront、Akamaiなど、多種多様なソリューションが残っています。

サービス・ワーカー採用

サービスワーカーの採用が着実に増え続けています。

図23.2. サービスワーカーの採用。

サービスワーカーを登録しているページは1%強であるのに対し、アクセス数の多い上位1,000ドメインにランクインしているページの約9%がサービスワーカーを登録しています。

このように、とくに上位1,000ページにおいてサービスワーカーの採用率が高いのは、リモートファースト、そしてそれに関連してモバイルフレンドリーという世界的なトレンドと関係があるのかもしれません。1年を通じて1つの場所で仕事をしたり生活したりすることへの依存度がシフトするにつれて、私たちはデバイスが私たちについていくために、さらにハードでスマートな働きをすることを必要としているのです。サービスワーカーは、ユーザーが信頼性の低いネットワークやローエンドデバイスを使用している場合、パフォーマンスを向上させることができるツールです。

サービスワーカー内のリソースをキャッシュする主な方法は、キャッシュストレージAPIを使用することです。これにより、開発者はワーカーを通過するすべてのリクエストに対して、カスタムキャッシュ戦略を作成できます。よく知られているものに、stale-while-revalidateネットワークにフォールバックするキャッシュキャッシュにフォールバックするネットワークキャッシュのみがあります。近年では、プラグアンドプレイでキャッシュを決定できるワークボックスの人気が高まっているおかげで、それらの戦略を採用するのがさらに容易になっています。

サービスワーカー、およびWorkboxについては、PWAの章で詳しく説明します。

キャッシュヘッダーの採用

CDNとブラウザの両方において、HTTPヘッダーは開発者がリソースを適切にキャッシュするために習得しなければならない主要なツールです。ヘッダーとは、HTTPリクエストやレスポンスから読み取られる単純な命令であり、それらのいくつかは、使用するキャッシュ戦略を制御するのに役立ちます。

キャッシュ関連ヘッダーは、それがあるかないかで、ブラウザやCDNに3つの重要な情報を伝えます。

  • Cacheability: このコンテンツはキャッシュ可能か?
  • Freshness: キャッシュ可能な場合、どのくらいの期間キャッシュできるのか?
  • Validation: キャッシュ可能な場合、キャッシュされたバージョンがまだ新鮮であることを確認するにはどうすればよいか?

ヘッダーは単独、あるいは一緒に使うものです。コンテンツがキャッシュ可能新鮮であるかどうかを判断するため。

  • Expiresは、明示的に有効期限を指定します(つまり、コンテンツの正確な有効期限を指定します)。
  • Cache-Control はキャッシュ期間 (すなわち、コンテンツが生成されたときから相対的に、ブラウザにキャッシュできる期間)を指定します。

両方が指定された場合、Cache-Controlが優先されます。

図23.3. Cache-Control ヘッダーと Expires ヘッダーを設定したレスポンスの割合。

2019年以降、Cache-Controlヘッダーの使用率は着実に増加しています。モバイルリクエストのレスポンスの74.2%が Cache-Control ヘッダーを含み、デスクトップリクエストのレスポンスの74.8%がこのヘッダーを利用していました。

2020年以降、この特定のヘッダーの使用率は、モバイルで0.71%、デスクトップでは1.13%増加しました。しかし、モバイルではまだ25.1%のリクエストが Cache-ControlExpires ヘッダーのどちらも使用していません。このことから、コミュニティでは Cache-Control の適切な使用に関する認識が高まっていると思われますが、これらのヘッダーを完全に採用するにはまだ長い道のりがあります。

内容を検証するために、私たちは

  • Last-Modified は、オブジェクトが最後に変更された日時を示します。この値は日付のタイムスタンプです。
  • ETag (Entity Tag) は引用符で囲まれた文字列として、コンテンツに一意の識別子を与えます。これは、サーバーが選択した任意の形式を取ることができます。通常はファイルの内容のハッシュ値ですが、タイムスタンプや単純な文字列でもかまいません。

両方が指定された場合、ETagが優先される。

図23.4. Last-ModifiedETag ヘッダーを設定したレスポンスのパーセンテージ。

2020年と2021年を比較すると、ETagの利用が年々わずかに増え、Last-Modifiedの利用が1.5%減るという、過去繰り返された傾向にあることがわかります。来年注目すべきは、ETagLast-Modifiedヘッダーを使用しない回答が1.4%増加するという新しい傾向で、これはコミュニティがこれらのヘッダーの価値を理解していないことを示唆している可能性があるのです。

Cache-Control ディレクティブ

Cache-Control ヘッダーを使用する場合、特定のキャッシュ機能を示す1つ以上のディレクティブ定義された値を指定します。複数のディレクティブはカンマで区切られ、どのような順番でも設定できますが、中には互いに衝突するものもあります(たとえば、publicprivateなど)。さらに、いくつかのディレクティブは max-age のような値をとります。

以下は、もっとも一般的な Cache-Control ディレクティブを示した表です。

ディレクティブ 説明
max-age 現在時刻を基準として、リソースのキャッシュが可能な秒数を示す。たとえば、max-age=86400などです。
public ブラウザや CDN を含む、任意のキャッシュが応答を保存できることを示します。これはデフォルトで想定されています。
no-cache キャッシュされたリソースは、たとえそれが古いとマークされていなくても、使用前に条件付きリクエストによって再検証されなければならない。
must-revalidate キャッシュされた古いエントリは、使用前に条件付きリクエストによって再検証されなければならない。
no-store レスポンスがキャッシュされてはならないことを示す。
private このレスポンスは特定のユーザーを対象としたものであり、CDNなどの共有キャッシュに保存されるべきものではありません。
immutable キャッシュされたエントリがその TTL の間、決して変更されず、再バリデーションが必要ないことを示す。
図23.5. Cache-Control ディレクティブの使用法。

max-ageディレクティブはもっとも一般的で、62.2%のデスクトップ・リクエストがこのディレクティブを含む Cache-Control レスポンス・ヘッダーを含んでいます。

2020年との比較では、上図の上位7つの指令のほとんどとともに、デスクトップではmax-ageの採用が2%増加しました。

immutable ディレクティブは比較的新しいもので、特定のタイプのリクエストのキャッシュ性を大幅に向上させることができます。しかし、まだ一部のブラウザでしかサポートされておらず、Wixが16.4%、Facebook 8.6%、Tawk 2.8%、Shopify 2.4%といったホストネットワークから来るリクエストが、ほとんどであることが分かっています。

もっとも誤用されている Cache-Control ディレクティブは、引き続き set-cookie で、デスクトップでは ディレクティブ 全体の0.07% で、モバイルでは0.08% で使用されています。しかし、2020年からは0.16%の使用量削減という意味でも喜ばしいことです。

no-cachemax-age=0no-store が一緒に使われている場合を見てみると年々増加傾向にあり no-storeno-cachemax-age=0 のいずれか/両方と一緒に指定されると、 no-store ディレクティブが優先され、他のディレクティブが、無視されることが分かってきています。これらのディレクティブの使用について、たとえば大規模なカンファレンスの際にもっと認識を高めることで、誤ってムダなバイトを使用することを避けることができるかもしれません。

51兆年
図23.6. max-ageで記録されたもっとも大きな値。

おもしろい事実:もっとも一般的なmax-ageの値は30日であり、もっとも大きな値は51兆年です。

304 Not Modified ステータス

サイズに関して言えば、304 Not Modified のレスポンスは 200 OK のレスポンスよりもずっと小さいので、必要なサイズのデータのみを配信することでページのパフォーマンスを向上させることができるということになります。そこで、条件付きリクエストを正しく使用することが重要になります。再バリデーション、つまりデータの節約は、 ETag ヘッダーまたは Last-Modified ヘッダーのどちらかを使用することで行うことができます。

Last-Modified レスポンスヘッダーは If-Modified-Since リクエストヘッダーと一緒に動作し、リクエストされたファイルに何らかの変更がなされたかどうかをブラウザに知らせます。

図23.7. キャッシュ戦略別のHTTP 304応答率。

2020年から2021年にかけて、If-Modified-Sinceの304レスポンスの分布が、7.7%増加したことがわかりました。これは、コミュニティがこれらのデータ節約を活用していることを示しています。

日付文字列の有効性

タイムスタンプを表すために使用される3つの主要なHTTPヘッダー、 DateLast-ModifiedExpires はすべて日付の書式を持つ文字列を使用します。HTTPレスポンスヘッダーの Date はほとんどの場合、ウェブサーバによって自動的に生成されるため、無効な値は非常に稀です。しかし、日付が正しく設定されていない場合、それが提供されるレスポンスのキャッシュに影響を与える可能性があります。

図23.8. 日付の形式が無効な回答の割合。

2020年から2021年にかけて無効なDateを使用する割合は0.5%改善しましたが、Last-ModifiedExpiresについては悪化しており、キャッシュ時の日付設定に関係が、あることがわかります。

このことから、日付ベースのヘッダーの自動化には、さらなる注意が、必要であることがわかる。

Vary

リソースのキャッシュに不可欠なステップは、そのリソースが以前にキャッシュされていたかどうかを理解することです。ブラウザは通常、キャッシュキーとしてURLを使用します。同時に、同じURLに対するリクエストでも Accept-Encoding が異なるとレスポンスが異なるため、不正にキャッシュされる可能性があります。そのため、Varyヘッダーを使用して、1つ以上のヘッダーの値をキャッシュキーに追加するようにブラウザに指示します。

図23.9. Vary ディレクティブの使用法。

もっとも普及している Vary ヘッダーは Accept-Encoding で90.3% の使用率、次いで User-Agent で10.9% 、 Origin で10.1% 、そして Accept で4.8% の使用率です。

2020年からAccept-Encodingの使用率が1.5%減少していることがわかりました。

図23.10. Vary ヘッダーを設定したモバイル用レスポンスのパーセンテージ。

監査した総リクエストのうち、Varyヘッダーを使用しているのは46.25%に過ぎませんが、2020年と比較すると、全体で2.85%増加していることを指摘することが重要です。

図23.11. Vary ヘッダーを持つモバイルレスポンスのうち、Cache-Control も設定されているものの割合です。

Vary ヘッダーを使用するリクエストのうち、83.4%は Cache-control も使用しています。これは、2020年から2.1%改善されたことを示しています。

キャッシュ可能なレスポンスにCookieを設定する

2020年のキャッシュの章では、キャッシュ可能なレスポンスで set-cookie を使用することに注意するよう念を押しました。なぜなら、レスポンスのわずか4.9%が private ディレクティブを使用しており、ユーザーの個人データがCDN経由で誤って別のユーザーに提供される危険性があるためです。

図23.12. Set-Cookie を使用するキャッシュ可能なレスポンスのパーセンテージ。

2021年には、set-cookieとキャッシュの共存に関する認識が高まっていることがわかります。set-cookieでprivateディレクティブを使用しているウェブページはまだ5%しかありませんが、キャッシュ可能な set-cookie レスポンスの総数は4.41%減少しています。

どのようなコンテンツをキャッシュしているのか?

図23.13. リソースタイプ別にキャッシュ戦略を使用したリクエストのパーセンテージ。

フォント、CSS、オーディオファイルは99%以上キャッシュ可能で、現在ほぼ100%のページでフォントがキャッシュされています。これは、静的なファイルであるため、キャッシュに適しているためと思われます。

しかし、もっともよく使われるリソースの中には、動的な性質のためか、キャッシュ不可能なものがあります。とくに、HTMLは23.4% ともっとも高い割合でキャッシュ不可能なリソースがあり、画像は10.1% でそれに続いています。

2020年と2021年のモバイルデータを比較すると、キャッシュ可能なHTMLが5.1%増加していることがわかります。これは、サーバーサイドレンダリングアプリケーションによって生成されたようなHTMLページをキャッシュするために、CDNをより良く利用する方向に進んでいる可能性を物語っています。ページは通常、特定のウェブページのコンテンツが頻繁に変更されない場合、SSRによって生成されます。このURLは、数週間あるいは数ヶ月間、同じHTMLを提供する可能性があり、そのコンテンツは高度にキャッシュ可能です。

タイプ デスクトップ モバイル
テキスト 0.2 0.2
XML 1 1
その他 1 1
動画 4 8
HTML 3 14
オーディオ 0.2 30
CSS 30 30
画像 30 30
スクリプト 30 30
フォント 365 365
図23.14. TTLの中央値(単位:日)

すべてのリソースタイプでTime To Live(TTL)の中央値を見てみると全体で同じような割合のキャッシュをしていても、モバイルでは、とくにHTML、オーディオ、ビデオでかなり長いキャッシュが、存在することがわかります。

図23.15. キャッシュ可能なレスポンスとキャッシュ不可能なレスポンスの割合。

とはいえ、モバイル体験のための最適化を続けていても、キャッシュ可能なデスクトップリソースの潜在的な量は、モバイル用のリソースよりわずかに多いままであることは興味深いことです。

キャッシュTTLとリソースエイジの比較は?

図23.16. コンテンツタイプ別のファーストパーティリソースエイジの分布(モバイルのみ)。
図23.17. コンテンツタイプ別のサードパーティリソースエイジの分布(モバイルのみ)。

画像と動画は、ファースト・ソースでも、サード・パーティ・ソースでも、同じ平均年齢を維持していることがわかる。画像は一貫してリソース年齢が2年であるのに対し、動画のリソースはほとんどが8~52週であった。

他のコンテンツタイプに分けると、サードパーティーのフォントは、8~52週間の間にもっとも多くキャッシュされ、72.4%であることがわかりました。しかし、ファーストパーティーの場合、リソースの年齢層は8~52週と2年以上に均等に分かれており、かなり大きなばらつきがあります。オーディオとスクリプトについても同様の結果が出ており、ファーストパーティでは8~52週、サードパーティでは1~7週が大半を占めています。

オーディオは、ファーストパーティーとサードパーティーの両方で、もっとも高度にキャッシュされたリソースでした。しかし、リソース年齢は、ファーストパーティ(平均8~52週間)とサードパーティ(わずか1~7週間)で大きく異なっていました。ファーストパーティーのオーディオリソースは更新頻度が、低い傾向があるため(なぜ?)、サードパーティーは、より新鮮なリソースを提供することでキャッシュの機会を利用している可能性があります。

キャッシュされたファーストパーティーのCSSの最大グループ(32.2%)は8~52週間の傾向があり、サードパーティーの最大グループは1週間未満で、その期間にキャッシュされたリソースは51.8%でした。

もっとも、HTMLは1週間未満で42.7%、サードパーティは1~7週間で43.1%と、ファーストパーティのグループがもっとも多くサービスを提供しています。

このデータを検討した上での考察。

  • ファーストパーティではHTML、サードパーティではCSSがもっとも新鮮なコンテンツです。
  • ファースト、サードパーティともに、もっとも陳腐化したコンテンツは画像です。

このデータからファーストパーティは、JSやCSSファイルへのリンクを含むHTMLコンテンツの更新を優先し、ブラウザ拡張機能のようにCSSやスクリプトを主体とするサードパーティは、CSSを最新の状態に保つことを優先していることがわかります。ファーストパーティとサードパーティの違いを考えると、サードパーティにとってコンテンツの配信方法は実際のコンテンツよりも重要であり、そのためコンテンツの表示と最適化が、より重要であることが分かります。

コンテンツ年齢と比較してキャッシュTTLが短すぎるとされたモバイルリソースは、2020年以降に改善が見られました。このデータは、コミュニティが適切な相対キャッシュについて理解を深めていることを示唆するものであり、エキサイティングなものです。

図23.18. 54%のモバイルリソースはTTLより古い

キャッシュTTLが長すぎると、古くなったコンテンツが、提供される可能性がありますが、短すぎるとエンドユーザーにとって何のメリットもありません。キャッシュTTLとコンテンツエイジの関係は、2020年の60.2%から2021年には54.3%となり、この差は徐々に縮まってきています。コンテンツエイジ(ページのHTMLやCSSなどの改修頻度)に対する気配りができればできるほど、より正確にキャッシュの上限を設定することができるようになるのです。

開発者はキャッシュ期間をコンテンツ年齢により正確に設定できるようになってきており、その結果、より責任ある、つまりより効果的なキャッシュを実現できるようになっています。

クライアント ファーストパーティ サードパーティ 全体
デスクトップ 59.5% 46.2% 54.3%
モバイル 60.1% 44.7% 54.3%
図23.19. 短いTTLを持つリクエストの割合。

ファーストパーティプロバイダーとサードパーティプロバイダーに分けると、もっとも改善されたのはサードパーティで、13.2%の改善が見られました。世界中の企業が、開発者向けにキャッシュを最適化する製品を開発していることは、非常に心強いことです。開発者コミュニティのパフォーマンス向上に対する関心の高まりが、サードパーティによるキャッシュ戦略の最適化を促し、さらにはその動機付けになった可能性があります。

しかし、今後数年間、ファーストパーティがどのように効果的な改善をしていくかという課題は残されています。

キャッシュの機会を特定する

図23.20. LighthouseキャッシュのTTLスコアの分布。

LighthouseのキャッシングTTLスコアに基づき、100点満点でランクインしたページが2020年の3.3%から2021年には4.4%に増加するという改善が見られました。

このスコアは、ページがキャッシュポリシーの追加的な改善によって恩恵を受けることができるかどうかを反映しています。31% のページが50パーセンタイルのスコアを上回ったことは喜ばしいことですが、25パーセンタイル以下の52% のページには大きな改善の可能性があります。

このことから、Webページにはある程度のキャッシュ機能が備わっていても、そのポリシーの使い方が古く、自社製品の最新状態に最適化されていないと考えざるを得ません。

図23.21. キャッシュによる潜在的なバイト数削減の分布。

2020年から2021年にかけてのLighthouseのwasted bytes監査に基づくと、繰り返し閲覧される監査対象の全ページにおいて、ムダなバイトが3.28%改善されました。これは、1MBをムダにするページの割合を42.8%から39.5%に下げ、有料のインターネットデータプランを持つ海外のユーザーにとって、よりコストの低い製品を作るというコミュニティのかなりの傾向を示しています。

現在、監査されたページのうち、ムダなバイトが0である割合は1.34%とまだ比較的低いです。今後数年間は、コミュニティがウェブパフォーマンスの最適化に注力し続けるため、この割合が増加することを期待しています。

結論

故・偉大なPhil Karltonは、「コンピューター サイエンスには難しいことが2つしかない」という有名な言葉を残しました。正直なところ、私はキャッシュがなぜそんなに難しいのか、いつも不思議に思っています。私の考えでは、キャッシュをうまく行うには、2つの重要な要素が必要です:シンプルに保つこと、そして潜在的なエッジケースをすべて理解することです。

残念ながらキャッシュを賢くしようとしすぎると、間違ったものをキャッシュしてしまったり、もっと悪いことに、過剰にキャッシュしてしまったりすることがあるのです。同じようなことですが、すべてのエッジケースを理解するには、広範な調査とテスト、そしてゆっくりとした漸進的な改良が必要です。それでも、古いブラウザがあなたをバスの下に投げ出さないことを願わなければなりません。しかし私たちがいまだに優れたキャッシュ戦略を追い求める理由は、ラウンドトリップリクエストの大幅な削減、サーバーの高い節約、ユーザーから求められるデータの削減、そして最終的にはより良いユーザー体験という、究極の報酬が非常に大きいからです。

どのような場合でも、キャッシュの最適な使用方法のプレイブックを用意するようにしてください。

  • 開発サイクルの初期段階、および製品出荷後のキャッシュ作業を優先させる。
  • 主要なエッジケースを再現するエンドツーエンドのテストを書く
  • 定期的なサイト監査と、古くなったり欠落している可能性のあるキャッシュルールの更新

最終的には私たちが仲間を指導し、理解しやすい良いドキュメントを書くことによって知識を広めることができればキャッシングは、それほど複雑なものではなくなります。キャッシングは、一部の人だけがマスターすればいいというものではありません。私たちの目標は、会社全体の共通認識として定着させることです。なぜなら、最終的に私たちが本当に注力したいのは、ユーザーにとって簡単で摩擦のない体験を構築することだからです。

著者