Backend

rate limits

rate limits

Когда вы запускаете первый пет-проект, вопрос ограничений часто вообще не стоит. Но как только сервис выходит в продакшн и на него налетают либо реальные пользователи, либо криво написанные скрипты-парсеры, либо злоумышленники с DDoS-атакой, выясняется неприятная вещь: ресурсы сервера конечны. CPU сгорает, база данных уходит в «ступор» из-за количества соединений, а память заканчивается быстрее, чем вы успеваете открыть дашборд мониторинга.

Здесь на сцену выходит Rate Limiting (ограничение частоты запросов). Это не просто «заглушка», а критически важный механизм обеспечения отказоустойчивости (resilience) и безопасности системы.

Зачем это нужно на самом деле?

Многие думают, что Rate Limit нужен только для защиты от хакеров. Это заблуждение. Вот основные причины, почему без него нельзя обходиться:

  1. Защита от «шумных соседей» (Noisy Neighbor Problem). В многопользовательских системах один клиент может забить весь канал запросами, из-за чего остальные пользователи будут видеть 504 Gateway Timeout.
  2. Предотвращение каскадных сбоев. Если один из микросервисов начинает тормозить, а вызывающая сторона продолжает слать запросы с огромной скоростью, очередь накапливается, и в итоге падает вся цепочка сервисов.
  3. Экономия денег. Если вы используете платные внешние API (например, OpenAI или Google Maps), бесконтрольный поток запросов может обнулить ваш бюджет за несколько часов.
  4. Борьба с брутфорсом и парсингом. Ограничение попыток входа или частоты скачивания страниц делает автоматический перебор паролей или кражу контента экономически невыгодными.

Популярные алгоритмы реализации

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

1. Fixed Window Counter (Счетчик в фиксированном окне)

Самый простой вариант. Мы определяем временной отрезок (например, 1 минута) и лимит (например, 100 запросов).

    • Как работает: Создается запись в кэше с ключом user_id:timestamp. С каждым запросом счетчик инкрементируется. Если он превышает 100 — возвращаем ошибку.
    • Минус: «Проблема границы». Если пользователь отправит 100 запросов в последние 2 секунды первой минуты и еще 100 в первые 2 секунды следующей, система пропустит 200 запросов за очень короткий промежуток времени.

2. Sliding Window Log (Лог скользящего окна)

Более точный, но ресурсозатратный метод.

    • Как работает: Для каждого пользователя хранится список меток времени (timestamps) всех его запросов. При новом запросе удаляются все метки, которые старше текущего окна, и проверяется размер оставшегося списка.
    • Минус: Огромный расход памяти, если лимиты высокие. Хранить тысячи временных меток для каждого пользователя в Redis — плохая идея.

3. Token Bucket (Корзина с токенами)

Классика, используемая в Amazon и многих других гигантах.

    • Как работает: У пользователя есть «корзина», куда с определенной скоростью (например, 2 токена в секунду) падают токены. Максимальный размер корзины ограничен. Каждый запрос «съедает» один токен. Если корзина пуста — запрос отклоняется.
    • Плюс: Позволяет обрабатывать кратковременные всплески трафика (bursts), пока в корзине есть запас токенов.

4. Leaky Bucket (Дырявое ведро)

Похоже на предыдущий, но с одним важным отличием: запросы выходят из системы с постоянной скоростью.

    • Как работает: Запросы попадают в очередь (ведро). Независимо от того, как быстро они туда залетают, сервер обрабатывает их строго по одному в секунду. Если ведро переполнилось — новые запросы выбрасываются.
    • Плюс: Идеально для сглаживания трафика и защиты бэкенда от резких скачков.

Где именно внедрять ограничения?

В зависимости от архитектуры, Rate Limit можно поставить на разных уровнях:

  1. Client-side (Клиент). Сделать ограничение в самом приложении. Это полезно для UX (чтобы пользователь не кликал по кнопке «Отправить» десять раз в секунду), но абсолютно бесполезно для безопасности, так как API-запрос можно отправить через curl или Postman.
  2. API Gateway / Reverse Proxy. Самое правильное место. Nginx, Kong, Traefik или AWS API Gateway умеют ограничивать трафик еще до того, как запрос дойдет до вашего приложения. Это экономит ресурсы вашего приложения.
  3. Application Level (Код приложения). Реализуется через Middleware. Нужно, если логика ограничения сложная (например, лимиты зависят от подписки пользователя в базе данных или от сложности самого запроса).

Практические советы по внедрению

Если вы решили внедрить Rate Limiting, не забудьте про следующие моменты, чтобы не разозлить пользователей и коллег:

Информативные заголовки. Не просто отдавайте 429 Too Many Requests, а добавьте в ответ HTTP-заголовки:

    • X-RateLimit-Limit: Общий лимит.
    • X-RateLimit-Remaining: Сколько запросов осталось.
    • Retry-After: Через сколько секунд можно повторить попытку.

Использование Redis. Не храните счетчики в памяти самого приложения (in-memory), если у вас больше одного экземпляра сервиса (горизонтальное масштабирование). Используйте Redis — он быстрый, поддерживает атомарные операции (INCR) и имеет встроенный механизм TTL (время жизни ключа).

Гибкость лимитов. Разделяйте лимиты для разных типов пользователей.

    • Анонимы: 10 запросов в минуту.
    • Зарегистрированные пользователи: 100 запросов в минуту.
    • Админы/Партнеры: Безлимит или очень высокий порог.

White-listing. Оставьте возможность добавлять IP-адреса в белый список (например, для внутренних сервисов мониторинга или доверенных партнеров), чтобы случайно не заблокировать самого себя.

Резюме

Rate Limiting — это не про ограничение возможностей пользователя, а про обеспечение стабильности системы. Начинать лучше с простого Fixed Window на уровне Nginx, но по мере роста продукта переходить к более гибким схемам вроде Token Bucket с хранением состояния в распределенном кэше. Помните: лучше вернуть пользователю ошибку 429, чем позволить всей системе упасть в бесконечный ребут из-за перегрузки.