🌐

フロントエンドエンジニアが知るべきキャッシュを理解する

2022/04/03に公開
3

キャッシュは、CPUのバスやネットワークなど様々な情報伝達経路において、ある領域から他の領域へ情報を転送する際、その転送遅延を極力隠蔽し転送効率を向上するために考案された記憶階層の実現手段である。(引用: フリー百科事典『ウィキペディア(Wikipedia)』)

こんにちは、@kaa_a_zu です。私たちエンジニアは、「キャッシュ」というワードをよく口にしています。それはインフラの設計をしている時かもしれないし、表示されるコンテンツが変わらない時かもしれないし、パフォーマンスの改善をしている時かもしれません。普段何気なく使っている「キャッシュ」とは一体何なのでしょうか。この記事は、そんな「(Webフロントエンドを触るエンジニアが知るべき)キャッシュ」について、どんなものがあるのかがちょっと分かったという状態になることを目的に書いています。

リソースリクエストの全体像

Webブラウザがリクエストをしてリソースを取得する時のプロセスは大まかに以下図のようになっています。

リソースを取得するプロセス

上図の通りではありますが、クライアントからリクエストが来た時「キャッシュがあればそれを返し、なければよりオリジンに近い部分に問い合わせをする」を繰り返していきます。

この記事では、「(Webフロントエンドを触るエンジニアが知るべき)キャッシュ」が対象なので

  • クライアント(Webブラウザ)側のキャッシュ
  • CDN
  • サーバーサイド側のキャッシュ

のうち「クライアント(Webブラウザ)側のキャッシュ」についての詳細を以下にダラダラと書いていきます。

クライアント(Webブラウザ)側のキャッシュ

インメモリキャッシュ

ChromeやFirefox、Safariなど世の中で利用されているブラウザにはインメモリなキャッシュがあります。インメモリキャッシュはRAMに保存されるため、書き込みとアクセスが高速になりますが、コンピュータの電源がオフになるか、その他の特定の状況で消去されます。このインメモリキャッシュは、各ブラウザのRendererプロセスに存在しています。

例えば、ChromiumのBlinkレンダリングエンジンでは以下のように実装されています。
https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/core/fetch/MemoryCache.cpp
コードを見てわかる通り、インメモリキャッシュには上限があり、8192MBKBと指定されています。また、LRU方式でキャッシュが削除されていることがわかります。(※Chromeに割り当てるメモリの量を現在は変えることが出来ません)

余談にはなりますが、Chrome97でも完全に導入されたBack/Forward Cacheも、このインメモリキャッシュにあたります。

ServiceWorkerキャッシュ

ご存知の通り、ServiceWorkerはHTTPをinterceptします。そしてServiceWorkerは、Cache Storage API もしくは、IndexedDBを用いて、リソースのキャッシュを行います。ServiceWorkerが他のキャッシュと大きく違うのは、HTTPをinterceptして、キャッシュの操作をプログラマブルに行うことができるという点です。そのため、リソース単位でのキャッシュ戦略を詳細に立てることができます。
ただ、(あくまで私の経験上の話ですが)ServiceWorker以外のキャッシュがクライアントには存在するため、リソース単位でのキャッシュ戦略を立てたところでパフォーマンスがより良くなるケースはありませんでした。そのため、ServiceWorkerキャッシュは、オフライン時のユーザー体験をより良くすることにのみ利用しています。この点については、皆様の知見をいただきたいです。

ServiceWrokerキャッシュを操作する際には、直接WebAPIを使うことは珍しく、多くの場合はWorkBoxのようなラッパーを利用することが一般的だと思います。(因みにWorkBoxはデフォルトでCacheStorageAPIを利用しています)

因みにMacの場合、それぞれはデフォルトで次の場所に保存されていることが確認できます。
IndexedDB(Mac):
~/Library/Application Support/Google/Chrome/Default/IndexedDB
Cache Storage(Mac):
~/Library/Application Support/Google/Chrome/Default/Service Worker/CacheStorage

ブラウザキャッシュ

エンジニアが最も意識することが多いのがRFC7234で定められているブラウザキャッシュ(=HTTPキャッシュ≒ディスクキャッシュ)だと思います。インメモリキャッシュの章でも書きましたが、ブラウザキャッシュは、Browserプロセスに存在しているものです。ブラウザキャッシュは、(HDDやSSDといった)ストレージに書き込まれます。そのため、RAMを利用しているインメモリキャッシュと比較すると、読み取りと書き込みに時間がかかります。既に訪問したことがあるページに再訪し、DevToolsのSizeをみるとmemory cachedisk cache が分かれていることが確認できると思います。また memory cache には Resource Scheduling(Queueing)フェーズ がないのに対して disk cache にはあることがわかります。時間にすると些細な差ではありますが、キャッシュの種類が違うことと、ブラウザの挙動に影響があることが分かります。繰り返しになりますが、ブラウザキャッシュはRAMを利用していない(揮発性ではない)ので半永続的に残ります。つまり、PCをシャットダウンしたとしても、ページ再訪時にはキャッシュを用いた高速なコンテンツ取得が可能になります。

memory cache と disk cache が書かれている DevTool Network タブ

ブラウザキャッシュの振る舞いや有効期限は、HTTPヘッダーを用いて操作を行います。キャッシュ操作の方法は本筋からはずれるため詳しくはMDN をご覧ください。

もしもこれより先にリクエストが飛んだ場合(キャッシュにヒットしなかった場合や、キャッシュ更新をした場合)には、結果であるHTTPResponseは以下2つに保存されます。

  1. ブラウザキャッシュ(これにはMediaCacheと通常キャッシュという2つの専用ストレージがあります)
  2. インメモリキャッシュ

クライアント(Webブラウザ)以降のキャッシュ

冒頭の図にあるように、クライアント(Webブラウザ)側のキャッシュ加えて、CDNでのキャッシュやサーバー側のキャッシュが存在します。キャッシュを用いることによって、ネットワークコストを削減できたり、パフォーマンスを向上させることが可能なため、サービス特性に合ったキャッシュ戦略を練ることが現代のエンジニアリングにおいては大事だと考えられています。しかしながら、単にキャッシュと言っても上述したように様々なものが存在します。それぞれの特性や流れを理解した上で利用していきたいなという自戒を込めた記事でした。ここまで読んでくださり、ありがとうございました。誤った解釈がありましたら、ご意見いただけると嬉しいです。

Discussion

lambdadbmallambdadbmal

ChromiumのBlinkレンダリングエンジンのインメモリキャッシュは、ソースを見ると8MBと思うのですがいかがでしょうか? 文中だと8192MB → 8GBに読めてしまったもので。

カーーズカーーズ

@lambdadbmal 様

ご指摘ありがとうございます!おっしゃる通りで、 MB は KB の誤りです。
修正をしました🙌

カーーズカーーズ

言及していなかったのですが、リソース以前にIPのキャッシュ(DNSキャッシュ)もPCとChromeに存在しますね。chromiumの場合はここで消せます chrome://net-internals/#dns

が、WebフロントエンジニアがDNSキャッシュを意識することはないと思い...ま...(記事を書く時には意識していませんでした🙇‍♂️)