CloudFront Functionsを使ってキャッシュ時からの経過時間ではなく現在の時刻ベースのキャッシュ制御をしてみた
CloudFrontのエッジにキャッシュされてからの時間ではなく、現在の時刻ベースでコンテンツをキャッシュするようにキャッシュさせたい
こんにちは、のんピ(@non____97)です。
皆さんはCloudFrontのエッジにキャッシュされてからの時間ではなく、現在の時刻ベースでコンテンツをキャッシュさせたいなと思ったことはありますか? 私はあります。
例えば、帳票などコンテンツ内にアクセス日の日付を付与する場合を考えます。
印字されている日付の間にリクエストが発生した場合はキャッシュを返し続けたいですが、翌日になったタイミングでその日を印字したコンテンツを返したいです。
「それならキャッシュポリシーでTTLを1日にすれば良いじゃないか」と思われるかもしれません。しかし、ここで設定されるTTLはエッジにキャッシュされてからの経過時間です。
例えば、0:00ちょうどにコンテンツを切り替えたい場合、TTLを一日にして23:59にキャッシュされると、翌日の23:59までCloudFrontのキャッシュを保持してしまいます。
「では、EventBridge SchedulerでCloudFrontのキャッシュを定期的に削除するStep Functionsなどを実行すれば良いではないか」と思われるかもしれません。しかし、EventBridge Schedulerは実行まで数十秒のラグがあります。
また、CloudFrontのキャッシュを削除するCreateInvalidation APIもリクエストしてから、キャッシュが削除されるまでラグがあります。
だからと言ってTTLを1秒にするのもなんだかスマートではありません。
0:00にちょうどコンテンツを切り替える場合の対応として、CloudFront Functionsでカスタムヘッダーを付与し、そのカスタムヘッダーをキャッシュキーとする方法が考えられます。
実際にやってみたので紹介します。
やってみた
検証環境
検証環境は以下のとおりです。
検証環境は全てAWS CDKでデプロイしました。使用したコードは以下GitHubリポジトリに保存しています。
ベースは以下記事です。
デフォルトTTLは12時間としています。
this.distribution = new cdk.aws_cloudfront.Distribution(this, "Default", {
defaultBehavior: {
origin:
cdk.aws_cloudfront_origins.FunctionUrlOrigin.withOriginAccessControl(
messagePdfLambda.addFunctionUrl({
authType: cdk.aws_lambda.FunctionUrlAuthType.AWS_IAM,
})
),
allowedMethods: cdk.aws_cloudfront.AllowedMethods.ALLOW_GET_HEAD,
cachedMethods: cdk.aws_cloudfront.CachedMethods.CACHE_GET_HEAD,
cachePolicy: new cdk.aws_cloudfront.CachePolicy(
this,
"MessageCachePolicy",
{
minTtl: cdk.Duration.seconds(1),
maxTtl: cdk.Duration.days(1),
defaultTtl: cdk.Duration.hours(12),
enableAcceptEncodingBrotli: true,
enableAcceptEncodingGzip: true,
queryStringBehavior:
cdk.aws_cloudfront.CacheQueryStringBehavior.allowList("message"),
headerBehavior:
cdk.aws_cloudfront.CacheHeaderBehavior.allowList(
"x-datetime-block"
),
cookieBehavior: cdk.aws_cloudfront.CacheCookieBehavior.none(),
}
),
viewerProtocolPolicy:
cdk.aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
responseHeadersPolicy:
cdk.aws_cloudfront.ResponseHeadersPolicy.SECURITY_HEADERS,
functionAssociations: [
{
function: datetimeBlockCf2,
eventType: cdk.aws_cloudfront.FunctionEventType.VIEWER_REQUEST,
},
],
},
httpVersion: cdk.aws_cloudfront.HttpVersion.HTTP2_AND_3,
priceClass: cdk.aws_cloudfront.PriceClass.PRICE_CLASS_ALL,
domainNames: props.domainName ? [props.domainName] : undefined,
certificate: props.domainName
? props.certificateConstruct?.certificate
: undefined,
logBucket: props.cloudFrontAccessLogBucketConstruct?.bucket,
logFilePrefix: props.logFilePrefix,
});
Lambda関数の説明
Lambda関数ではsPDFを使って、クエリmessage
で指定された文字列と10分単位で切り捨てた時刻をPDFに印字して、出力します。
10分単位で切り捨てた時刻の例は2025/2/4 17:13:30 にアクセスして来たら、2025/2/4 17:10
と表示するといった形です。
今回は日本語フォントをインストールしていないので、マルチバイト文字をmessage
で渡された場合は400エラーを返すようにしています。同様にそもそもmessage
を指定していない場合や、30文字以上指定された場合も400エラーにしています。
実際のコードは以下のとおりです。
CloudFront Functionsの説明
CloudFront FunctionsではLambda関数と同じく10分単位で切り捨てた時刻を計算し、x-datetime-block
ヘッダーを付与します。
x-datetime-block
ヘッダーをキャッシュキーとしているため、x-datetime-block
の値が変わったタイミング = 10分単位でキャッシュミスとなり、オリジンへ問い合わせが発生します。
実際のコードは以下のとおりです。
動作確認
実際に動作確認してみます。
18:01にmessage=test1
と指定してアクセスします。
test1
とTimestamp: 2025/2/4 9:00 block
とが印字されています。
また、X-Cache
レスポンスヘッダーを確認すると、Miss from cloudfront
となっていることからキャッシュヒットせず、オリジンに問い合わせが行われたことが分かります。
続いて、18:01にmessage=test2
と指定してアクセスします。
test2
とTimestamp: 2025/2/4 9:00 block
とが印字されています。
こちらも同様にX-Cache
レスポンスヘッダーがMiss from cloudfront
となっていることからキャッシュヒットせず、オリジンに問い合わせが行われたことが分かります。
キャッシュヒットするかどうか確認します。
18:09にmessage=test1
とmessage=test2
を指定してアクセスします。
どちらもX-Cache
レスポンスヘッダーがHit from cloudfront
となっていることからキャッシュヒットしたことが分かります。
それでは18:10:02にmessage=test1
を指定してアクセスします。
Timestamp: 2025/2/4 9:00 block
からTimestamp: 2025/2/4 9:10 block
と変わりました。
また、X-Cache
レスポンスヘッダーがMiss from cloudfront
となっていることからオリジンへ問い合わせしていることが分かります。意図したとおりです。
18:11にmessage=test2
を指定してアクセスした場合もキャッシュヒットせず、表示される時刻が更新されていました。
Cache PolicyやCache-Controlディレクトティブで制御仕切れない場合はCloudFront Functionsも検討しよう
CloudFront Functionsを使ってキャッシュ時からの経過時間ではなく現在の時刻ベースのキャッシュ制御をしてみました。
Cache PolicyやCache-Controlディレクトティブで制御仕切れない場合はCloudFront Functionsも検討すると良いでしょう。
CloudFrontのキャッシュ制御のお作法は以下AWS Black Beltが非常に参考になります。
この記事が誰かの助けになれば幸いです。
以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!