TL;DR
GAEのエッジキャッシュを利用する時は課金を有効にしましょう。
Google App Engine
みなさん、Goolge App Engineをお使いでしょうか。
Google App EngineはGCPが誇るPaaS実行環境ですが、素晴らしい点がいくつもあります。ざっとあげるだけでも、
- スタンダード環境なら無料利用枠が色々ある。
- F1インスタンスなら常時稼働でも無料。
- Google Storageは5GBまでなら無料。
- インターネット宛ての下り転送料が1GB/dayまで無料。
- 独自ドメイン & HTTPS証明書も無料で利用可能
- Go言語やPython、Node.jsなど色々な言語ランタイム利用可能
- A/Bテストやロールバックが簡単に実行可能
- 負荷に応じたスケーリング性能
といったように色々なメリットのあるサービスです。
そして、特徴のひとつに、
- レスポンスヘッダに、cache-control を指定することで、エッジキャッシュを利用でき、高速に配信可能。
というものがあります。それを使わない手はないということで、利用しようとしたところ、待てど暮らせどキャッシュがされない。その原因とは一体…というのが今回のお話です。
ことのはじまり
GAEでアプリケーションを作成していた時のこと。
GAEはリージョンによって転送料の金額が変わります。アメリカ、ヨーロッパのリージョンは安め($0.12/GB)でアジアのリージョンは高め($0.14/GB) です。当然安い方がいいのですが、地理的に離れていれば、その分レイテンシも高くなります。
じゃあ、どれぐらい差があるのか検証してみようということで、新しくプロジェクトを作成しGAEのアプリケーションをus-centralリージョン(アイオワ)で作成しました。
アプリケーションをデプロイし、検証は無事におわりました。東京リージョンとアイオワでは、およそ80msぐらいのレイテンシの違いがあるということがわかりました。
そこで、あることに気づきました。
東京リージョンのアプリケーションはCSSがキャッシュされているのに、アイオワリージョンのCSSはキャッシュがされていなかったのです。
エッジキャッシュにキャッシュされる条件
使用したapp.yamlは以下のようなものです。
runtime: go api_version: go1.9 threadsafe: true handlers: - url: /css/ static_dir: www/css - url: /.* script: _go_app
ネット上を徘徊すると、昔は言語ランタイムの不具合でキャッシュされないことがあったということや、appspot.comドメイン(デフォルトのドメイン)じゃないとキャッシュされないケースがある、などの情報は得られましたが、今回のケースには当てはまりませんでした。言語ランタイムはgo言語の最新ですし、appspot.comでアクセスしてもキャッシュされないからです。
そこで、キャッシュの条件が記載されていたstack overflowのページを見つけました。
それによると、Cloud CDNでキャッシュされる条件と同じであるとのことです。
[blogcard url=”https://cloud.google.com/cdn/docs/caching”]
上記ページから引用すると
以下の条件を満たしていること。
- Cloud CDN が有効になっているバックエンド サービスまたはバックエンド バケットから提供されている。
GET
リクエストに対するレスポンスである。- ステータス コードが
200
、203
、300
、301
、302
、307
、410
のいずれかである。 Content-Length
ヘッダーまたはTransfer-Encoding
ヘッダーがある。Cache-Control: public
ヘッダーがある。Cache-Control: s-maxage
、Cache-Control: max-age
、Expires
のいずれかのヘッダーがある。
かつ、以下の条件を満たしていないこと。
Set-Cookie
ヘッダーがある。- 本文が 10 MB を超えている。
Vary
ヘッダーにAccept
、Accept-Encoding
、Origin
以外の値が設定されている。Cache-Control: no-store
、no-cache
、private
のいずれかのディレクティブが設定されている。- 対応するリクエストに
Cache-Control: no-store
ディレクティブが設定されている。
ということになります。
レスポンスを見ると…
今回のケースで言えば、東京リージョンではキャッシュされているので、Google App EngineはCloud CDN が有効になっているバックエンド サービスということで良いでしょう。
GETのリクエストですし、ステータスコードも200、Cache-Controlもpublic, max-age=600です。
[memo title=”MEMO”]ちなみに、Google App Engineの場合、static_dir, static_filesで指定した静的コンテンツには、デフォルトでCache-Control: public, max-age=600の設定が付与されます。このmax-ageの指定値はdefault_expirationの項目で全体的に変更でき、static_dirやstatic_filesのオプションにexpirationを付与することで個別に変更できます。[/memo]
Content-Lengthも…ん?Content-Lengthがない?
キャッシュされているアプリのレスポンスにはContent-Lengthが存在しています。つまり、Content-Lengthがないのがキャッシュされない原因らしいということまではわかりました。ですが、なぜないのかがわかりません。
東京リージョンのアプリでやっていて、アイオワリージョンのアプリでやってないことを思い浮かべると…
- 東京リージョンのアプリは大元のBitBucketのリポジトリをCloud Source Repositoryにミラーリングし、Cloud Buildをキックしてデプロイしていた
- アイオワリージョンのアプリは大元のBitBucketのリポジトリをローカルにcloneして手動デプロイしていた
という違いがあったので、こうなったら合わせて確認してみるっきゃないと思い、Cloud Source Repositoryの画面からミラーリングの設定を行おうとしました。
その時、
このプロジェクトは新規で作成したプロジェクトだったので、課金が有効になっていないことに気づきました。
課金を有効にして再度試してみると…
content-lengthのヘッダが表示されると共に、ageヘッダも表示されました。
というわけで、キャッシュされるようになりました。めでたし、めでたし。
終わりに
結果的に、課金を有効にすることでエッジキャッシュも有効になりましたが、その理屈がわかるようなドキュメントや情報を見つけることはできませんでした。
以上、同じような悩みを持った方の参考になれば幸いです。
P.S. Google App Engineのキャッシュのレイテンシは素晴らしいですね(参考)