GraphQL APIのパフォーマンステスト

この記事はBob Reselmanによるゲスト投稿です。

GraphQLは、2012年にFacebookでニュースフィードデータの送信方法を改善することを目的として開発され、利用されるようになったAPIアーキテクチャです。2015年にオープンソース化されて以来、GraphQLは広く採用されており、Atlassian、Credit Karma、GitHub、Intuit、KLM、Pinterest、Shopify、The New York Times、WordPress、Yelpなどの企業のAPIを支えています。

FacebookはGraphQLを仕様として開発しました。この仕様は Node.jsRubyPythonPHPJavaC#など、多くのテクノロジーで実装されています。

GraphQLは革新的テクノロジーです。APIの設計やAPIテスト、特にパフォーマンステストに関して新たな考え方を要求します。

静的データ構造および反復的クエリーへの対処

GraphQLはAPIを利用する際に開発者が遭遇する2つの問題に対処します。1つは、多くの場合、APIによって返されるデータ構造が不変であることによる問題、もう1つは反復的クエリーの問題です。反復的クエリーとは、意味のあるデータセットを作成するのにAPIを連続して呼び出す必要があることを指します。これらの問題をわかりやすいサンプルシナリオを例に考えてみましょう。

たとえば、ユーザーが特定の著者の本を検索し、その後、本の詳細情報を公開できるようなアプリケーション機能を実装するとしましょう。その際、 Open Libraryが公開しているパブリックAPIを利用します。Open Library APIはREST(ful)であるため、特定の著者の書籍情報を取得するには、次のようなURLを作成してOpen Libraryの検索APIを呼び出します。

http://openlibrary.org/search.json?author=charles+bukowski

すると、著者Charles Bukowskiに関連する書籍のデータが返されます。下記はレスポンスの一部を抜粋したものです。

サンプル: 通常、REST APIは事前に定義されたデータ構造に従ってデータを返す

APIが大量のデータを返していることに注目してください。一部のデータは、著者が書いた本に直接関連していますが、書籍とは無関係のデータもあります。

ここでの目的となるデータは、書籍のタイトルとISBN番号です。レスポンスの中を探してみると、たしかに各書籍のタイトルとISBN番号が見つかります(上の図では、目的の情報を赤い枠で囲って強調してあります)。ISBN番号が取得できたら、Open Library APIのbooksセクションを使って特定の書籍の詳細情報を取得できます。

次のようなURLを作成します:

https://openlibrary.org/api/books?bibkeys=ISBN:0876850875

APIは下記のJSON構造を返します。

{
     "ISBN:0876850875": { 
        "bib_key": "ISBN:0876850875",
         "preview": "noview",
         "preview_url": "https://openlibrary.org/books/OL17228140M/Post_office",
         "info_url": "https://openlibrary.org/books/OL17228140M/Post_office"
     }
 }

APIが別のAPI呼び出しを記述するURLをデータポイントとして返す場合がある

上記のサンプルでは、単純な文字列データとURLの両方を含むJSON構造が返されていることに注目してください。これは、APIに対してこれらのURLを呼び出すと、さらに追加の情報を取得できることを表しています。サンプルのinfo_urlフィールドのURLをAPIにサブミットします。

https://openlibrary.org/books/OL17228140M/Post_office

返されたレスポンスは、下の図のように、書籍に関するすべての情報を含むWebページのHTMLです。

APIは複雑なデータを任意のフォーマット、たとえばHTMLなどでも返す場合がある

この例では、機能要件を満たす情報を取得するために、APIを3回呼び出す必要がありました。つまり、最初に著者による書籍を取得し、次にISBN番号によって書籍の詳細を取得し、最後に2番目の呼び出しによって返されたURLを使用して書籍のすべての情報を取得しました。さらに、各呼び出しの結果として、目的の情報だけではなく、まったくいらない情報も返されました。

このような一連の処理を行う代わりに、欲しい情報を記述して1回だけクエリーを実行し、欲しい情報を欲しい形で受け取ることができたらいいと思いませんか?

GraphQLの導入

GraphQLを利用すると、APIによって提示されるオブジェクトグラフという形でレスポンスのデータ構造を記述したクエリーを作成できます。GraphQL APIにクエリーをサブミットすると、フィールド定義に沿ったレスポンスが返されます。次はクエリーのサンプルです。

query{
   search(author: "charles bukowski") {
     books {
       title
       isbn
       num_of_pages
       opening_paragraph
       cover_art
     }
   }
 }

このクエリーはGraphQLクエリー言語で記述されており、「著者Charles Bukowskiによって書かれた書籍を検索し、返された書籍ごとにタイトル、ISBN番号、ページ数、冒頭のパラグラフ、表紙画像を表示する」という意味です。

クエリーが返すフィールドは、APIの背後にあるオブジェクトグラフ固有です。また、クエリーの名前searchも固有です。GraphQLでは、予約語を除けば、クエリーおよびミューテーションを一意に識別する任意の名前を使用できます(ミューテーションとは、APIによって保存されたデータを変更するクエリーです)。

オブジェクトグラフとは

オブジェクトグラフとは、離散数学における構造で、事物と事物の間の関係を記述するものです。正式な用語では事物をノードと呼びます。事物間の関係を表す用語はエッジです。

従来のリレーショナルデータベースでは、データは表形式で組織され、行と列で構成されます。非SQLデータベースはデータをドキュメントに保存し、内部のデータ関係は、学者が書籍や学術論文の内容を整理するときのような多層アウトライン形式で記述されます。

いっぽうオブジェクトグラフは、次の図に示すように、多数のノード間の多数の関係を容易に記述できる柔軟性を備えています。

例からわかるとおり、REST APIを使用して書籍情報を取得する場合は、要件を満たすデータを取得するまでに、ネットワーク経由でサーバーと何往復か通信する必要がありましたが、GraphQLでは同じ目的を1回のクエリーで達成できました。GraphQLはクエリーに記述された情報だけを返すので、余計なオーバーヘッドがありません。GraphQLクエリーでフィールドの記述を変更してレスポンスのデータ構造を変更することもできます。簡単に言えば、GraphQLを利用すると、RESTの場合と比べてデータ交換が1往復に減るだけでなく、抽出されクライアントに送り返されるデータの量もかなり少なくなります。さらに、RESTレスポンスのデータ構造が不変であるのに対し、GraphQLのデータ構造は柔軟です。

しかし、一見いいことずくめのようでも、GraphQLの利用を検討する際にパフォーマンステスターが意識しておくべき潜在的な問題があります。事前に注意していないと、GraphQL API の実装で意図しないサーバー側のボトルネックが発生するという重大なリスクがあります。実際、2018年の終わりごろ、この問題がNetflixによって発見されました

GraphQLのパフォーマンスボトルネックの危険性

GraphQLを利用すると、クライアントとサーバー間のネットワーク上では1往復でクエリーの要求を満たせるからと言って、ネットワーク全体のトランザクションまで削減されるとは限りません。パフォーマンスの問題が最小化されるというわけでもありません。この潜在的な危険性をわかりやすいシナリオを例に考えてみましょう。

下の図は、クライアントとREST API間の典型的な複数リクエストによるやり取りを表しています。シナリオでは、グローバルリージョンUS CENTRALに配置されたREST APIサーバーへの3回の呼び出しが示されており、各呼び出しが特定のリクエストに応える特定のアプリケーションに転送されていることが示されています。各アプリケーションはREST APIサーバーとは異なるグローバルリージョンに配置されています。言い換えれば、すべてが分散されています。

REST APIが個々の実行アプリケーションに転送される

ネットワークトポロジーは最適とは言えませんが、少なくとも潜在的な危険性は個々のリクエストの範囲に制限されます。そのため、パフォーマンスの問題が発生した場合も、追跡は比較的容易です。実際、問題を修正する際は、アクセスログのURLとREST APIサーバーの転送トラフィックのURLを比べるだけで済むでしょう。

さて次に、GraphQL APIを用意してデータの取得を1つのリクエストにまとめた場合のシナリオを見てみましょう。下の図が示すように、クライアントはGraphQL APIサーバーに1つのクエリーを送信し、必要なすべてのデータをクエリーのレスポンスとして受け取ります。

GraphQLクエリーは1回でも、内部的なネットワーク遅延の増加によりサーバーサイドでパフォーマンスが劣化する可能性がある

