CDNキャッシュ向けレスポンスヘッダーCache-Control:s-maxage を触ってみた

HTTPレスポンスヘッダー「Cache-Control:s-maxage」ディレクティブを使うと、CDNのキャッシュ時間をオリジンでコントロールできます。
2021.01.21

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

CDNやプロキシといった共有キャッシュ向けにキャッシュの保持期間を制御する Cache-Control: s-maxage=seconds というレスポンスヘッダーが存在します。

ブラウザとCDNでキャッシュの保持期間を分けたい時や、マルチCDNの構成においてオリジン側でCDNのキャッシュの保持期間を一元管理したい時などに重宝します。

本記事では、この共有キャッシュ向けレスポンスヘッダーについて、かんたんに紹介します。

ブラウザ向けの max-ageと共有キャッシュ向けの s-max-age

s-maxage によく似たディレクティブに max-age があります。どちらも Cache-Control と一緒に用いますが、用途は少し異なります。

max-age はCDNやプロキシといった共有キャッシュ、及び、クライアント向けのキャッシュ設定です。

s-maxage共有キャッシュに特化したキャッシュ設定であり、max-age / Expires よりも優先されます(上書きます)。その上で、CDN によって、CDN設定とs-maxage のどちらのキャッシュ時間を優先するか決まります。

s-maxage の綴りは s-max-age ではないので、ご注意ください。

CloudFront と Cache-Control:max-age, s-maxage の連携

CDN に Amazon CloudFront を用いているケースにおいて、max-ages-maxage の値によって CDN(エッジ)ではどのようにキャッシュされるのでしょうか。

CloudFrontのキャッシュポリシーにデフォルトの Managed-CachingOptimized を設定している場合で確認します。

このポリシーは次の通りです。

  • Minimum TTL : 1
  • Maximum TTL : 31536000
  • Default TTL : 86400

Minimum TTL=0 の場合、下記ルールとは異なります。詳細はドキュメントを参照してください

max-ageもs-maxageも未設定の場合

オリジンが max-ages-maxage も返さない場合、

  • CloudFront は Default TTL の期間だけキャッシュ保持(厳密には MinimumとDefaultの大きい方)
  • クライアントのキャッシュ期間はブラウザ依存

max-ageは設定されていてs-maxageは未設定の場合

オリジンが max-age を返し s-maxage は返さない場合、

  • CloudFront は Minimum TTL/Maximum TTL/max-age の中から、2番めに大きい値だけキャッシュ。例えば Minimum < max-age < Maximum の場合、 max-age の期間だけキャッシュ
  • クライアントは max-age の期間だけキャッシュ

max-ageとs-maxageの両方が設定されている場合

オリジンが max-ages-maxage の両方を返す場合、

  • CloudFront は max-age よりも s-maxage を優先し、Minimum TTL/Maximum TTL/s-maxage の中から、2番めに大きい値だけキャッシュ。例えば Minimum < s-maxage < Maximum の場合、 s-maxage の期間だけキャッシュ
  • クライアントは max-age の期間だけキャッシュ

S3 がオリジンの場合に s-maxage を確認

CloudFront のオリジンに S3 を設定し、s-maxage の挙動を確認します。

CloudFront のキャッシュポリシーを Managed-CachingOptimized に設定し、S3オブジェクトのメタデータで Cache-Control を設定します。

Cache-Control: s-maxage=300,max-age=180 の場合、 Minimum TTL(=1) < s-maxage(=300) < Maximum TTL(= 31536000) のため、エッジでは s-maxage で指定した秒数だけキャッシュされるはずです。

初回アクセス時

初回アクセス時には キャッシュミスします。

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 14
Connection: keep-alive
Date: Wed, 20 Jan 2021 15:29:10 GMT
Last-Modified: Wed, 20 Jan 2021 15:09:33 GMT
ETag: "910c8bc73110b0cd1bc5d2bcae782511"
Cache-Control: s-maxage=300,max-age=180
Accept-Ranges: bytes
Server: AmazonS3
X-Cache: Miss from cloudfront
...

キャッシュ期間内の2度目のアクセス

