Практические стратегии для ограничения скорости (rate limiting) в 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 обычно предоставляет API через одну конечную точку (например, /graphql), а запросы и входные параметры отправляются через тело POST.

Рассмотрим следующий пример:

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

В этом сценарии, имитирующем главную страницу альбома, вызов API к конечной точке /graphql запрашивает одновременно списки пользователей и фотографий. Теперь задумайтесь, остаются ли применимыми традиционные стратегии обратного прокси, выполняемые на уровне запросов, для GraphQL API.

Ответ — нет. Традиционные серверы обратного прокси не могут эффективно обрабатывать вызовы GraphQL API, содержащие сами запросы, что делает невозможным применение политик, таких как ограничение скорости. Для GraphQL API гранулярность "HTTP-запросов" кажется слишком грубой.

Однако API-шлюз Apache APISIX включает встроенную поддержку возможностей GraphQL to HTTP. Администраторы могут предварительно настроить запрос, позволяя клиентам вызывать его напрямую через HTTP POST, не понимая деталей GraphQL, а только предоставляя необходимые входные параметры. Это не только повышает безопасность, но и позволяет применять политики 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 на основе таких правил. Однако, если злоумышленник попытается получить данные для 100 фотографий в одном запросе, стоимость запроса взлетит до 3400, превышая предопределенный лимит, и запрос будет отклонен.

Помимо ограничения максимальной стоимости на запрос клиента, можно установить дополнительные ограничения на временные интервалы, например, разрешая клиентам всего 2000 запросов в минуту и отклоняя избыточные запросы, что может предотвратить злонамеренные действия.

Для реализации таких возможностей прокси-компонент должен разбирать и рассчитывать стоимость запросов. API7 Enterprise поддерживает эти функции, позволяя динамически разбирать запросы GraphQL и реализовывать ограничения скорости для пользователей на основе конфигураций.

GraphQL API сталкивается с проблемами на уровне прокси, где традиционные обратные прокси не могут эффективно воспринимать и обрабатывать сложность и вложенные отношения в запросах GraphQL. В отличие от этого, технологии API-шлюзов оказываются неоценимыми в преодолении этих сложностей, где API7 Enterprise может быть отличным выбором.

Tags: