GraphQL APIのレート制限の実践的な戦略

January 4, 2024

Technology

REST APIにレートリミットを実装するのは比較的簡単です。なぜなら、URIパスを使用して特定のAPIリソースを表し、HTTPメソッドを使用してリソースに対する操作を示すことが一般的だからです。プロキシ層は、この情報に基づいて事前に定義されたレートリミットルールを簡単に適用できます。

しかし、GraphQL APIになると、状況は大幅に複雑になります。これらの課題をどのように克服するかについて詳しく見ていきましょう。

シンプルなシナリオ

REST APIとは対照的に、GraphQLは独自のクエリ言語を使用します。リソースの取得や操作にパスやHTTPメソッドを頼るのではなく、データのクエリと操作をQueryとMutationの下に統一します。Queryはデータを取得し、Mutationは作成、更新、削除などのデータ操作を行います。

GET /users
GET /users/1
POST /users
PUT /users/1
DELETE /users/1

query { users { fullName } }
query { user(id: 1) { fullName } }
mutation { createUser(user: { lastName: "Jack" }) { id, fullName } }
mutation { updateUser (id: 1, update: { lastName: "Marry" }) { fullName } }
mutation { deleteUser (id: 1) }

上記の例は、APIクエリ方法の変化を示しています。RESTとは異なり、GraphQLはリソースに関数を呼び出し、必要な入力パラメータを渡し、クエリされたデータを含むレスポンスを返すようなものです。クエリ方法の違いに加えて、GraphQLは通常、単一のエンドポイント(例: /graphql)を介してAPIを公開し、クエリと入力パラメータはPOSTボディで送信されます。

以下の例を考えてみましょう:

query {
    users {
        fullName
    }
    photos {
        url
        uploadAt
    }
}

このシナリオでは、アルバムのホームページをシミュレートし、/graphqlエンドポイントへのAPI呼び出しでユーザーと写真のリストを同時にクエリします。ここで、従来のリバースプロキシ戦略がGraphQL APIに適用可能かどうかを考えてみましょう。

答えはノーです。従来のリバースプロキシサーバは、クエリ自体を含むGraphQL API呼び出しを効果的に処理できず、レートリミットなどのポリシーを適用することができません。GraphQL APIにとって、「HTTPリクエスト」の粒度は粗すぎるのです。

しかし、APIゲートウェイであるApache APISIXは、GraphQL to HTTP機能を組み込んでいます。管理者はクエリステートメントを事前に設定し、クライアントがGraphQLの詳細を理解せずに必要な入力パラメータを提供するだけで、HTTP POSTを介して直接呼び出すことができます。これにより、セキュリティが向上するだけでなく、このコンテキストでHTTP APIポリシーを適用することが可能になります。

rate-limiting

実質的に、これは動的なGraphQLクエリをAPIプロバイダーが提供する知識ベースの静的なクエリに変換し、利点と欠点の両方をもたらします。時には、GraphQLの動的クエリ機能を犠牲にしたくない場合もあります。他のシナリオについても引き続き議論していきましょう。

複雑なシナリオ

GraphQLは、データモデリングとAPI記述のために独自の言語を使用し、ネストされたデータ構造を可能にします。前の例を拡張してみましょう:

query {
    photos(first: 10) {
        url
        uploadAt
        publisher {
            fullName
            avatar
        }
        comments(first: 10) {
            content
            sender {
                fullName
                avatar
            }
        }
    }
    // users...
}

この場合、最初の10枚の写真を取得し、各写真の出版社と最初の10件のコメントとその送信者を含めるシミュレーションを行います。バックエンドサービスは、複数のデータベーステーブルやマイクロサービスへの呼び出しを含むクエリを処理する必要があります。このようなネストされたクエリシナリオでは、データ量とネストレベルが増加するにつれて、バックエンドサービスとデータベースの計算負荷が指数関数的に増加します。

複雑なクエリがサービスを圧倒しないようにするために、プロキシ層でそのようなクエリを検査してブロックしたい場合があります。この戦略を適用するためには、プロキシコンポーネントがクエリステートメントを構造化データに解析し、各層のネストされたフィールドを取得して、GraphQLの一般的な慣習に従ってフィールドにクエリコストとして複雑さの値を割り当てる必要があります。その後、全体のクエリ複雑さに対してグローバルな制限を適用できます。上記のクエリの場合、各フィールドのコストを1と仮定すると:

10 * photo (url + uploadAt + publisher.fullName + publisher.avatar + 10 * comment (content + sender.fullName + sender.avatar))

10 * (1 + 1 + 1 + 1 + 10 * (1 + 1 + 1)) = 340

クエリの総コストは340で、許容範囲内です。このようなルールに基づいてAPIクエリコストの制限を設定できます。しかし、悪意のあるクライアントが1回のクエリで100枚の写真のデータを取得しようとすると、クエリコストは3400に急増し、事前に定義された制限を超えてリクエストが拒否されます。

クライアントクエリごとの最大コストを制限するだけでなく、時間間隔に対する追加の制限を設けることもできます。例えば、クライアントが1分間に合計2000クエリを許可し、超過したクエリを拒否することで、悪意のあるクローラーを防ぐことができます。

このような機能を実装するためには、プロキシコンポーネントがクエリを解析してコストを計算する必要があります。API7 Enterpriseはこれらの機能をサポートしており、GraphQLクエリを動的に解析し、設定に基づいてユーザーレートリミットを実装できます。

GraphQL APIは、プロキシ層で課題に直面しています。従来のリバースプロキシは、GraphQLクエリステートメント内の複雑さとネスト関係を効果的に認識して処理することが困難です。一方、APIゲートウェイ技術はこれらの課題を克服するのに非常に有効であり、API7 Enterpriseはそのための優れた選択肢です。

Tags: