[アップデート] Amazon CloudFront がオリジン Cache-Control ヘッダーの stale-while-revalidate と stale-if-error をサポートしました
いわさです。
CloudFront ではオリジンの Cache-Control で個々のオブジェクトに対するキャッシュの挙動を制御することが可能です。
本日のアップデートでstale-while-revalidate
とstale-if-error
を CloudFront でも利用出来るようになりました。
それぞれの仕様は以下で確認出来ます。
stale-while-revalidate
ディレクティブは、キャッシュの有効期間が切れた後も一定期間は古いキャッシュを利用することが出来ます。
その間にバックグラウンドで非同期でオリジンレスポンスを取得してキャッシュを最新化することが出来る仕組みです。
これによって、これまではキャッシュが切れたタイミングで発生するオリジンへの同期リクエストを回避することが出来ます。
stale-if-error
ディレクティブは、オリジンがエラーステータス(ステータスコード 500, 502, 503, 504)を応答する場合に一定期間は古いキャッシュを使用します。
キャッシュの有効期間が切れて、オリジンへのリクエストが発生した際にエラーになっても、一定期間は古いキャッシュでカバーすることが出来ます。
Lambda + CloudFront で検証してみる
今回次のように関数 URL を有効化した Lambda をオリジンにして、CloudFront を前段に配置する構成で検証してみました。
Lambda でレスポンスヘッダーなどを生成させます。
キャッシュが使われているかわかるように、レスポンスボディはタイムスタンプを使います。
また、同期的にオリジンリクエストが発生しているかわかるように 10 秒間のスリープを設定しています。
オリジンで指定するmax-age
ディレクティブは 60 秒です。CloudFront のキャッシュポリシーにも依存しますが、60 秒間 CloudFront 側で保持するように指示しています。
import json import time def lambda_handler(event, context): time.sleep(10) return { 'statusCode': 200, 'headers': { "Content-Type": "application-json", "Cache-Control": "max-age=60" }, 'body': json.dumps(time.time()) }
あとは関数 URL をオリジンに CloudFront を構成するだけです。
なお、マネージドポリシーの CachingOptimized は次のような構成になっています。
cURL でアクセスすると次のようになります。
初回は応答まで 11 秒かかりましたが、2 回目以降は同じレスポンスが応答されているのでキャッシュが使われていることがわかりますね。応答時間も 0.3 秒になりました。
% curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361140.8902435 11.575475 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361140.8902435 0.349105
また、max-age
が 60 なので上記から約 60 秒経過すると次のように、またオリジンへのリクエストが発生し、約 10 秒応答の生成に必要なタイミングがあります。
それ以降はまた 60 秒間キャッシュが使われる感じです。
% curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361140.8902435 0.305020 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361210.9583316 10.611459 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361210.9583316 0.447639
stale-while-revalidate
まずはstale-while-revalidate
を使ってみます。
Lambda でCache-Control
に次のようにstale-while-revalidate
を 30 で追加します。
import json import time def lambda_handler(event, context): time.sleep(10) return { 'statusCode': 200, 'headers': { "Content-Type": "application-json", "Cache-Control": "max-age=60, stale-while-revalidate=30" }, 'body': json.dumps(time.time()) }
初回はオリジンリクエストが発生しています。
数秒ごとにリクエストを送信し続けます。60 秒間はキャッシュが使われていることがわかります。
# 初回 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361313.878341 10.827932 # 60秒後 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361313.878341 0.687723 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361313.878341 0.386813 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361384.4733567 0.302307 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361384.4733567 0.402332
さらに、60 秒経過後も一定期間は古いキャッシュが使用され続けています。
この間は古いキャッシュを使用しつつ、バックグラウンドでオリジンにアクセスしています。
オリジンから新しいレスポンスが取得された後は新しいレスポンスがキャッシュされて使用されていますね。
通常発生する同期的なオリジンリクエストの待機が発生せずに、新しいキャッシュがすぐに使われているように見えます。
なお、この機能は有効期限が切れた後のstale-while-revalidate
で設定した間古いキャッシュが使われるというものなので、その期間にリクエストが発生しなければ非同期でのオリジンリクエストも発生せずに、先程の Lambda でいうと 90 秒の間にリクエストが発生していない場合は通常どおりオリジンへの同期的なリクエストが発生し、10 秒間待機することになります。
stale-if-error
続いてstale-if-error
です。
こちらは先程と少し似ているのですが、有効期間が切れた後も、一定期間はオリジンでエラーが発生した場合でも古いキャッシュを使い続けてくれるというものです。
ここでは次のようにstale-if-error
へ 300 を設定しました。
import json import time def lambda_handler(event, context): time.sleep(10) return { 'statusCode': 200, 'headers': { "Content-Type": "application-json", "Cache-Control": "max-age=60, stale-if-error=300" }, 'body': json.dumps(time.time()) }
まずは正常な形でキャッシュさせます。
% curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361529.9832242 10.708288 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361529.9832242 0.441098
続いて Lambda のステータスコードを 500 に変更します。
import json import time def lambda_handler(event, context): time.sleep(10) return { 'statusCode': 500, 'headers': { "Content-Type": "application-json", "Cache-Control": "max-age=60, stale-if-error=300" }, 'body': json.dumps(time.time()) }
1 分後キャッシュが切れたタイミングでアクセスしてみるとどうでしょうか。
% curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361529.9832242 0.441098 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361529.9832242 10.881692 % curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684361529.9832242 10.412313
期待どおり古いキャッシュが使用され続けていますね。
ただし、オリジンレスポンスを評価して古いキャッシュを使うかどうか判断しているので上記のように都度 10 秒の待機時間が発生しています。
こちらのディレクティブはあくまでもオリジンエラーを一定期間回避するためのもので、先程のようにオリジンの同期待ちを回避するためのものではないのでご注意ください。
2 つのディレクティブを組み合わせる必要があります。
さらに、5 分程度後にアクセスしてみると次のようにエラーレスポンスが取得されました。
% curl https://d1d708qrpnpap.cloudfront.net/ -w '\r\n%{time_total}' 1684362059.602223 10.550493
さいごに
本日は Amazon CloudFront がオリジン Cache-Control ヘッダーの stale-while-revalidate と stale-if-error をサポートしたので試してみました。
CloudFront のキャッシュは強力ですが、キャッシュ有効期間の切れ目って気になりますよね。
その切れ目の期間にアクセスが集中した場合は Request Collapsing という CloudFront の仕様でオリジンへの負荷がかからないようになっていたりします。
ただ、その場合でもオリジンがレスポンスを生成するまでの待機時間は発生するので、今回サポートされた Cache-Control ディレクティブをうまく使うことでクライアント側の操作性を向上出来る気がします。