290秒後にアクセスします(max-age < 290 < s-maxage)。

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 14
Connection: keep-alive
Date: Wed, 20 Jan 2021 15:29:10 GMT
Last-Modified: Wed, 20 Jan 2021 15:09:33 GMT
ETag: "910c8bc73110b0cd1bc5d2bcae782511"
Cache-Control: s-maxage=300,max-age=180
Accept-Ranges: bytes
Server: AmazonS3
X-Cache: Hit from cloudfront
Via: 1.1 53767392640cf5282c1ce18d7cc7b0e1.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: HAM50-C1
X-Amz-Cf-Id: r4CPRg6eZDjv25BWIjwjYvbvkwCMNJRhQYB33AAoWLMy4MTKMzVO4g==
Age: 290

CloudFront では s-maxage の時間だけキャッシュされているので、キャッシュヒットします。

エッジキャッシュのstale後のアクセス

エッジキャッシュが stale になったあとにアクセスします。

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 14
Connection: keep-alive
Last-Modified: Wed, 20 Jan 2021 15:09:33 GMT
Accept-Ranges: bytes
Server: AmazonS3
Date: Wed, 20 Jan 2021 15:34:10 GMT
Cache-Control: s-maxage=300,max-age=180
ETag: "910c8bc73110b0cd1bc5d2bcae782511"
X-Cache: RefreshHit from cloudfront
...

X-Cache: RefreshHit from cloudfront から、エッジキャッシュが stale なので、オリジンにアクセスしていることがわかります。

オリジンS3バケットのサーバーアクセスロギングから対応するログを確認すると、この RefreshHit のリクエスト(conditional GET)に対して 304: Not Modified を返していました。

008600d... BUCKET-NAME [20/Jan/2021:15:34:09 +0000] 130.***.***.*** - 
  ERXXX REST.GET.OBJECT s-maxage.html 
  "GET /s-maxage.html HTTP/1.1" 304
  - - 14 9 - "-" "curl/7.64.1" - Ng...= - - - BUCKET-NAME.s3.amazonaws.com -

s-maxageとSurrogate-Control ヘッダーとの違いは?

Cache-Control: s-maxage ディレクティブと同様の機能を一部のCDNベンダーは Surrogate-Control ヘッダーで提供しています。

Surrogate-ControlCache-Control の エッジ版であり、W3C の 2001年版の Edge Architecture Specificationにも確認できます。

実際の仕様はベンダー・ミドルウェアによって細部が異なります。

例えばFastly の場合、エッジキャッシュの保持期間に関してCache-Control: s-maxage=3600Surrogate-Control: max-age=3600は同じであり、Surrogate-Controlヘッダーはオリジン-CDN間でのみ利用され、クライアント向けレスポンスからは除外される点がCache-Controlと異なります。

参考 : Configuring caching | Fastly Help Guides

Surrogate-Control のCDN向け 統一規格 CDN-Cache-Control レスポンスヘッダー のドラフトが公開中

CDN に特化した統一的なキャッシュヘッダーがほしいということで、CDN-Cache-Control という新しいレスポンスヘッダーのドラフトが 2020年11月に公開されました。

The CDN-Cache-Control HTTP Response Header Field

Cache-Control の仕様はすでに十分すぎるくらいに複雑で拡張が難しく、CDNだけでなくプロキシも含めた共有キャッシュが対象であること、Surrogate-Control はベンダーごとに独自仕様となっていて、互換性を維持したまま拡張するのが難しいことから、新しいヘッダーを用意することになったようです。

マルチCDN環境でCDNのキャッシュ設定を一元管理できる未来を期待したい一方で、ドラフトによると "Individual CDNs can choose to define their own control mechanisms that take precedence over this header field."とあるので、厳しい現実を突きつけられる可能性もあります。

このドラフトの著者は Akamai/Fastly/Cloudflare の CDN ベンダーから構成されています。 著者の一人 Mark Nottingham さんは Surrogate-Control を定義した W3C Note 04 August 2001 版 Edge Architecture Specification の著者の一人でもあります。

まとめ

CDNのキャッシュを制御するには、CDN本体の設定だけでなく、オリジンのレスポンスヘッダー(Cache-Control: s-maxage/Surrogate-Control)でも制御できます。

設定が分散していると挙動を把握するのが難しくなるため、まずは CDN 単体でキャッシュ設定することをおすすめします。

最大の疑問点、s-maxageの綴りがs-max-ageでない理由については、最後までわかりませんでした。

それでは。

参考