Rate Limiting на практике: как не сгореть под нагрузкой и не потерять клиентов


Введение: почему ваш API уже сегодня может быть на грани коллапса

Представьте: утром вы запускаете новый микросервис, который должен обрабатывать тысячи запросов в секунду. Всё работает гладко — до тех пор, пока не происходит одно из двух:

  1. Бот или скрипт начинает шлепать запросы со скоростью 10 000 в секунду, потому что у него так прописано в коде.
  2. Ваш же клиент (или конкурент) отправляет массовые запросы, потому что у него нет ограничений на стороне API.

Результат: серверы начинают throttling-ить запросы, клиенты получают 503 Service Unavailable, а вы — звонки с претензиями. Всё это — классические последствия отсутствия Rate Limiting.

Но Rate Limiting — это не только защита от злоумышленников. Это ещё и:

  • Экономия ресурсов: вы не тратите CPU и память на обработку ненужных запросов.
  • Предсказуемость: клиенты знают, что им ответит API, и не будут писать жалобы в поддержку.
  • Контроль над трафиком: вы можете давать приоритет важным запросам, а остальные — отсекать.

Проблема: когда Rate Limiting становится спасательным кругом

Сценарий 1: API без ограничений — это бомба замедленного действия

Допустим, у вас есть публичный API, который используется тысячами клиентов. Вы не ограничиваете количество запросов — и вот что происходит:

  • Боты и скрипты начинают атаковать ваш сервис, потому что нет никаких барьеров.
  • Клиенты начинают жаловаться, что API работает медленно или вовсе падает.
  • Вы тратите время на то, чтобы разбираться с проблемами, вместо того чтобы развивать продукт.

Сценарий 2: Rate Limiting как часть бизнес-логики

Но Rate Limiting — это не только защита. Это ещё и инструмент управления трафиком. Например:

  • Бесплатные тарифы могут иметь ограничение в 100 запросов в минуту.
  • Платные тарифы могут иметь больше прав, но при этом вы можете ограничивать пиковую нагрузку, чтобы не перегружать базу данных.
  • Внутренние сервисы могут иметь разные приоритеты: одни запросы обрабатываются быстрее, другие — медленнее.

Практика: как правильно внедрять Rate Limiting

1. Выбор алгоритма

На практике используются три основных подхода:

Алгоритм Описание Пример использования
Fixed Window Запросы считаются в фиксированных интервалах (например, 100 запросов в минуту). Простой, но может привести к "спайкам" в конце окна.
Sliding Window Интервал скользит, и запросы считаются плавно. Более точный, но сложнее в реализации.
Token Bucket Запросы "покупают" токены из "ведра", которое пополняется со временем. Хорошо подходит для переменной нагрузки.

Лучший выбор? Если вам нужна простота — Fixed Window. Если важна точность — Sliding Window или Token Bucket.

2. Где ставить Rate Limiting?

  • На уровне API-гейтвея (например, Nginx, Kong, Traefik) — если вам нужна централизованная защита.
  • На уровне приложения (например, Express.js middleware, Flask-Limiter) — если вам нужна гибкость.
  • На уровне базы данных (например, PostgreSQL pgbouncer) — если проблема в нагрузке на БД.

Пример конфигурации Nginx:

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        proxy_pass http://backend;
    }
}

Здесь:

  • rate=10r/s — 10 запросов в секунду на IP.
  • burst=20 — допускается до 20 запросов сразу (для сглаживания пиков).
  • nodelay — не ждёт конца интервала, если запросы уже в очереди.

3. Как сообщать клиентам об ограничениях?

Клиенты должны знать, что API ограничен, иначе они будут писать жалобы. Используйте заголовки:

HTTP/1.1 429 Too Many Requests
Retry-After: 5
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 60

Пример реализации на Express.js:

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 минут
  max: 100, // лимит на окно
  message: 'Too many requests, please try again later.',
  headers: true
});

app.use('/api/', limiter);

Примеры: как это работает в реальных системах

Пример 1: Ограничение на уровне Docker Swarm

Если у вас кластер Docker Swarm, можно использовать Traefik для Rate Limiting:

# traefik.yml
entryPoints:
  web:
    address: ":80"

providers:
  docker:
    exposedByDefault: false

api:
  dashboard: true

middlewares:
  rate-limit:
    rate:
      average: 100
      burst: 50

Пример 2: Ограничение на уровне Kubernetes (Istio)

Если вы используете Istio, можно настроить Rate Limiting через Envoy:

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: rate-limit
spec:
  workloadSelector:
    labels:
      app: my-service
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        portNumber: 8080
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.local_ratelimit
        typed_config:
          "@type": type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
          value:
            stat_prefix: http_local_rate_limiter
            token_bucket:
              max_tokens: 100
              tokens_per_fill: 100
              fill_interval: 60s

Типичные ошибки и как их избежать

  1. Неправильный выбор алгоритма

    • Ошибка: Использование Fixed Window для переменной нагрузки.
    • Решение: Перейти на Sliding Window или Token Bucket.
  2. Отсутствие гибкости в настройках

    • Ошибка: Одно и то же ограничение для всех клиентов.
    • Решение: Использовать разные лимиты для разных групп (например, VIP-клиенты vs обычные).
  3. Неправильная обработка ошибок

    • Ошибка: Возврат 500 Internal Server Error вместо 429 Too Many Requests.
    • Решение: Всегда возвращайте 429 с заголовками Retry-After и X-RateLimit-*.
  4. Забывание про тестирование

    • Ошибка: Rate Limiting настроен, но не протестирован под нагрузкой.
    • Решение: Используйте Locust или k6 для симуляции трафика.
  5. Отсутствие логирования

    • Ошибка: Нет информации о том, кто и когда превысил лимит.
    • Решение: Логируйте события превышения лимита с метаданными (IP, путь, время).

Вывод: как правильно внедрять Rate Limiting без боли

  1. Начните с простого

    • Настройте базовый Fixed Window на уровне API-гейтвея (Nginx, Traefik).
    • Проверьте, что это работает, и только потом усложняйте.
  2. Гибкость > Жесткость

    • Не накладывайте одно ограничение на всех. Разные клиенты — разные лимиты.
    • Используйте Token Bucket, если у вас переменная нагрузка.
  3. Сообщайте клиентам

    • Всегда возвращайте 429 с правильными заголовками.
    • Документируйте лимиты в API-спецификации (Swagger/OpenAPI).
  4. Тестируйте под нагрузкой

    • Используйте Locust или k6 для симуляции трафика.
    • Проверяйте, как система ведёт себя при превышении лимита.
  5. Логируйте и мониторьте

    • Отслеживайте, кто и когда превышает лимит.
    • Используйте Prometheus + Grafana для визуализации метрик.

Итог: Rate Limiting — это не просто защита от DDoS, а инструмент, который помогает контролировать нагрузку, экономить ресурсы и не терять клиентов. Начните с простых решений, но не останавливайтесь на достигнутом — оптимизируйте и тестируйте.