しかし、背後のサーバー側では、GraphQLサーバーが異なるロケーションにある異なるアプリケーションにネットワーク経由で複数の呼び出しを行っています。GraphQLサーバーのインテリジェントな機能が複数のデータプロバイダーからのデータを1つのレスポンスに集約します。こうして、クライアント側では1往復のネットワーク上のやりとりが、サーバー側では何往復にもなり、サーバーサイドのやりとりのいずれかが著しいネットワークの遅延を招くこともあります。これが、Netflixが同社のマーケティングキャンペーンを外部プラットフォームで管理するためのプロジェクトであるMonetを実装したときにはじめて経験した問題でした。

NetflixのエンジニアはすべてのAPIアクティビティを1つのGraphQL API URLに統合しました。しかし、背後ではGraphQLサーバーが異なるデータセンターにあるさまざまなRESTサービスに処理を委譲していました。パフォーマンスはあまりよくありませんでした。解決策は、すべてのRESTサーバーをGraphQLサーバーと同じデータセンターに移すことでした。サーバーのハードウェアが近くなったおかげで、パフォーマンスは大幅に改善されました。

適切な準備がGraphQL APIテストの鍵

幸い、Netflixは早期に問題を突き止め、解決することができました。しかし、パフォーマンス解析を計画する際、しかるべき準備をしていなければ、Netflixのようにうまくはいかないかもしれません。問題は、クライアントからどのようにGraphQLを呼び出しているかに関係します。

特定のリソースを表す個別のURLに対してリクエストが行われるRESTとは異なり、クライアントはGraphQL APIが公開する1つのURLを使用してHTTP POST経由でGraphQL APIにクエリーを発行します。その後、GraphQLサーバーが内部的にルート転送を行います。そのため、事前に準備しておかないかぎり、リクエストがサーバー側の基盤で通ったルート全体を追跡するのは難しい場合があります。

ボトルネックの疑いがあるとき、HTTPアクセスログの情報と他の追跡情報を関連付けるだけでは、根本的な原因を特定できません。少なくとも、リクエストがAPI側でどのように処理されたかを追跡するキーとして、GraphQL APIへのクエリーリクエストにコリレーション IDを追加しておく必要があります。

GraphQL APIでパフォーマンスをモニターするには、単にクライアントでのリクエストからレスポンスまでの時間を測るだけでは不十分だということを理解するのが重要です。パフォーマンスのボトルネックがサーバー側のどこで起こるかわからないだけでなく、リクエストのルートがわかりにくいためにパフォーマンスのモニターが特に困難になる可能性があります。事前の準備が必要です。どのような準備が必要かは、GraphQLの実装によって異なるでしょう。テスト準備の要となるのは、GraohQLの実装を詳細にわたって理解し、パフォーマンスのボトルネックを検出できる包括的なモニタリングが確実に実施されるようにすることです。

まとめ

GraphQLは企業がAPIを公開する手段として人気を集めています。GraphQLは簡潔でありながら柔軟性のあるデータ管理方法を提供します。REST APIを使用すると、特定のニーズを満たすデータを取得するためにクライアントがネットワーク上で何往復かやりとりしなければならない場合があるいっぽうで、GraphQLを使用すると1往復で済みます。またGraphQLでは、まさにクライアントのニーズを満たすデータ構造を宣言できます。REST APIではデータ構造が事前定義され、不変であるため、これはGraphQLがRESTより有利な点です。

しかし、GraphQLはAPI実装に関するあらゆる問題を解決する万能薬ではありません。パフォーマンスのボトルネックが発生する可能性があり、根本的な原因を突き止めるのが難しい場合もあります。そのため、テスト担当者がGraphQL API開発者と密接に連携し、適切な追跡メカニズムを用意するとともに、単にクライアントとサーバー間でリクエストとレスポンスの間隔を計測する以上のパフォーマンステストを作成することが重要です。GraphQL実装のすべての部分をパフォーマンス解析の対象とする必要があります。

GraphQLには利点が数多くありますが、有効性を確実にするには、計画と事前対策が必要です。

(この記事は、開発元Gurock社の Blog 「Performance Testing a GraphQL API」2019年5月521日の翻訳記事です。)

eBook 公開中

Paul Gerrard著 効果的なテスト管理12の秘密 (日本語)

テスト計画やテスト管理に役立つ12のトピックを解説します。

詳細はこちら