stale-while-revalidate で最新の状態を維持する

ウェブアプリの配信時に即時性と新鮮性のバランスを取るのに役立つ追加ツール。

stale-while-revalidate は、即時性(キャッシュに保存されたコンテンツをすぐに読み込む)と新しさ(キャッシュに保存されたコンテンツの更新が今後使用されるようにする)のバランスをとるのに役立ちます。定期的に更新されるサードパーティのウェブサービスまたはライブラリを使用している場合や、ファーストパーティ アセットの有効期間が短い傾向がある場合は、stale-while-revalidate を既存のキャッシュ ポリシーに追加すると便利です。

Cache-Control レスポンス ヘッダーで max-age とともに stale-while-revalidate を設定できる機能は、Chrome 75Firefox 68 で利用できます。

stale-while-revalidate をサポートしていないブラウザは、その構成値を無視し、max-age を使用します。後ほど説明します。

どういう意味ですか?

stale-while-revalidate を、キャッシュに保存されたレスポンスが古い可能性があるという考えと、再検証のプロセスの 2 つの部分に分割しましょう。

まず、キャッシュに保存されたレスポンスが「古い」かどうかをブラウザがどのように判断するかについて説明します。stale-while-revalidate を含む Cache-Control レスポンス ヘッダーには max-age も含める必要があります。max-age で指定された秒数が古さを決定します。max-age より新しいキャッシュに保存されたレスポンスは最新とみなされ、それより古いキャッシュに保存されたレスポンスは古い状態とみなされます。

ローカルにキャッシュに保存されたレスポンスがまだ新鮮な場合は、そのままブラウザのリクエストを処理できます。stale-while-revalidate の観点から、このシナリオでできることは何もありません。

ただし、キャッシュに保存されたレスポンスが古い場合は、別の age ベースのチェックが実行されます。キャッシュに保存されたレスポンスを stale-while-revalidate 設定で指定された追加の時間枠内に収める必要があります。

古いレスポンスの経過時間がこの期間内であれば、ブラウザのリクエストを処理するために使用されます。同時に、ネットワークに対して「再検証」リクエストが実行されます。その際、キャッシュに保存されているレスポンスの使用が遅延することはありません。返されたレスポンスには、以前にキャッシュに保存されたレスポンスと同じ情報が含まれている場合もあれば、異なる場合もあります。どちらの場合も、ネットワーク レスポンスはローカルに保存され、以前のキャッシュが置き換えられ、今後の max-age 比較で使用される「新しさ」タイマーがリセットされます。

ただし、キャッシュに保存された古いレスポンスが stale-while-revalidate の時間枠外にある場合は、ブラウザのリクエストを処理しません。代わりに、ブラウザはネットワークからレスポンスを取得し、そのレスポンスを最初のリクエストの処理とローカル キャッシュへの新しいレスポンスの入力の両方に使用します。

ライブ例

以下は、現在の時刻(具体的には、毎時現在の分数)を返す HTTP API の簡単な例です。

このシナリオでは、ウェブサーバーは HTTP レスポンスで次の Cache-Control ヘッダーを使用します。

Cache-Control: max-age=1, stale-while-revalidate=59

この設定では、1 秒以内に時刻のリクエストが繰り返された場合、以前にキャッシュに保存された値は引き続き新鮮であり、再検証なしでそのまま使用されます。

1~60 秒後にリクエストが繰り返された場合、キャッシュに保存された値は古くなりますが、API リクエストの処理に使用されます。同時に、「バックグラウンドで」再検証リクエストが行われ、今後使用するためにキャッシュに新しい値が入力されます。

60 秒を超えてリクエストが繰り返されると、古いレスポンスはまったく使用されず、ブラウザのリクエストの処理とキャッシュの再検証は、ネットワークからレスポンスを受け取るかどうかに左右されます。

次の表に、この例で各状態が適用される時間枠とともに、3 つの異なる状態の詳細を示します。

前のセクションの情報を示した図。

一般的なユースケース

上記の「分単位の API サービス」の例は架空のものですが、想定されるユースケースを示しています。更新が必要な情報を提供するサービスですが、ある程度の古さは許容されます。

あまり不自然でない例としては、現在の天候の API や、過去 1 時間に作成されたトップニュースの見出しなどがあります。

通常、既知の間隔で更新され、複数回リクエストされる可能性があり、その間隔内で静的なレスポンスは、max-age による短期キャッシュに適しています。max-age に加えて stale-while-revalidate を使用すると、ネットワーク レスポンスをブロックせずに、より新しいコンテンツでキャッシュから今後のリクエストに対応できる可能性が高くなります。

サービス ワーカーとどのように連携しますか?

stale-while-revalidate について聞いたことがある場合は、サービス ワーカー内で使用されるレシピのコンテキストで聞いた可能性があります。

Cache-Control ヘッダーを介した stale-while-revalidate の使用は、サービス ワーカーでの使用といくつかの類似点があり、新しさのトレードオフと最大存続期間に関する多くの考慮事項が適用されます。ただし、Service Worker ベースのアプローチを実装するか、Cache-Control ヘッダー構成のみを使用するかを決定する際には、考慮すべき点がいくつかあります。

次の場合は、サービス ワーカー アプローチを使用します。

  • ウェブアプリで Service Worker をすでに使用している。
  • キャッシュの内容をきめ細かく制御し、最近使用されていないものから期限切れになるようにするポリシーを実装する必要がある。Workbox の Cache Expiration モジュールが、この問題の解決に役立ちます。
  • 再検証ステップ中に古いレスポンスがバックグラウンドで変更されたときに通知を受け取る。Workbox の ブロードキャスト キャッシュ更新モジュールが役立ちます。
  • この stale-while-revalidate の動作は、すべての最新ブラウザで必要です。

次の場合に Cache-Control アプローチを使用します。

  • ウェブアプリの Service Worker のデプロイとメンテナンスのオーバーヘッドを処理したくない。
  • ブラウザの自動キャッシュ管理によって、ローカル キャッシュが大きくなりすぎないようにします。
  • 現時点では、すべての最新ブラウザでサポートされていないアプローチでも問題ありません(2019 年 7 月現在。今後サポートが拡大される可能性があります)。

Service Worker を使用している場合、Cache-Control ヘッダーを介して一部のレスポンスに対して stale-while-revalidate が有効になっている場合、通常、Service Worker がリクエストへの応答の「最初の試行」を行います。Service Worker が応答しないと判断した場合、またはレスポンスの生成プロセス中に fetch() を使用してネットワーク リクエストを送信した場合は、Cache-Control ヘッダーで構成された動作が有効になります。

その他の情報

ヒーロー画像: Samuel Zeller による