Настройка GraphQL Rate Limiting и Depth Limiting

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка GraphQL Rate Limiting и Depth Limiting
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Rate Limiting и Depth Limiting для GraphQL API

GraphQL позволяет клиентам формировать произвольно сложные запросы. Без ограничений один запрос может запросить миллионы записей через вложенные связи или создать CPU-killer запрос с глубиной вложенности 50 уровней. Rate limiting для GraphQL отличается от REST: нельзя просто считать запросы — нужно учитывать сложность.

Depth Limiting

Ограничение максимальной глубины вложенности AST-дерева запроса:

import depthLimit from 'graphql-depth-limit'
import { ApolloServer } from '@apollo/server'

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(7)  // максимум 7 уровней вложенности
  ]
})

Атака без depth limit:

# Этот запрос легален, но рекурсивно создаёт миллионы объектов
{
  user {
    friends {
      friends {
        friends {
          friends {
            friends { id name }
          }
        }
      }
    }
  }
}

Query Complexity (сложность запроса)

Depth не учитывает широту запроса. graphql-query-complexity считает суммарную стоимость:

import { createComplexityLimitRule } from 'graphql-query-complexity'
import { fieldExtensionsEstimator, simpleEstimator } from 'graphql-query-complexity'

const complexityRule = createComplexityLimitRule(1000, {
  estimators: [
    // Брать complexity из SDL @complexity директивы
    fieldExtensionsEstimator(),

    // Учитывать аргументы пагинации
    ({
      type, field, args, childComplexity
    }) => {
      if (args.limit) {
        return args.limit * childComplexity
      }
      if (args.first) {
        return args.first * childComplexity
      }
      return 1 + childComplexity
    },

    // Базовая стоимость поля = 1
    simpleEstimator({ defaultComplexity: 1 })
  ],

  onSuccess: (complexity) => {
    console.log(`Query complexity: ${complexity}`)
  },

  formatErrorMessage: (complexity) =>
    `Query too complex (${complexity}). Max allowed: 1000`
})

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(7),
    complexityRule
  ]
})

Complexity в SDL с директивами

directive @complexity(value: Int!, multipliers: [String!]) on FIELD_DEFINITION

type Query {
  # Простое поле: complexity 1
  user(id: ID!): User

  # Список: complexity = first * childComplexity
  posts(first: Int = 10): [Post!]! @complexity(value: 1, multipliers: ["first"])

  # Дорогая операция: complexity 10
  searchUsers(query: String!): [User!]! @complexity(value: 10)
}

Rate Limiting по операциям

// Разные лимиты для разных типов операций
class GraphQLRateLimiter {
  constructor(redis) {
    this.r = redis
  }

  async checkRequest(userId, operationName, complexity) {
    const now = Math.floor(Date.now() / 1000)
    const minute = now - (now % 60)

    // 1. Лимит по количеству операций (запросов) в минуту
    const opsKey = `gql:ops:${userId}:${minute}`
    const ops = await this.r.incr(opsKey)
    this.r.expire(opsKey, 120)

    if (ops > 200) {
      throw new GraphQLError('Too many requests', {
        extensions: { code: 'RATE_LIMITED', retryAfter: 60 }
      })
    }

    // 2. Лимит по суммарной сложности в минуту
    const complexityKey = `gql:complexity:${userId}:${minute}`
    const totalComplexity = await this.r.incrby(complexityKey, complexity)
    this.r.expire(complexityKey, 120)

    if (totalComplexity > 10000) {
      throw new GraphQLError('Query complexity budget exceeded', {
        extensions: { code: 'COMPLEXITY_LIMITED', retryAfter: 60 }
      })
    }

    // 3. Специальные лимиты для дорогих операций
    const expensiveOps = ['SearchUsers', 'ExportData', 'GenerateReport']
    if (expensiveOps.includes(operationName)) {
      const expKey = `gql:expensive:${userId}:${minute}`
      const expCount = await this.r.incr(expKey)
      this.r.expire(expKey, 120)

      if (expCount > 5) {
        throw new GraphQLError(`Too many ${operationName} calls`, {
          extensions: { code: 'RATE_LIMITED' }
        })
      }
    }

    return { allowed: true, remainingOps: 200 - ops }
  }
}

Apollo Server Plugin для rate limiting

const rateLimiter = new GraphQLRateLimiter(redis)

const rateLimitPlugin = {
  async requestDidStart() {
    return {
      async executionDidStart({ request, context }) {
        const userId = context.user?.id || context.req.ip
        const operationName = request.operationName || 'anonymous'

        // Complexity рассчитывается до выполнения
        const complexity = request.extensions?.queryComplexity || 1

        await rateLimiter.checkRequest(userId, operationName, complexity)
      }
    }
  }
}

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [rateLimitPlugin],
  validationRules: [depthLimit(7), complexityRule]
})

Защита от Introspection в продакшен

Introspection раскрывает полную схему — нежелательно в продакшен:

import { NoSchemaIntrospectionCustomRule } from 'graphql'

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
  validationRules: process.env.NODE_ENV === 'production'
    ? [NoSchemaIntrospectionCustomRule]
    : []
})

Лимиты aliases и directives

Aliases позволяют запросить одно поле с разными аргументами — DoS вектор:

# 1000 одинаковых запросов через aliases
{
  a1: user(id: "1") { name }
  a2: user(id: "1") { name }
  # ... a1000
}
import { createComplexityLimitRule } from 'graphql-query-complexity'

// graphql-armor — комплексная защита
import { createArmor } from '@escape.tech/graphql-armor'

const armor = createArmor({
  maxAliases: { n: 15 },           // максимум 15 aliases
  maxDirectives: { n: 50 },        // максимум 50 директив
  maxDepth: { n: 7 },              // глубина
  maxTokens: { n: 1000 },          // токены в запросе
  costLimit: {
    maxCost: 5000,
    objectCost: 2,
    scalarCost: 1,
    depthCostFactor: 1.5,
    ignoreIntrospection: true
  }
})

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [...armor.plugins],
  validationRules: [...armor.validationRules]
})

Срок выполнения

Настройка depth limiting, query complexity и rate limiting для GraphQL — 1–2 рабочих дня.