CloudFrontで素早くコンテンツを更新させたい場合にTTLを短くしInvalidationを行わないキャッシュ戦略を考える
はじめに
清水です。AWSのCDNサービスであるAmazno CloudFrontを利用する場合に、頻繁に更新されるファイルではないため、なるべく長くCloudFrontにキャッシュさせオリジンへのアクセスやデータ転送の負荷などは極力少なくしたい。けれどオリジン側でファイルの更新があった場合は、なるべく早くCloudFront側でもキャッシュの反映を行いたい、といったことがあります。
このようなケースで1つ考えられる方法は、CloudFront側のキャッシュ有効期限(TTL)を長くしておき、オリジン側でファイルの更新があった場合にはキャッシュの無効化(Invalidation)を行うことです。しかしこのInvalidationは本来、イレギュラーなケースで使用するべきもので常用するものではありません。
もう1つ考えられる方法は、CloudFrontでのキャッシュ有効期限を短くすることです。オリジン側でファイルの更新があった場合は最大でもこのキャッシュ有効期限の間にCloudFront側に反映されるため、キャッシュ有効期限を短くしておけばそのぶん早くCloudFront側に反映されます。この方法の場合、キャッシュ有効期限が切れるごとにオリジンにアクセス、ファイルの転送を行いオリジンの負荷が増えることが懸念されるかもしれません。しかしCloudFrontでは、キャッシュの有効期限が切れたファイルがエッジに残っている場合にはオリジンにファイル鮮度の確認のみ行い、実際のファイルの転送は行いません。オリジンへの負荷を最小限にとどめることができます。このことから、CloudFrontのキャッシュを有効活用しつつファイルの更新も素早く行いたい、といった場合でも、キャッシュの有効期限を短くとる、というキャッシュ戦略が選択できると考えます。
本エントリではこのキャッシュ戦略についての詳細や挙動などをまとめてみました。
Invalidationはもしものときに使うべき
CloudFrontのキャッシュ無効化(Invalidation)機能、便利ですよね。以前はInvalidation完了に15分ほど時間がかかっていましたが、2017年にこの時間が大幅に短縮されたことから、私はわりとカジュアルに使用していました。
上記エントリにあるように、エッジロケーションの90%には5秒で、全体では1分でキャッシュの削除が行われます。
更新頻度が少ないファイルで、しかしオリジン側で更新があった場合はすぐにCloudFront側も更新させたい、といった場合には、「CloudFrontのキャッシュ有効期限を長くしてオリジン側で更新があった際にInvalidationを行う」、というキャッシュ戦略も考えられると思います。(私もこれでいけるのでは、と考えていました。)
ですが、CloudFrontのよくある質問を確認するとInvalidation(無効化機能)は「予測できない状況でのみ使用できます」という文言があります。英語のページでは「unexpected circumstances」のときに使用するべき、という記載です。「unexpected circumstances」を日本語訳すると「予期しない状況」、「もしものとき」、「想定外のとき」、などでしょうか。
無効機能は、予想できない状況でのみ使用できます。前もって頻繁にファイルをキャッシュから削除する必要がある場合は、ファイルのバージョニングシステムの実行または失効期間の短縮 (あるいは両方) を行います。
よくある質問 - Amazon CloudFront | AWS
You should use invalidation only in unexpected circumstances; if you know beforehand that your files will need to be removed from cache frequently, it is recommended that you either implement a versioning system for your files and/or set a short expiration period.
FAQs | CDN, Zone Apex, Edge Cache | Amazon CloudFront
Invalidationは実行することで別途料金が発生することもあり、また同時に実行できるパスの数などにも制限があります。本来、Invalidationは常用する機能ではなく、あくまでイレギュラーケースでキャッシュを削除したい場合にのみ実行するべきもの、というふうに捉えています。またよくある質問に記載があるとおり、ファイルのバージョニングやTTLを短くすることでキャッシュを制御するのが本来の方法と考えます。
AWS Black Belt Online Seminar「Amazon CloudFront deep dive」でも、Invalidationよりもキャッシュ期間の設定を推奨することを紹介していました。
- 【AWS Black Belt Online Seminar】Amazon CloudFront deep dive - YouTube
- 20201028 AWS Black Belt Online Seminar Amazon CloudFront deep dive
なお、CloudFrontではキャッシュ有効期限が切れる前でも、リクエストが少ないファイルについてはエッジからキャッシュを削除する場合があります。この点についても「CloudFrnotのキャッシュ有効期限を長くしてオリジン側で更新があった際にInvalidationを行う」キャッシュ戦略では留意しておきましょう。
エッジロケーションに頻繁にリクエストされないファイルがあれば、CloudFront は、頻繁にリクエストされるようになったファイル用にスペースを確保するために、そのファイルを削除する (そのファイルの有効期限が切れる前に削除する) 場合があります。
コンテンツがキャッシュに保持される期間 (有効期限) の管理 - Amazon CloudFront
TTLが切れてもRefreshHitすればキャッシュ本体は使い回される
Invalidationを行わず、かつオリジンで更新があった場合に素早くCloudFront側でも反映を行いたい場合、キャッシュ有効期限を短くする方法が考えられます。(他にもファイルのバージョニングを使う方法も検討できますが、要件にもよると思うのでここではいったん割愛します。)
このキャッシュ有効期限(TTL)を短くするという方法をとった場合、CloudFrontでキャッシュ有効期限が切れてしまえば再度オリジンからデータを取得する必要があるため非効率になりそう、と考えてしまうかと思います。(実際、私はこの考えがあり、キャッシュヒット率を上げるためにはオリジン側で更新がないファイルであればキャッシュ有効期限はなるべく長くし、Invalidationを使用するほうが良いのではないか、と考えていました。)
しかし、CloudFrontの挙動をしっかりと確認するとこのようなケースでの対策も取られていることがわかります。実際にデベロッパーズガイドでその内容が記載されている箇所を確認してみましょう。
- 「条件付きの GET 」 Amazon S3 オリジンに対するリクエストと応答の動作 - Amazon CloudFront
- 「条件付きリクエスト 」 カスタムオリジンの場合のリクエストとレスポンスの動作 - Amazon CloudFront
まとめてみると、CloudFrontは以下の挙動をすることがわかります。
- エッジキャッシュで有効期限が切れたオブジェクト(ファイル)に対するリクエストを受け取った場合
- CloudFrontは以前のオリジンへのリクエスト時のEtagとLastModifiedの値を所持している
- キャッシュの有効期限が切れた際のオリジンへのリクエストに、この値からなる情報をリクエストヘッダに含める
- オリジンではこのリクエストヘッダの情報をもとにして、ファイルに更新があったかどうかを判断する
- ファイルが更新されていれば、ファイル全体をCloudFrontに返す
- ファイルが更新されていない場合は、HTTP 304 Not Modifiedのステータスコードのみを返す
304 Not Modifiedの場合は、ファイル本体はCloudFrontに転送されません。CloudFrontはオリジンにキャッシュがまだ新鮮なものであるかどうかの確認は行いますが、まだフレッシュな状態であれば新たにオリジンからデータをフェッチすることなく、すでに保持しているキャッシュをクライアントに返すこととなります。
CloudFrontのアクセスログのresult-type
や、CloudFrontからのレスポンスヘッダのx-cache
の値がRefreshHit
となっているのはこのケースになります。
この挙動であれば、キャッシュの有効期限が切れた際、いちどオリジンにリクエストは飛びますが、更新がなければファイル本体の転送は行われません。そしてオリジン側でファイルの更新があった場合は、オリジンにアクセスした際にそれがわかり、CloudFront側にも反映されます。キャッシュの有効期限を短くしておけばその分、ファイル(コンテンツ)更新の際のCloudFrontへの反映時間も短くなるわけです。
オリジン側の負荷を極限まで減らしたい、というならリクエスト自体を減らすことも検討する必要があるかと思いますが、特にシンプルな静的コンテンツ配信といった場合ではリクエスト自体はそこまでオリジンへの負荷にならないのではないでしょうか。また特にS3をオリジンに使う場合、アクセス数が多い場合でもある程度までならS3で捌けるかと思います。スパイクアクセスがきても、CloudFront側で少しでもキャッシュ有効期限があれば、オリジンとなるS3へのアクセス量は激増しないかと考えます。
RefreshHitな挙動と素早いコンテンツの更新を実際に確認してみる
それではこのCloudFrnotで有効期限がいちどきれたキャッシュを再度使用するRefreshHitの挙動、また実際にコンテンツを更新した際の挙動を、S3をオリジンとするCloudFrontで実際に確認してみます。
S3オリジンは静的ウェブサイトホスティング設定を行わず、CloudFrontオリジンアクセスアイデンティティを使用する設定としました。キャッシュ設定はmin/max/defualtのTTLをいずれも10秒としています。またS3側でCache-control
ヘッダの付与などは行っておりません。シンプルにCloudFront側で10秒間キャッシュする設定です。
RefreshHitの挙動の確認
curlコマンドでコンテンツを取得、キャッシュ有効期限の10秒よりも少し短い8秒間待機してこれを繰り返してみます。以下のような挙動になる想定です。
- 1回目(0秒経過): キャッシュMiss(10秒間キャッシュ)
- 2回目(8秒経過): キャッシュHit
- 3回目(16秒経過): キャッシュRefreshHit(10秒間キャッシュするので26秒までキャッシュ)
- 4回目(24秒経過): キャッシュHit
- 5回目(32秒経過): キャッシュRefreshHit(10秒間キャッシュするので42秒までキャッシュ)
- 6回目(40秒経過): キャッシュHit
- 7回目(48秒経過): キャッシュRefreshHit(10秒間キャッシュするので58秒までキャッシュ)
実際に実行してみた結果が下記となります。実行開始が「21時38分01秒」と上記の表記から1秒ズレてしまいましたが、意図通りの挙動となっていることがわかります。またオリジンであるS3のファイルは更新していないので、etagやlast-modifiedの値は変わっていません。(なお、取得対象となるhtmlファイルは正しいタグ構成になっていません。</head>
を入れ忘れているのにあとになって気が付きましたが、そのまま記載しています。)
% while :; date; echo ""; do curl -i https://www.example.com/short_ttl_test.html; echo "----"; sleep 8; done 2021年 8月30日 月曜日 21時38分01秒 JST HTTP/2 200 content-type: text/html content-length: 56 date: Mon, 30 Aug 2021 12:38:02 GMT last-modified: Mon, 30 Aug 2021 12:30:58 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 x-cache: Miss from cloudfront via: 1.1 6b386e52785c656425dda94f551c1d13.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: uvlKg3YDeL-dgTMwtwJ55w8ICSsesIKmRpFRgfD74Z_5HB2IpVd45A== <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時38分09秒 JST HTTP/2 200 content-type: text/html content-length: 56 date: Mon, 30 Aug 2021 12:38:02 GMT last-modified: Mon, 30 Aug 2021 12:30:58 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 x-cache: Hit from cloudfront via: 1.1 75f71de88dd651df60c175d5ab3c7586.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: NxRhxiHZh76Wfg2_8moRSNvpoT6O73_Afm3h4aF265_o7JouMYXyOQ== age: 8 <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時38分17秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:38:19 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: RefreshHit from cloudfront via: 1.1 c3faefbce04416977cbd7b9ab845d111.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: Ik-KYLITXQv4RoppdI1M9SPiRaTNvwz05KB5Q37pbz669WFja5a7Ag== <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時38分26秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:38:19 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: Hit from cloudfront via: 1.1 025de06f7deee324c277661a5d0ef5fb.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: khRZ54_dc2W_eaMHswy-Ex5Dg9hI1JHeaSzVslqaDLP_RS5Qo6PCOw== age: 8 <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時38分34秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:38:35 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: RefreshHit from cloudfront via: 1.1 01d4e8d94c61f8f56aebaa1af365cc6e.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: JtgvnRysOXq53C1-3SGp_1y4jVQ8x696Xj5M-x0CBuSQPOjjOx2lWg== <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時38分42秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:38:35 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: Hit from cloudfront via: 1.1 2005babf9e16815c80be6808c6f595b1.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: mZ34O0A-hEzFExiOFIWW5kcDNsS7ZdS9pGiBijzQGzyjBQ-xnd1l2w== age: 8 <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時38分50秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:38:51 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: RefreshHit from cloudfront via: 1.1 f92013124d5bf39059d54d83f591b87b.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: PXmjyo-yEReXY29cMpmR8-_p3_0d3MNoN_yMk9j-QlY8-BiuJkM3HQ== <html><head><title>test</title><body>test</body></html> ----
アクセスログについても確認してみます。 まずはCloudFrontのログです。上記の実行結果と同様、初回のみMissですが、以降はHitとRefreshHitを繰り返す挙動となっています。
2021-08-30 12:38:01 NRT12-C2 414 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Miss uvlKg3YDeL-dgTMwtwJ55w8ICSsesIKmRpFRgfD74Z_5HB2IpVd45A== www.example.com https 58 0.045 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Miss HTTP/2.0 - - 55509 0.045 Miss text/html 56 - - 2021-08-30 12:38:09 NRT12-C2 418 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Hit NxRhxiHZh76Wfg2_8moRSNvpoT6O73_Afm3h4aF265_o7JouMYXyOQ== www.example.com https 58 0.003 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Hit HTTP/2.0 - - 55511 0.003 Hit text/html 56 - - 2021-08-30 12:38:18 NRT12-C2 418 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - RefreshHit Ik-KYLITXQv4RoppdI1M9SPiRaTNvwz05KB5Q37pbz669WFja5a7Ag== www.example.com https 58 0.108 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 RefreshHit HTTP/2.0 - - 55512 0.108 RefreshHit text/html 56 - - 2021-08-30 12:38:26 NRT12-C2 419 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Hit khRZ54_dc2W_eaMHswy-Ex5Dg9hI1JHeaSzVslqaDLP_RS5Qo6PCOw== www.example.com https 58 0.002 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Hit HTTP/2.0 - - 55513 0.002 Hit text/html 56 - - 2021-08-30 12:38:34 NRT12-C2 419 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - RefreshHit JtgvnRysOXq53C1-3SGp_1y4jVQ8x696Xj5M-x0CBuSQPOjjOx2lWg== www.example.com https 58 0.059 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 RefreshHit HTTP/2.0 - - 55514 0.059 RefreshHit text/html 56 - - 2021-08-30 12:38:42 NRT12-C2 420 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Hit mZ34O0A-hEzFExiOFIWW5kcDNsS7ZdS9pGiBijzQGzyjBQ-xnd1l2w== www.example.com https 58 0.006 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Hit HTTP/2.0 - - 55515 0.006 Hit text/html 56 - - 2021-08-30 12:38:50 NRT12-C2 418 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - RefreshHit PXmjyo-yEReXY29cMpmR8-_p3_0d3MNoN_yMk9j-QlY8-BiuJkM3HQ== www.example.com https 58 0.039 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 RefreshHit HTTP/2.0 - - 55517 0.039 RefreshHit text/html 56 - - 2021-08-30 12:38:58 NRT12-C2 419 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Hit XIT_ZMCsjBSnVtH5nhHkH4a0lbFQoRhLAXxtNo7cJxToTrerS9lhYA== www.example.com https 58 0.003 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Hit HTTP/2.0 - - 55518 0.003 Hit text/html 56 - -
続いてCloudFrontのオリジンであるS3のログです。初回のMissのタイミングではHTTPステータスコード200でコンテンツを返しています。続いてRefreshHitのタイミングでオリジンであるS3にアクセスがありますが、いずれもHTTPステータスコードは304で、コンテンツ自体は返していない状況となります。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:38:01 +0000] 70.132.19.67 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy MA22ZPBC26813WMD REST.GET.OBJECT short_ttl_test.html "GET /short_ttl_test.html HTTP/1.1" 200 - 56 56 34 34 "-" "Amazon CloudFront" - 8KkVcrAck6csOkDK8+WTtCwREHI0qot+iPgc4JB3tjCFem2MZ7lDupBZ3sHAtcztrMRVEjq3f7Y= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:38:18 +0000] 70.132.19.134 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy 5WNS7G14HGABWXPY REST.GET.OBJECT short_ttl_test.html "GET /short_ttl_test.html HTTP/1.1" 304 - - 56 44 - "-" "Amazon CloudFront" - Osjoc/X97XJ18ammqca0pB4nQeTOXTY35SaSqJNxU77yqeR77IP1Kk5qMSbheRGkZRTVtiFiN4A= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:38:34 +0000] 70.132.19.87 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy 09SQQMEWKCCAK3AH REST.GET.OBJECT short_ttl_test.html "GET /short_ttl_test.html HTTP/1.1" 304 - - 56 45 - "-" "Amazon CloudFront" - C2iW4iRngOnkAcFOBT2A4EasroJI6kJCCRj0V9CvoUPdrVa7uky5emgvu6/umxCDqexa3c0virs= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:38:50 +0000] 70.132.19.139 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy 9N7D7TCTJZH5ZBBA REST.GET.OBJECT short_ttl_test.html "GET /short_ttl_test.html HTTP/1.1" 304 - - 56 29 - "-" "Amazon CloudFront" - 1+1QHa04+re44ql/rnr18hLtw0FOQoJhZU1ZAHT0ZQBO2be5TMRe0swesvtGgCBR2uEsv/fcBHM= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 -
コンテンツを更新する際の挙動の確認
続いて、オリジン側のコンテンツが反映された際の挙動も確認してみます。こちらもcurlコマンドでのコンテンツ取得を、キャッシュ有効期限の10秒よりも少し短い8秒間待機して繰り返します。途中、21時43分ジャストのタイミングでオリジンであるS3にファイルを上書きアップロードして更新しました。
直後のアクセスとなる21時43分02秒のタイミングでキャッシュMissが起こり、取得したコンテンツが反映されている(HTML内の文字列がUPDATE
になっている)ことがわかります。またこのタイミングでetagとlast-modifiedの値が変わっていることも確認できます。
% while :; date; echo ""; do curl -i https://www.example.com/short_ttl_test.html; echo "----"; sleep 8; done 2021年 8月30日 月曜日 21時42分30秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:42:31 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: RefreshHit from cloudfront via: 1.1 3230a3d42078a094780d1894002fcfd5.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: DuKwMXE0x35P6aArTWtYhKX6Cy58RzMhpAgdg7EZu6Zh7kv358CvRw== <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時42分38秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:42:31 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: Hit from cloudfront via: 1.1 01d4e8d94c61f8f56aebaa1af365cc6e.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: C5ynUl3hgDvn09yDKJxmkPOa6KidnhchFhcpdNVoYNHD1MTyhir2mw== age: 8 <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時42分46秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:42:47 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: RefreshHit from cloudfront via: 1.1 390641c56ef5ff8b95f0703aa85527fb.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: MgNWToXv63Q4cfUCmIMD_nBAX2LyWDQjTgcfJFNKESchd_FpjKGulA== <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時42分54秒 JST HTTP/2 200 content-type: text/html content-length: 56 last-modified: Mon, 30 Aug 2021 12:30:58 GMT x-amz-version-id: 7vDqzEfDlSYouiJQPlNHB9YJJqqLFor7 accept-ranges: bytes server: AmazonS3 date: Mon, 30 Aug 2021 12:42:47 GMT etag: "c4f1141edc1d7912abd1ef17e66066b2" x-cache: Hit from cloudfront via: 1.1 2d905d2c9a6d0b833a673c4fbaea5b54.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: ajbgoxf0l3D1kbVdYdGLYHgtP3pMq-8Mu_FLacQ7Z5Grbpkw_ualfA== age: 8 <html><head><title>test</title><body>test</body></html> ---- 2021年 8月30日 月曜日 21時43分02秒 JST HTTP/2 200 content-type: text/html content-length: 60 date: Mon, 30 Aug 2021 12:43:03 GMT last-modified: Mon, 30 Aug 2021 12:43:01 GMT etag: "32fe7583b3b5ebc3bc8cd9e9d2ff3b25" x-amz-version-id: 8d08pkGAifdf0OuEvLQWKNzXQqtW8hlW accept-ranges: bytes server: AmazonS3 x-cache: Miss from cloudfront via: 1.1 d3d9dad2af73f55ca535e5ee799f7ad8.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: dv2prQxZ2NGQdjaiPpEIAjG-0-uUQKU6_wgGB2GMLW9U8Q5vFQK7qQ== <html><head><title>UPDATE</title><body>UPDATE</body></html> ---- 2021年 8月30日 月曜日 21時43分10秒 JST HTTP/2 200 content-type: text/html content-length: 60 date: Mon, 30 Aug 2021 12:43:03 GMT last-modified: Mon, 30 Aug 2021 12:43:01 GMT etag: "32fe7583b3b5ebc3bc8cd9e9d2ff3b25" x-amz-version-id: 8d08pkGAifdf0OuEvLQWKNzXQqtW8hlW accept-ranges: bytes server: AmazonS3 x-cache: Hit from cloudfront via: 1.1 fd95d915cb5f672e4b8b3613a0dde9ea.cloudfront.net (CloudFront) x-amz-cf-pop: NRT12-C2 x-amz-cf-id: UtqXmjYmsgi0r_vKZnB_dkk3uNqf_a6zCflZClAR2eTd41NbRehpcA== age: 8 <html><head><title>UPDATE</title><body>UPDATE</body></html> ----
CloudFront、S3のアクセスログもそれぞれ掲示しておきます。S3側で該当のタイミングでは、きちんと304ではなく200でコンテンツ本体がCloudFrontに返されていることが確認できます。
2021-08-30 12:42:30 NRT12-C2 419 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - RefreshHit DuKwMXE0x35P6aArTWtYhKX6Cy58RzMhpAgdg7EZu6Zh7kv358CvRw== www.example.com https 58 0.048 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 RefreshHit HTTP/2.0 - - 55646 0.045 RefreshHit text/html 56 - - 2021-08-30 12:42:38 NRT12-C2 418 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Hit C5ynUl3hgDvn09yDKJxmkPOa6KidnhchFhcpdNVoYNHD1MTyhir2mw== www.example.com https 58 0.001 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Hit HTTP/2.0 - - 55651 0.000 Hit text/html 56 - - 2021-08-30 12:42:46 NRT12-C2 419 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - RefreshHit MgNWToXv63Q4cfUCmIMD_nBAX2LyWDQjTgcfJFNKESchd_FpjKGulA== www.example.com https 58 0.049 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 RefreshHit HTTP/2.0 - - 55655 0.048 RefreshHit text/html 56 - - 2021-08-30 12:42:54 NRT12-C2 418 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Hit ajbgoxf0l3D1kbVdYdGLYHgtP3pMq-8Mu_FLacQ7Z5Grbpkw_ualfA== www.example.com https 58 0.001 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Hit HTTP/2.0 - - 55657 0.001 Hit text/html 56 - - 2021-08-30 12:43:02 NRT12-C2 419 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Miss dv2prQxZ2NGQdjaiPpEIAjG-0-uUQKU6_wgGB2GMLW9U8Q5vFQK7qQ== www.example.com https 58 0.063 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Miss HTTP/2.0 - - 55659 0.063 Miss text/html 60 - - 2021-08-30 12:43:10 NRT12-C2 422 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - Hit UtqXmjYmsgi0r_vKZnB_dkk3uNqf_a6zCflZClAR2eTd41NbRehpcA== www.example.com https 58 0.002 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 Hit HTTP/2.0 - - 55661 0.002 Hit text/html 60 - - 2021-08-30 12:43:19 NRT12-C2 421 XXX.XXX.XXX.XXX GET d123456789012.cloudfront.net /short_ttl_test.html 200 - curl/7.64.1 - - RefreshHit jravVdCZ7omDyrp86As5xryWGPBgHDkaNBLbB_K8DtT4J4g5Tbea3g== www.example.com https 58 0.049 - TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 RefreshHit HTTP/2.0 - - 55662 0.049 RefreshHit text/html 60 - -
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:42:30 +0000] 70.132.19.88 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy K2QR7SSHCCD61QGD REST.GET.OBJECT short_ttl_test.html "GET /short_ttl_test.html HTTP/1.1" 304 - - 56 38 - "-" "Amazon CloudFront" - ExTffBiqa/KC5uYdW9kVtijgXjvS1Cg70nSOAnX0btdadCv0cosxiFKgnWsFcIURenI10NJGCG4= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:42:46 +0000] 70.132.19.86 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy FVEZB3JK588APPCN REST.GET.OBJECT short_ttl_test.html "GET /short_ttl_test.html HTTP/1.1" 304 - - 56 31 - "-" "Amazon CloudFront" - Vame1hCwW4xQNhkBrvYB8mzNLqXee69h4rhI3xdzOYnsPvZpiJfQnS/dIYfbr3PFz5vljV95C8o= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:43:00 +0000] XXX.XXX.XXX.XXX - X65MG7AMH41H4B31 REST.OPTIONS.PREFLIGHT short_ttl_test.html "OPTIONS /short_ttl_test.html HTTP/1.1" 200 - - - 4 - "https://s3.console.aws.amazon.com/s3/upload/my-s3-bucket?region=ap-northeast-1" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" - vpXlrkAxIpnreESXA4AiORJlVjraRoJjFEBoUP8guizE7cgztXxCbPKW3mXWKw3dGvCXZ0lXORQ= - ECDHE-RSA-AES128-GCM-SHA256 - my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:43:00 +0000] XXX.XXX.XXX.XXX arn:aws:sts::123456789012:assumed-role/user.name/user.name X65JWNTNKHQV77W7 REST.PUT.OBJECT short_ttl_test.html "PUT /short_ttl_test.html HTTP/1.1" 200 - - 60 46 24 "https://s3.console.aws.amazon.com/s3/upload/my-s3-bucket?region=ap-northeast-1" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36" - bOJ3RC7mRPozh9C2Uwf2+C78Zes9hkJxF/USnfSTWaOIVzBG2dmPTk7MwmNG6Npf2tgF0hQO96Y= SigV4 ECDHE-RSA-AES128-GCM-SHA256 QueryString my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx my-s3-bucket [30/Aug/2021:12:43:02 +0000] 70.132.19.86 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy TRG6WJE0XM9V7E9B REST.GET.OBJECT short_ttl_test.html "GET /short_ttl_test.html HTTP/1.1" 200 - 60 60 46 46 "-" "Amazon CloudFront" - 8FO1h/Lgfe4ghSP5xLWbeDBbfP40DLRznIsYpINz1M+vLciSjXUD8KO1xt+73El3BQ/tGox0g80= SigV4 ECDHE-RSA-AES128-GCM-SHA256 AuthHeader my-s3-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.2 -
まとめ
CloudFrontで頻繁に更新がないコンテンツでも、オリジン側で更新があった場合に素早くキャッシュ側も更新させたい場合は、むやみにキャッシュの無効化を行うのではなくキャッシュ有効期限を短くしてRefreshHitさせる戦略を採りましょう、という話でした。実は当初、私がこのRefreshHit際の挙動についてきちんと把握できておらず、コンテンツを再度オリジンから取得するのかな?ぐらいの漠然としたイメージでいました。キャッシュ有効期限が切れれば都度、オリジンからデータ自体を取得するイメージです。これだとデータ転送のためにオリジンの負荷が高くなるなどで非効率ですよね。しかしそうではなく、オリジン側のコンテンツに更新がある場合のみコンテンツ本体の再取得が行われるという挙動でした。またInvalidationについても、わりとカジュアルに使用できるという勘違いもしており、今回のケースではキャッシュ有効期限を長くしておいてInvalidationを行えばいいのではと考えていました。今回RefreshHitの挙動などを確認して、改めてキャッシュ戦略の奥深さを感じたしだいです。また実際には今回確認したCloudFrontでのキャッシュ期間に加えて、ブラウザでのキャッシュ期間も考慮する必要があるかと思いました。