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 }
}
}
}
}
}
}
Складність запиту
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
]
})
Складність у 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}`







