ちょっと話題の記事

CloudFrontで素早くコンテンツを更新させたい場合にTTLを短くしInvalidationを行わないキャッシュ戦略を考える

CloudFrontで頻繁に更新されるコンテンツではないため長くキャッシュさせておきたいが、更新があった場合はすぐに反映させたい、というケースではTTLを短くしておきましょう。オリジンからのデータ本体の転送は更新の際にしか実施されません。
2021.08.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。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よりもキャッシュ期間の設定を推奨することを紹介していました。

なお、CloudFrontではキャッシュ有効期限が切れる前でも、リクエストが少ないファイルについてはエッジからキャッシュを削除する場合があります。この点についても「CloudFrnotのキャッシュ有効期限を長くしてオリジン側で更新があった際にInvalidationを行う」キャッシュ戦略では留意しておきましょう。

エッジロケーションに頻繁にリクエストされないファイルがあれば、CloudFront は、頻繁にリクエストされるようになったファイル用にスペースを確保するために、そのファイルを削除する (そのファイルの有効期限が切れる前に削除する) 場合があります。

コンテンツがキャッシュに保持される期間 (有効期限) の管理 - Amazon CloudFront

TTLが切れてもRefreshHitすればキャッシュ本体は使い回される

Invalidationを行わず、かつオリジンで更新があった場合に素早くCloudFront側でも反映を行いたい場合、キャッシュ有効期限を短くする方法が考えられます。(他にもファイルのバージョニングを使う方法も検討できますが、要件にもよると思うのでここではいったん割愛します。)

このキャッシュ有効期限(TTL)を短くするという方法をとった場合、CloudFrontでキャッシュ有効期限が切れてしまえば再度オリジンからデータを取得する必要があるため非効率になりそう、と考えてしまうかと思います。(実際、私はこの考えがあり、キャッシュヒット率を上げるためにはオリジン側で更新がないファイルであればキャッシュ有効期限はなるべく長くし、Invalidationを使用するほうが良いのではないか、と考えていました。)

しかし、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でのキャッシュ期間に加えて、ブラウザでのキャッシュ期間も考慮する必要があるかと思いました。