Webサイトのキャッシュ戦略を整理する

現在、Web上にはたくさんのWebサイトが存在しています。見る側も昔と比べ格段に増え、現在はスマホをもつ世界中の人々がアクセスしてきます。

そのため、なにかしらWebサイトを立ち上げる際には現在はCDNなどを使用したキャッシュの仕組みが欠かせません。このエントリではWebサイトのケースによってどのようにキャッシュをとり負荷を減らすことができるのかを整理しつつまとめていきます。

Webサイトのキャッシュ

Webサイトをキャッシュする方法は数種類あります。

  • CDNなどを使用したページ全体、コンテンツ全体のキャッシュ
  • Redisなどのインメモリデータベースを用いたキャッシュ
  • ブラウザ側に保存するキャッシュ

他にもあるかもしれませんが、ぱっと思い浮かべるキャッシュはこのあたりかと思います。それぞれの方法にはメリットやデメリットが存在するのでどれが正解ということはなく必要なケースに応じて選択する必要がありそうです。

CDNなどを使用したキャッシュ

多分、現在のWebサイトではほとんどがCDNを通っているかと思います。有名なCDNサービスとしては Cloudflare や Akamai 、 Fastly 、 CloudFront などでしょうか。
WebサイトでこれらのどのCDNを使っているかというのは簡単に調べることができます。レスポンスのHTTPヘッダーにServerViaがついており、例えばCloudflareはServer: cloudflareCf-Cache-Statusなどが付与され、Fastlyなんかは内部でVarnish*1というOSSのHTTPアクセラレーターを使っているためVia: 1.1 varnishが付与されます。

Cloudfrareの例 `Server: cloudflare`が付与されている

Fastlyの例 内部ではVarnishを使っているため `Via: 1.1 varnish` が付与される

このようなCDNのキャッシュ制御はExpiresなども使えますが基本的にはCache-Controlヘッダーを使うかと思います。詳しくはMDNを読むといいでしょう*2

developer.mozilla.org

CDNのキャッシュのみを制御するなら、s-maxageが一番良いです。

Redisなどのインメモリデータベースを用いたキャッシュ

GraphQLのキャッシュや、APIのキャッシュなどでよく使用されるイメージです。RDSで取得する場合SELECTするとやはり時間がかかるし高負荷になった際に大変になります。RDSなどはレプリケーション*3をしてレプリカインスタンスから読むという方法もありますが、GraphQLで同じ情報を複数取得するなどの場合は一度インメモリデータベースに入れてしまうことでパフォーマンス改善することができます。

ブラウザ側に保存するキャッシュ

Cache-Controlヘッダーでmax-ageを指定したりETagヘッダーを付与しておくことでブラウザ側でキャッシュすることが可能です。プライベートキャッシュが正式名称っぽい。変更しない画像などに設定しておくことでリクエストが飛ばない(一部飛ぶ)ので表示も早くなる可能性があります。

developer.mozilla.org

developer.mozilla.org

ケースに応じたキャッシュ

CDNのキャッシュ設定はWebサイトのケースに応じた設定をすると良いです。ケースに応じた設定とは、ピークタイミング、認証が必要か、リアルタイムで更新が必要かどうかなどです。

ピークタイミングは全体的に緩やかで認証は必要ないケース

ブログやまとめサイトのようなケースになると思います。こういう場合はキャッシュは長くても短くても問題ないかと思います。しかし、記事の更新時などにはうまくキャッシュを更新する仕組みが必要です。
考えられる方法としては、

  • キャッシュ間隔を1分など短い間隔にしておく Cache-Control: s-maxage: 60, stale-while-revalidate
  • 記事更新された場合にパージする
  • 記事執筆者だけキャッシュ無しで見せて他は5分ほどのキャッシュにする

があるかと思います。一番簡単なのは1つめですが1分おきにリクエストがあれば都度オリジンにアクセスが来るのでここの調整は難しいです。3番目の執筆者だけキャッシュをしない方法は zenn.dev がやられている方法*4です。CDNでやるなら、セッション用Cookieをキャッシュキーに追加し、オリジン側ではCookieがついている場合はCache-Control: no-storeとかにすると実現できると思います。
2番目のパージは基本的にCDNはできないと考えたほうがいいです。もしどうしてもやりたい場合は、パージが高速に行われるVarnishを直接EC2などで動かしてつかうかFastlyを使ってAPI経由でパージするかです。ちなみに、FastlyのAPIパージは0.1秒ぐらいでできるので結構体験は良いです。

ある一定時間だけアクセス数が爆増する(スパイクする)ケース

Appleのイベント後の購入ページとかが一番わかり易いかなと思います。多分あれめちゃくちゃアクセス来ます。
基本的にCDNから配信されていれば耐えられると思いますが、もし1分だけ爆増し、その時間がわかっている場合などではそのタイミングでキャッシュの期限が切れてオリジンへのアクセスが来ないように気をつけるといった対策も有効です。また、Redisなどに事前キャッシュを作っておくという手も存在します。

リアルタイム性が求められるケース

この場合はキャッシュをすることが難しくなります。方法としてはページレイアウトなどキャッシュできる部分はキャッシュし、リアルタイム性を求める部分はAPIとするのが一般的かと思います。HTMLの一部分だけキャッシュするという方法が ASP.NET に存在しますがぶっちゃけ混乱しそうというのが私の考えです*5。できるだけ構成をシンプルにするのに越したことはないと思います。

atmarkit.itmedia.co.jp

最近ではAbema.tvがワールドカップの配信でCDNの活用が話題になっていました。Abemaではm4sというストリーミング形式のファイルを配信することでストリーミングさせているようで、これらのファイルをCDN通すことでキャッシュしています。そのため、数秒から数分の遅延が生じるのかと思われます。

www.businessinsider.jp

www.cyberagent.co.jp

まとめ

キャッシュは楽しい。

説明が間違っている箇所を見つけた場合、Twitterやはてブなどでご指摘ください。