CloudFront Functionsを使ってキャッシュ時からの経過時間ではなく現在の時刻ベースのキャッシュ制御をしてみた

CloudFront Functionsを使ってキャッシュ時からの経過時間ではなく現在の時刻ベースのキャッシュ制御をしてみた

Cache PolicyやCache-Controlディレクトティブで制御仕切れない場合はCloudFront Functionsも検討しよう
Clock Icon2025.02.04

CloudFrontのエッジにキャッシュされてからの時間ではなく、現在の時刻ベースでコンテンツをキャッシュするようにキャッシュさせたい

こんにちは、のんピ(@non____97)です。

皆さんはCloudFrontのエッジにキャッシュされてからの時間ではなく、現在の時刻ベースでコンテンツをキャッシュさせたいなと思ったことはありますか? 私はあります。

例えば、帳票などコンテンツ内にアクセス日の日付を付与する場合を考えます。

印字されている日付の間にリクエストが発生した場合はキャッシュを返し続けたいですが、翌日になったタイミングでその日を印字したコンテンツを返したいです。

キャッシュのイメージ.png

「それならキャッシュポリシーで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でカスタムヘッダーを付与し、そのカスタムヘッダーをキャッシュキーとする方法が考えられます。

実際にやってみたので紹介します。

やってみた

検証環境

検証環境は以下のとおりです。

CloudFront Functionsを使ってキャッシュ時からのTTLではなく時間ベースのキャッシュ制御してみた検証環境構成図.png

検証環境は全てAWS CDKでデプロイしました。使用したコードは以下GitHubリポジトリに保存しています。

https://github.com/non-97/aws-cdk-cloudfront-periodic-cache

ベースは以下記事です。

https://dev.classmethod.jp/articles/aws-cdk-cloudfront-oac-lambda-function-url/

デフォルトTTLは12時間としています。

./lib/construct/contents-delivery-construct.ts
    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エラーにしています。

実際のコードは以下のとおりです。

https://github.com/non-97/aws-cdk-cloudfront-periodic-cache/blob/main/lib/src/lambda/message-pdf/index.ts

CloudFront Functionsの説明

CloudFront FunctionsではLambda関数と同じく10分単位で切り捨てた時刻を計算し、x-datetime-blockヘッダーを付与します。

x-datetime-blockヘッダーをキャッシュキーとしているため、x-datetime-blockの値が変わったタイミング = 10分単位でキャッシュミスとなり、オリジンへ問い合わせが発生します。

実際のコードは以下のとおりです。

https://github.com/non-97/aws-cdk-cloudfront-periodic-cache/blob/main/lib/src/cf2/datetime-block/index.js

動作確認

実際に動作確認してみます。

18:01にmessage=test1と指定してアクセスします。

5.test1_1801.png

test1Timestamp: 2025/2/4 9:00 blockとが印字されています。

また、X-Cacheレスポンスヘッダーを確認すると、Miss from cloudfrontとなっていることからキャッシュヒットせず、オリジンに問い合わせが行われたことが分かります。

続いて、18:01にmessage=test2と指定してアクセスします。

6.test2_1801.png

test2Timestamp: 2025/2/4 9:00 blockとが印字されています。

こちらも同様にX-CacheレスポンスヘッダーがMiss from cloudfrontとなっていることからキャッシュヒットせず、オリジンに問い合わせが行われたことが分かります。

キャッシュヒットするかどうか確認します。

18:09にmessage=test1message=test2を指定してアクセスします。

8.test1_1809.png
7.test2_1809.png

どちらもX-CacheレスポンスヘッダーがHit from cloudfrontとなっていることからキャッシュヒットしたことが分かります。

それでは18:10:02にmessage=test1を指定してアクセスします。

9.test1_1810.png

Timestamp: 2025/2/4 9:00 blockからTimestamp: 2025/2/4 9:10 blockと変わりました。

また、X-CacheレスポンスヘッダーがMiss from cloudfrontとなっていることからオリジンへ問い合わせしていることが分かります。意図したとおりです。

18:11にmessage=test2を指定してアクセスした場合もキャッシュヒットせず、表示される時刻が更新されていました。

10.test2_1811.png

Cache PolicyやCache-Controlディレクトティブで制御仕切れない場合はCloudFront Functionsも検討しよう

CloudFront Functionsを使ってキャッシュ時からの経過時間ではなく現在の時刻ベースのキャッシュ制御をしてみました。

Cache PolicyやCache-Controlディレクトティブで制御仕切れない場合はCloudFront Functionsも検討すると良いでしょう。

CloudFrontのキャッシュ制御のお作法は以下AWS Black Beltが非常に参考になります。

https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_AmazonCloudFront-CacheControl_0430_v1.pdf

この記事が誰かの助けになれば幸いです。

以上、クラウド事業本部 コンサルティング部の のんピ(@non____97)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.