GraphQL caching ②AWS Prototyping program を利用した開発

# はじめに
本記事は以下の三本立てシリーズの二本目の記事です。

[①導入の背景と目的](https://qiita.com/datahaikuninja/items/f22fde98e76602f12691)
②AWS Prototyping programを利用した開発
[③プロダクションリリースまでに解決した課題、導入による効果](https://qiita.com/datahaikuninja/items/d00316226910f3df2eb8)

本記事では、以下のトピックを扱います。
- GraphQLとCloudFrontの相性
- AWS Prototyping program
- 技術選定

# GraphQLとCloudFront の相性
実はGraphQLとCloudFrontの相性は良くはありません。「GraphQL」「caching」「CDN」等のワードを組み合わせてGoogle検索をすると、別のCDN製品に関するいくつかのBlogが見つかりますが、CloudFrontでキャッシングしたという事例はありませんでした。CloudFront以外のCDNについても、当時は実際に本番環境に導入して運用している事例は見かけませんでした。

なぜ相性が良くないのでしょうか。私自身はGraphQLを使用した開発経験はありませんが、インフラ視点では、GraphQLが持つ次の特徴がCloudFrontと相性が良くないと考えています。

- URLエンドポイントは基本的に一つである。[^1]
- 慣習的にPOSTメソッドを用いる。[^2]

## GraphQLをCloudFrontでキャッシュする上での課題
GraphQLが持つ上記の特徴が、GraphQLのレスポンスをCloudFrontでキャッシュさせる上での課題になります。

### 一つのURLエンドポイント
CloudFrontはBehaviorでキャッシュ制御を行います。Behaviorはリクエストのパスパターン毎に作成が可能なので、パスパターン毎にキャッシュオブジェクトのTTLやキャッシュキーを変更するといった柔軟なキャッシュ制御が可能です。

ところが、GraphQLのURLエンドポイントは基本的には一つであるため、柔軟なキャッシュ制御は困難です。あらゆるGraphQLリクエストを一つのBehaviorでコントロールすることになります。一つのBehaviorでコントロールするということは、TTLはキャッシュ可能なレスポンスの中で最も短い時間に合わせなければならない、ということです。キャッシュキーの設計も難しくなります。

また、キャッシュの削除（Invalidation）を実施すると、すべてのキャッシュが消失し、オリジンへリクエストが一気に流れ込むため、迂闊に削除できません。オリジン側のデータ更新を速やかに反映したい場合にCDNのキャッシュを削除することがあると思いますが、そのような状況では関連するAPIに絞ってキャッシュを削除したいはずです。しかし、URLエンドポイントが一つという特性上、キャッシュを部分的に削除することができません。

### POSTメソッド
CloudFrontはPOSTリクエストに対するレスポンス結果をキャッシュしません。

>Q.Amazon CloudFront は POST レスポンスをキャッシュしますか？
>Amazon CloudFront は POST、PUT、DELETE、および PATCH リクエストへのレスポンスをキャッシュしません。これらのリクエストはオリジンサーバーにプロキシされます。OPTIONSリクエストに対する応答のキャッシュを有効にすることができます。[^3]

つまり、シンプルにGraphQLサーバーの前にCloudFrontを置くだけでは、GraphQLのレスポンスをCloudFront にキャッシュさせることはできない、ということです。
CloudFrontを使用するメリットはキャッシングだけではなく、AWSグローバルネットワークによる通信の安定化もありますが、ユーザーの多くが国内にいるサイト・サービスの場合は、それだけのためにCloudFrontを使うケースは稀だと思います。[^4]

# AWS Prototyping program
CloudFrontだけ使っても GraphQLのレスポンスをキャッシュさせることはできません。ここからは、どういった経緯でCloudFrontにGraphQLのレスポンスをキャッシュさせることが可能な構成にたどり着いたのか、という話をします。

結論から書くと、AWS Prototyping programを利用して、GraphQLのレスポンスをCloudFrontでキャッシュするプロトタイプを開発し、LOWYAにフィットするように改良する形で、キャッシングの仕組みを開発しました。

## AWS Prototyping programとは
AWS Prototyping programとは、AWSのプロトタイピングエンジニアが、AWSのユーザーがAWSを使って実現したい仕組みのプロトタイプを開発してくれる無償のプログラムです。（決して丸投げで開発してもらえるわけではありません。）

プログラムの概要は以下のツイートの通りです。[^5]
（プログラムについて詳しく知りたい方は、西谷さんではなくAWSのSAさんに相談してください。）

https://twitter.com/Keisuke69/status/1221563011413893120?s=20&t=Yn-J_l8lDHEnhgVWwL4CFA

私たちは、弊社担当のソリューションアーキテクトの方からこのプログラムを紹介いただいて、AWSのプロトタイピングエンジニアの方と一緒にプロトタイプを開発していきました。[^6]
GraphQLのキャッシングをなんとかしてAWSで実現できないか、と考えていたものの、ユースケースに最適なアーキテクチャが思い浮かばず困っていたので、とても助かりました。

## 完成したプロトタイプ
完成したプロトタイプがこちらです。

https://github.com/aws-samples/amazon-cloudfront-cache-graphql

どのような仕組みか気になる方は上記リポジトリのREADMEをご参照いただければと思います。

要点のみ書くと、CloudFrontはPOSTリクエストのレスポンスをキャッシュしない、という致命的な課題をクリアするために、OriginRequestのLambda@Edgeで、POSTからGETにメソッド変換し、POSTリクエストのボディを分割してキャッシュキーとするヘッダーに格納して、CloudFrontへリクエストし直す、ということをやっています。

URLエンドポイントが一つという特性から生じる制約は許容しています。

LOWYAで実際に動いているコード、実装において考慮したポイントなど、詳細は三本目の記事で紹介します。

# 技術選定
ここまで、CloudFrontでGraphQL cachingを実現するためには、という話をしてきましたが、最初からCloudFrontありきで技術選定をしたわけではありません。

技術選定において、次のポイントを重視していました。
- バックエンドのインフラ構成の変更を必要としないこと[^7]
- アプリケーションの改修が極力少ないこと
- APIのセキュリティ対策も実現できること

LOWYAは2020年8月に、旗艦店ECシステムをパッケージ製品からコンテナベースの内製システムにフルリニューアルしています。[^8]リニューアルから2年が経過していますが運用は安定しており、GraphQL cachingのために大幅な構成変更を行って運用の安定性が犠牲になるリスクをとりたくありませんでした。

また、アプリケーションの改修が極力少ない方法を探していました。改修箇所が少なくないと、GraphQL cachingを導入するためにかかる工数が増えるだけでなく、改修による影響で運用が変わり、システムの安定性が損なわれる懸念もあります。

そして、DoS・DDoS攻撃をはじめとする悪意あるリクエストからAPIを保護するためのセキュリティ対策も同時に実現したいと考えていました。

これらのポイント（条件）を満たす選択が、AWS Prototyping programで開発したプロトタイプでした。

技術選定にあたって比較表を作成したので、表の一部分を記載します。

| 製品/比較項目 | Cloudflare workers| Fastly compute@edge | Amazon CloudFront + Lambda@Edge(AWS Prototype) | Stellate GraphQL Edge Caching(GraphCDN) |
|:-:|:-:|:-:|:-:|:-:|
| 実現方法  | エッジで動作するコードを開発する  | 左に同じ  | 左に同じ | キャッシュ動作を定義する構成ファイルを記述する  |
| 参考になるblog  | [Cloudflare Workers でGraphQLリクエストをキャッシュして30msで返すようにした話](https://wawoon.dev/posts/speed-up-graphql-response-by-edge-cache)  | [Compute@Edge は GraphQL Server の夢を見るか](https://hack.nikkei.com/blog/advent20211206/)  | なし  | なし  |
| 参考になる公式ドキュメント |[Introducing the Workers Cache API](https://blog.cloudflare.com/introducing-the-workers-cache-api-giving-you-control-over-how-your-content-is-cached/) | [Expose REST APIs as GraphQL](https://developer.fastly.com/solutions/tutorials/graphql/) | なし | [GraphQL Edge Caching](https://stellate.co/graphql-edge-caching)

Amazon CloudFront + Lambda@Edge (AWS Prototyping) を選択した決め手は以下です。
- 開発コストが低いこと
    - Cloudflare と Fastly は有償のプロフェッショナルサービスを利用してサポートを依頼する必要があったが、AWS Protoryping program は無償でプロトタイプを提供してもらえる
- 学習コストが低いこと
    - CloudFrontとLambda@Edgeはすでに利用しているため、知見がある
- バックエンドのインフラ構成の変更が不要、アプリケーションの改修範囲も狭いこと
    - フロントエンドとバックエンドのアプリケーションの間に、CloudFront と Lambda@Edge で構成されたAPIキャッシュ層が挟まるだけなので、導入と切り戻しが容易である。結果的にはアプリケーションの改修箇所も少なく済んだ
- AWS WAF, AWS ShieldAdvanced を活用することでセキュリティ強化が可能であること

# 本記事のまとめ
本記事では、GraphQL cachingの仕組みをどのようにして開発したか、三つのトピックと絡めて書きました。次の記事では、プロトタイプをプロダクションリリースするまでに生じた課題をどのようにして解決したかについて書きたいと思います。プロトタイプをLOWYAにフィットさせるためにどのような苦労があったのかがテーマです。

[^1]:https://graphql.org/learn/serving-over-http/#uris-routes
[^2]:GraphQL はGETメソッドを扱えます。https://graphql.org/learn/serving-over-http/#uris-routes
しかし、以下の stack overflowを読むと、慣習的にはPOSTメソッドを利用するようです。GETリクエストはボディを持てないため、クエリをURLパラメータとして渡す必要があり、文字サイズの制限が問題になるケースがあるという見解も述べられています。
https://stackoverflow.com/questions/59162265/why-are-graphql-queries-post-requests-even-when-we-are-trying-to-fetch-data-and
[^3]:https://aws.amazon.com/jp/cloudfront/faqs/
[^4]:グローバルに展開するサービスであれば、キャッシングできなくてもAPIアクセラレーターとして CloudFront を使用するメリットはあるでしょう。一方、LOWYAのように利用者の大半が国内にいるサービスの場合は CloudFront の通信コストを計算し、導入にかかる工数や構成変更の影響をしっかりと調査した上でメリットがあるかどうか判断した方がよいです。
[^5]:西谷さんはAWSを退職されています。本記事を読んで西谷さんへご連絡なさらないようにお願いします。
[^6]:感染症対策のため、プロトタイプの開発はリモートで実施しました。プロトタイプの開発は、ヒアリング→アーキテクティング→ビルディング→ハンドオーバー という流れで実施されました。つまり、私たちが実現したいことをAWSのプロトタイピングエンジニアの方へ伝えて、仕様やアーキテクチャに関してディスカッションし、プロトタイピングエンジニアの方がプロトタイプを開発して私たちに引き渡し、私たちの環境で検証を実施する、という流れです。
[^7]:LOWYAバックエンドのアーキテクチャは一本目の記事をご覧ください。
[^8]:LOWYA旗艦店システムの内製化についてご興味がある方はデブサミ2021の登壇資料をご覧ください。https://newrelic.com/jp/resources/presentations/vega-corporation-devsumi-2021-summer