CDNのオリジンにGCSを使う際に注意したいポイント

こんにちは、いわなちゃんさん(@xcir)です。

Webサイトには様々なリソースが存在し、それらをS3やGCSなどのオブジェクトストレージに格納することはよくあります。

しかし、多くのサイトではオブジェクトストレージを直接使うことはあまりなく(そもそもお勧めできませんが)通常はCDNを被せて利用します。

オブジェクトストレージを提供するプラットフォームはCDNも提供しています。
例えばAWSであればCloudFrontがありますしGCPであればCloudCDNがあり、それらを組み合わせで使う場合も多いでしょう。

ところが機能、コストなど様々な理由でAkamaiやFastlyなどの外部CDNを使うことがあります。
オブジェクトストレージなので単純にファイル置き場として考えればそこまで気にするポイントはないのかもしれません。

ところが、GCSについては組み込みキャッシュ(Built-in cache)が存在し、これがまたかなり癖があります。
今回はGCSと外部CDNを組み合わせて使う場合に気を付けておきたいポイントを解説します。

GCSの組み込みキャッシュの動き

GCSはCache-Controlを付与すると自身でキャッシュするようになります。

これだけを見ると特にCDNは必要ないのではと感じるかもしれませんが、パージができないためGCS上でファイルを上書きしても設定されたTTLが過ぎるまではキャッシュされます。
また、リンク先にもあるようにコストを考えると少し使いづらいものです。

Cache-Controlを設定しなければ問題ないのではと考える方もいらっしゃると思うのですが、特に指定がない場合はpublic, max-age=3600つまり1時間キャッシュされる設定で動きます。

つまり何らかの対策を打たないとGCSを単純なファイル置き場として使えません。

ということで実際にメタ情報を設定しどのような動作をするかを確認しました。
あくまでこの情報は2021/09/01時点の情報で、実際に使う場合は一度自身でも動作確認することをお勧めします。

公開バケット(allUsers=Storageオブジェクト閲覧者)

メタ設定
(Cache-Control)
設定なし(default)max-age=120private,
max-age=120
s-maxage=120
レスポンスされた
Cache-Control
public, max-age=3600max-age=120private, max-age=120s-maxage=120
GCS組み込みキャッシュ有効有効無効有効
ブラウザキャッシュ有効有効有効有効(heuristics)
メタ設定(Cache-Control)no-cacheno-storeprivate
レスポンスされた
Cache-Control
no-cacheno-storeprivate
GCS組み込みキャッシュ有効(TTL=0)無効無効
ブラウザキャッシュ有効(TTL=0)無効有効(heuristics)

わかりやすいようにレスポンスされたCache-Controlがブラウザがどのように解釈するかも一緒に書きました。
GCS組み込みキャッシュはCDNとしてみた場合、割と素直にCache-Controlを解釈しています。
なので逃げ道が少ないというのが正直なところです。
基本的にはno-storeやprivateをつければよいでしょう。

なお、no-cacheやheuristicsの動きはHTTPキャッシュ入門の入門という記事を書いてますのでこちらも合わせて読んでもらればと

非公開バケット

認証済みURL(?authuser=1)

メタ設定(Cache-Control)設定なし(default)max-age=120
レスポンスされた
Cache-Control
no-cache, no-store, max-age=0, must-revalidateno-cache, no-store, max-age=0, must-revalidate
GCS組み込みキャッシュ無効無効
ブラウザキャッシュ無効無効

v4署名

メタ設定(Cache-Control)設定なし(default)max-age=120
レスポンスされた
Cache-Control
private, max-age=0max-age=120
GCS組み込みキャッシュ無効無効
(Ageが帰ってこないのでキャッシュしてない認識)
ブラウザキャッシュ有効(heuristics)有効

動きで面白いなと感じたのは非公開バケットをオリジンとした場合の動きで、すべてキャッシュされませんでした。
なおこの動きはあくまでも非公開の時で、公開の場合はまた動きが違いました。

ちなみにオリジンにリクエストする際にお手軽にv4署名を行えるCDNは自分の知っているものだとAkamai, Fastlyがあります。(もちろんCloudFrontでもLambda@Edge使えば行けます)

どのように設定すればいいのか

GCSをオリジンとして使う場合のポイントは、いかにGCS側の組み込みキャッシュを抑制するかです。
v4署名が使えるCDNであれば、GCS側を非公開にした上でv4署名で行えばよいでしょう。
問題は公開バケットをオリジンとして使わざるを得ない場合です。
この際にCDNで以下のような機能があれば問題ないのですが

  1. キャッシュする際のTTLを別に設定できるか(オリジンのCache-Controlを無視)
  2. Cache-ControlをCDNで編集できるか

このどちらもできないCDNの場合(Cache-Controlのみを見てキャッシュするCDN)だと組み込みキャッシュを回避することはできないので組み合わせを考えたほうが良いでしょう。

CloudFront+GCS公開バケットでの例

例えばCloudFront(cf)の場合で考えてみましょう。

TTLは別に設定できる(minimum/default/max-TTL)のとCache-Controlの編集(Lambda@Edge / Functions )も可能です。
Cache-Controlを編集するならv4署名すればいいので一旦それは置いておいてTTLの設定のみで考えてみましょう。

TTLの設定のみで行う場合はminimum-TTLを0以上に設定することでs-maxage=0指定時でもcfでキャッシュできます。
なので、メタにs-maxage=0, max-age=120を設定したうえでminimum-TTLを60と設定すればcfでは60秒、ブラウザでも60~120秒キャッシュできるようになります。(private指定の場合でもキャッシュされますが、エラー時に使われるstale-if-errorとしてのキャッシュとなります)

CDNとブラウザでのTTLの割り振りについては今回の本筋ではないのと、割といろいろ考えること多いので記事キャッシュに関する本を書いたのでそれ見てほしいなぁとか思います。

※cfのprivate指定時でもキャッシュされますが、エラー時しか使われないのでs-maxageに修正しています

そのほかに注意が必要なこと(X-Http-Method-Override)

GCSはX-Http-Method-Overrideに対応しています。

これは指定したメソッドに上書きができるというものですが、
例えばX-Http-Method-Override: HEADと指定した場合、CDN側からするとGETリクエストなのにHEADの結果(0バイト)をキャッシュさせるといったことが可能になります。

なので、GCSをオリジンとする場合はこのヘッダを転送しないようにしましょう。(ヘッダを削る)
ちなみにcfだとデフォルトだと転送しませんが、他のヘッダを転送しようとして(といってもGCSをオリジンとするなら不要ですが)含めないようにしましょう。

あと組み込みキャッシュだとどういう動きになるのか見てみました。

  • 未キャッシュ状態でX-Http-Method-Override: HEADを指定した場合はキャッシュしない
  • キャッシュ状態でX-Http-Method-Override: HEADを指定した場合は既にキャッシュされているオブジェクトをレスポンス(実質無効)

この動きはCloudCDNでも同じで、個人的にはVaryつけてほしいなぁと思ったりします。

ネットワーク費用を下げよう(CDN Interconnect)

これはGCSに限らずなのですが、クラウドを使う上で結構大きい費用として対インターネットのネットワーク費用があります。
通常このネットワーク費用はあまり割引が無いのですが、CDN Interconnectという機能を使うことで半額ぐらいにすることが可能になります。

この機能を使うことができるのは限られたCDNで、条件は各CDNで微妙に異なるので、CDNベンダーに問い合わせたほうが良いのですが、
基本的にはCDNのエッジとGoogle側が直接接続している必要があります。

あくまで例ですが、自分はLocation TypeをRegionにしてCDNの多段エッジの親側のリージョンをGCSのリージョンの近い箇所を選ぶと使えました。

うまく効くとネットワーク費用の項目で

Network Egress via Carrier Peering Network - APAC Based Compute Engine

こんな感じでCarrier Peeringというキーワードが増えますのでわかると思います。
GCS限定というわけでもないので、何かしらGCPで提供している前段にCDNを被せるとキャッシュでトラフィックが減る以外にも安くなるケースがあることを知っておくとよいでしょう。

最後に

GCSの組み込みキャッシュは案外気づかず使われていて、パージしたのになぜか消えない、CDNのオフロード率が悪くなる(組み込みキャッシュ側でTTLを消費するため)などから気づくことがあります。
まずはデフォルトでキャッシュされるということを理解したうえでどのように使うか考えてみるとよいでしょう。