⛏️

stale-while-revalidateから紐解くNext.jsのISR

2022/01/24に公開

こんにちは。みやぞんです。

前回の記事ではLPの記事投稿部分のヘッドレスCMS化に伴い、Next側ではどのようにレンダリングするのが正解なのだろうと色々考え、結果的にはSGを選択したと説明しました。しかし後になってISRに関する理解が誤っていたことに気が付き二転三転としたところでISRの採用に落ち着きました。(修正してます)

https://zenn.dev/hk_206/articles/6981eca2fb56c5

今回は、そのときに少し深掘して学んだISRの挙動についての記事になります。

CDNとは

まずはWebの基本知識であるCDNについてです。ISRを理解する上でキャッシュやCDNの理解は必須です。CDNとはContents Delivery Networkのことで複数のキャッシュサーバ(エッジサーバとも呼ばれる)で構成されたネットワークのことです。オリジンサーバとクライアントの間に位置するネットワークで、以下の利点があります。

  • 負荷分散(オリジンサーバにアクセスが集中しない)
  • キャッシュによるコンテンツ配信の高速化
  • 可用性向上


画像:カゴヤのサーバ研究室

CDNではアクセスするクライアント間でキャッシュが共有されます。

もう少し詳しい説明はこちら。
https://gihyo.jp/book/pickup/2021/0012

Vercel エッジネットワーク

Vercel製のCDNのようなものです。エッジネットワークは世界中の数十の場所で稼働しています。

https://vercel.com/docs/concepts/edge-network/overview

AWSもエッジロケーションっていうのが至るところにありますよね。

Vercelのキャッシュについて

静的ファイルは最初のリクエストの後、自動的にキャッシュされます。(最大31日間)
また、再デプロイ時にはキャッシュは事実上無効となります。

静的ファイルはハッシュによってキャッシュされるため、ファイルが変更されない場合はデプロイをまたいで持続することができます。

https://vercel.com/docs/concepts/edge-network/caching

stale-while-revalidate

Vercelのキャッシュの特徴は「stale-while-revalidate」というCache-Controlヘッダーの拡張機能をサポートしている点です。

簡単に説明すると、キャッシュが再生成される時間を決めて、キャッシュ生成されてから指定の時間が生成するとリクエストをトリガーにして新しいキャッシュがバックグラウンドで生成されるというものです。
つまり動的コンテンツであっても静的ページを定期的に更新することでキャッシュできるようになります。

詳しい挙動はISRと一緒に追っていくとわかりやすいと思います。

ISRの挙動について

ここまできたらISRは実際にどのように動いているのかを追ってみましょう。

Vercel公式の画像を借りて説明します。

  1. コンテンツのキャッシュが未生成の場合はオリジンサーバにリクエストを送りSSRでページを生成する。またこのときキャッシュサーバー(CDN)にキャッシュを作る。
  2. 次のリクエストからキャッシュサーバがキャッシュを返す。
  3. キャッシュが生成されてからrevalidationで設定した秒数が経過するまでは同じキャッシュを返し続ける。
  4. 指定した秒数が経過してから初めてのリクエスト時に、それがトリガーとなってキャッシュ(ver.2)を再生成する。このアクセスではまだ古いキャッシュ(ver.1)が返される。(つまり単に指定した秒数が経過しただけでは勝手にキャッシュを再生成することはない)
  5. 新しくキャッシュが生成されてからその次のリクエストからは新しいキャッシュが返される。

これが「stale-while-revalidate」というキャッシュ戦略を利用したISRの動作です。

ちなみに、developer toolでCache-Controlを確認するとISRのどのフェーズにいるのかがわかります。

MISS: キャッシュが生成されていないのでSSRによってページを生成した
HIT: ISRによって生成された新しいキャッシュを利用中
STALE: キャッシュのrevalidate秒数を超えているので古いキャッシュを返しつつ、裏で新しいキャシュを生成します。次のアクセスから新しいキャッシュを返します(HIT)

https://vercel.com/docs/concepts/next.js/incremental-static-regeneration

CDNは共有キャッシュなのでこのサイクルはクライアント一人ひとりで生じるわけではなく、サイクルごと共有されている。つまりAさんが「キャッシュが作られていない状態のアプリケーション」にアクセスしたとき、それがトリガーとなってSSRが実行され同時にキャッシュも生成されます。その後すぐにBさんがアクセスしたらBさんが受け取るのはすでに生成されているキャッシュとなるのです。

なので最初にアクセスしたAさんは二回目のアクセスからからキャッシュが提供がされ、それ以外のクライアントは初回からキャッシュが提供されます。

ここまでISRのことを学んでみると、前の記事でも書いたとおり、強整合性が求められるようなページでない限りは動的ページはISR一択なのでは改めて感じます。(SEOやOGPの関係ないところではCSRもありだとは思います。)

最後に

今回は悔しい勘違いでしたが、おかげでISRの正しい理解にたどり着くことができました...。
公式ドキュメントに必ず正しい仕様は書いてあるので、面倒臭がらずに穴が空くほど目を通さないとなと感じました。エンジニアになりたての頃は心がけていたはずでしたが...。精進します!

Discussion