GraphQL Caching について
はじめに
GraphQL (Query Language)は、Facebookにより開発され、2015年にリリースされたAPI用のデータクエリ言語であり、REST APIの次のパラダイムとして益々注目されています。GraphQLの主な特徴として、クライアントが送出するAPIコールであるHTTP POST Bodyにサービスに必要なリソースを明示的に記述することで、過剰なAPIコール数の削減や、レスポンス内の余分な情報を無くすことでペイロードサイズの削減ができる点などがあげられます。また、基本的に単一のエンドポイントのみを利用すること、APIのバージョンという概念が存在しないことなどもGraphQLの特徴となります。
REST APIと比較して利点が多いように思われるGraphQLですが、一つの課題としてCDNにおけるキャッシュが難しいという点があげられます。REST APIにおける多くのケースでは、APIリソース毎にURLが一意であれば、URLをベースにキャッシュキーを生成する事ができます。(「キャッシュキー」についてはこちらをご参照ください。)一方、GraphQLでは単一のエンドポイントのみを利用し、HTTP POST Bodyの内容によってレスポンス結果が異なるため、URLベースでキャッシュキーの生成ができず、これまでのCDNの仕組みではキャッシュを実現する事ができません。結果として、CDNによるオリジンサーバのオフロードを実現ができなかったり、クライアントのパフォーマンスやユーザ体験に影響を及ぼす可能性があります。
このような課題を解決するために、昨年リリースしたAkamai API Gatewayという製品における拡張機能として、「GraphQL Caching」が実装されました。本機能ではGraphQLで記述されたリクエストに対して、HTTP POST BodyのParseおよびHash値を取り一意のキャッシュキーを生成することで、GraphQLで呼び出されたレスポンス結果をCDN上のエッジサーバーでキャッシュすることが可能になります。
本ブログではGraphQL Cachingについて具体例を交えて説明したいと思います。
尚、GraphQLそのものの技術仕様についてはこちらを参照ください。
動作例1:商品情報の呼び出し
まず、REST APIとGraphQLにおけるAPIコールの基本動作の違いを見ていきたいと思います。GraphQLはREST APIと比較し、必要最低限の欲しいリソースのみを取得できる事が分かります。
REST API:
商品情報を取得するためのAPIエンドポイントが"/api/v2/product/{product-id}"のようにあるとします。サービスとしては商品情報のうち一部のデータがあれば十分であったとしても、レスポンスには商品に関する全ての情報が盛り込まれてしまいます。
Request:
GET /api/v2/product/100
Response:
{
"id":100,
"brand":"Brand-A",
"title":"Title-A",
"sku":"11111",
"ean":"222222222",
"picture":"abc.jpg",
"released":"2018"
...
}
GraphQL:
商品情報を取得するための「単一」のAPIエンドポイントが"/grapql"のようにあるとします。サービスとして必要なデータのみHTTP POST Bodyに記述することで、レスポンスとして必要十分なデータのみを取得する事ができます。以下は、商品情報のうちbrand, sku, titleのみが必要な場合の例となります。
Request:
POST /graphql
query {
products(id:"100") {
brand
sku
title
}
}
Response:
{
"data":{
"products": [
{
"brand":"Brand-A",
"sku":"11111",
"title":" Title-A "
}
]
}
}
動作例2:商品リストの呼び出し
次に、GraphQLがREST APIと比較して、APIコール数を大幅に削減できることが分かる例を見ていきたいと思います。
REST API
商品リストを取得するためのAPIエンドポイントが"/api/v2/profile/{profile-id}"のようにあるとします。最初に商品リストのリクエストを実施するとレスポンスとして複数商品のIDが返ってきますが、その後さらにそれぞれの商品情報取得のために個別にリクエストを実行する必要があります。また、それら一つ一つのレスポンスにはサービスとして必要の無いデータが含まれる場合があります。
Request:
GET /api/v2/profile/10000/wishlist
Response:
{
"profile":"10000",
"public":"true",
"products":[
{"id":"aaa","product":"100"},
{"id":"bbb","product":"121"},
{"id":"ccc","product":"250"},
{"id":"ddd","product":"384"},
{"id":"eee","product":"399"},
{"id":"fff","product":"533"}
]
}
GraphQL
商品リストを取得するための「単一」のAPIエンドポイントが"/grapql"のようにあるとします。HTTP POST Bodyに必要なデータを記述することで、一度のリクエストで必要十分な情報を取得する事ができます。
Request:
POST /graphql
query {
wishlist(id:"10000") {
public
products{
brand
sku
title
}
}
}
Response:
{
"data":{
"wishlist":{
"public": "true",
"products": [{
"brand":"Brand-A",
"sku":"11111",
"title":"Title-A"
},{
"brand":"Brand-B",
"sku":"22222",
"title":"Title-B"
},
...
]
}
}
CDNにおけるキャッシュキーの生成方法
REST APIおよびGraphQLのキャッシュキーの生成方法の違いについて、見ていきたいと思います。
REST API
APIエンドポイントが"http://api.example.com/api/blog/{id}"のようにあるとします。たとえば{id}をクエリストリングで指定する場合、エッジサーバーでは以下のようにURLベースで一意のキャッシュキーを生成する形になります。
URL1: http://api.example.com/api/blog/100
Cache Key1: /L/1/1/365d/api.example.com/api/blog?id=100
URL2: http://api.example.com/api/blog/200
Cache Key2: /L/1/1/365d/api.example.com/api/blog?id=200
GraphQL
APIエンドポイントが"http://api.example.com/graphql"のようにあるとします。GraphQL Caching機能により、エッジサーバーではHTTP POST BodyのParseを行い、Hash値をキャッシュキーとして扱うことができます。
正規化を行ってからHash値を算出するため、POST Bodyに空白、行終了、コメント、コンマ等の差分があっても同一のキャッシュキーとして扱われる形となります。
GraphQL Caching の設定方法
設定方法についてはAPI Gateway User Guideから、「API Gateway operation」>「API Gateway delivery settings 」>「GraphQL caching」をご参考ください。
こちらが直リンクとなっています。
さいごに
本ブログでは、通常CDNで扱う事が難しいとされるGraphQLについて、Akamaiの機能を利用する事でキャッシュが実現できることを説明させていただきました。本来のGraphQLの特徴であるAPIコール数およびペイロードの削減に加え、キャッシュ可能なレスポンスをエッジサーバで保持することでさらなるクライアントのパフォーマンス向上が見込めますし、大基本であるオリジンサーバのオフロードも実現が可能な仕組みとなっております。ぜひ、ご活用をご検討いただけると幸いです。