Рейтлимитинг в API: как защитить бэкенд от перегрузок

Рейтлимитинг в API: как защитить бэкенд от перегрузок сен, 6 2026

Представьте ситуацию: вы запускаете новый микросервис. Всё работает идеально на тестовом стенде. Но стоит выпустить его в продакшн, как начинается хаос. Один агрессивный клиент или скрипт-парсер начинает слать тысячи запросов в секунду. База данных захлебывается, CPU сервера улетает в космос, а обычные пользователи получают ошибки 503 вместо нужных данных. Знакомо? Это классический случай, когда рейтлимитинг отсутствует или настроен неправильно. Без него ваш API открыт для любого, кто хочет его «положить».

Многие думают, что достаточно поставить Nginx перед приложением и забыть о проблеме. Но это полумера. Рейтлимитинг - это не просто пропускная способность сети, это бизнес-логика защиты ресурсов. В этой статье мы разберем, как правильно внедрить ограничение частоты запросов, какие алгоритмы использовать и почему стандартные решения часто подводят в распределенных системах.

Зачем вообще нужен лимит на запросы?

Давайте честно: если у вас внутренний корпоративный сервис с тремя пользователями, вам, возможно, он не нужен. Но как только появляется внешний трафик, все меняется. Рейтлимитинг решает три конкретные задачи, которые нельзя игнорировать.

  • Защита инфраструктуры. Ваш сервер имеет конечную производительность. Если входящий поток превышает возможность обработки, очередь растет до бесконечности, пока память не кончится. Лимит отсекает лишний шум на входе.
  • Справедливость доступа. Представьте, что один пользователь скачивает архивы через API, занимая весь канал связи. Остальные ждут ответа по 10 секунд. Квоты гарантируют, что никто не монополизирует ресурс.
  • Монетизация. Хотите продавать доступ к данным? Тогда вам нужно различать бесплатный тариф (100 запросов в час) и платный (10 000). Без точного учета это невозможно.

Важно понимать: рейтлимитинг - это часть архитектуры, а не просто настройка веб-сервера. Он должен быть предсказуемым для клиента. Клиент должен знать, сколько у него осталось «жетонов», и когда можно повторить попытку.

Выбор алгоритма: Token Bucket против Leaky Bucket

Существует несколько математических моделей для подсчета запросов. Не все они одинаково полезны для разных типов нагрузки. Давайте сравним два самых популярных подхода, чтобы вы не выбрали вслепую.

Сравнение алгоритмов рейтлимитинга
Критерий Token Bucket (Ведро с токенами) Leaky Bucket (Дырявое ведро) Fixed Window (Фиксированное окно)
Поведение при всплесках Разрешает кратковременные пики, если был запас Сглаживает пиковую нагрузку, превращая её в ровный поток Пропускает двойной объем на границе окон
Сложность реализации Средняя (требует хранения состояния времени последнего обновления) Высокая (часто требует очереди задач) Низкая (простой счетчик)
Лучшее применение Интерактивные API, мобильные приложения Отправка писем, фоновые задачи, запись логов Грубая защита от DDoS, простые статистики
Эффективность памяти Очень высокая (хранится только баланс и время) Средняя (зависит от размера буфера) Высокая (один ключ на период)

Для большинства современных REST API я рекомендую Token Bucket. Почему? Потому что пользователи так работают. Они могут нажать кнопку пять раз подряд, а потом ждать минуту. Фиксированное окно наказывает их за активность в начале периода, даже если они простаивали весь предыдущий час. Token Bucket же накапливает «токены» во время простоя и позволяет совершить серию быстрых действий сразу после загрузки страницы.

Визуализация алгоритмов Token Bucket и Leaky Bucket

Где хранить состояние: проблема распределенных систем

Если у вас одно приложение на одном сервере, вы можете хранить счетчики в оперативной памяти процесса. Быстро, дешево, сердито. Но современная разработка редко обходится одним инстансом. Вы используете Kubernetes, балансировщик нагрузки распределяет трафик между десятью подами. Где хранится информация о том, сколько запросов сделал пользователь ID 12345?

Если каждый под считает локально, то пользователь может сделать 100 запросов к поду A и 100 к поду B, хотя лимит был 100. Итого 200. Это нарушение контракта. Поэтому в распределенных средах обязательно нужен централизованный стор для метрик.

Стандарт де-факто здесь - Redis. Он быстрый, поддерживает атомарные операции и отлично справляется с TTL (временем жизни ключей). Реализовать алгоритм Token Bucket в Redis можно одной Lua-скриптом, который гарантирует, что проверка баланса и списание токенов произойдут одновременно, без гонок данных.

-- Псевдокод логики в Redis
local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
-- ... логика расчета новых токенов и списания

Не используйте для этих целей реляционные базы данных вроде PostgreSQL, если нагрузка высокая. Запись в диск или даже в shared memory SQL-движка будет узким горлышком. Redis работает в памяти и обрабатывает сотни тысяч операций в секунду на ноде.

Как сообщить клиенту о лимитах?

Техническая реализация важна, но не менее важно UX. Что происходит, когда лимит исчерпан? Возвращать пустой ответ 429 Too Many Requests недостаточно. Клиент должен понять, что делать дальше.

Существует общепринятый стандарт HTTP-заголовков, который стоит внедрять везде:

  • X-RateLimit-Limit: Максимальное количество запросов в текущем окне.
  • X-RateLimit-Remaining: Сколько запросов осталось у пользователя прямо сейчас.
  • X-RateLimit-Reset: Unix timestamp момента, когда квота обновится.

Более современный подход, рекомендованный IETF (Internet Engineering Task Force), использует заголовок RateLimit и Retry-After. Первый содержит структуру данных с лимитом, остатком и временем сброса в одном поле. Второй указывает, сколько секунд ждать перед повторной попыткой. Это снижает количество заголовков в каждом ответе и делает протокол чище.

Не забудьте про документацию. Разработчик вашего API должен видеть эти заголовки в Swagger/OpenAPI спецификации. Иначе они будут писать код, игнорирующий лимиты, и постоянно получать ошибки.

Распределенная система с централизованным хранилищем Redis

Типичные ошибки и ловушки

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

Лимитирование по IP адресу

Это кажется очевидным решением, но оно ломается в реальном мире. Корпоративные клиенты выходят в интернет через один NAT-шлюз. У них 50 сотрудников, и все они используют один IP. Если вы поставите лимит 100 запросов в минуту на IP, вы заблокируете всю компанию. А хакеры, напротив, легко меняют IP с помощью ботов. Лучше идентифицировать клиента по API-ключу или токену пользователя.

Жесткие лимиты без grace period

Если лимит сбрасывается ровно в полночь, то пользователи, активно работавшие в 23:59, могут столкнуться с резкой остановкой. Добавьте небольшой буфер или используйте скользящее окно (sliding window log), которое более плавно реагирует на изменения активности.

Игнорирование веса запросов

Не все запросы равны. GET /status дешев для базы данных. POST /generate-report тяжелый, требует процессора и дисковых операций. Взвешивайте запросы. Тяжелый метод может стоить 5 или 10 единиц лимита, тогда как легкий - всего одну.

Практический чек-лист внедрения

Прежде чем включать рейтлимитинг в проде, проверьте себя по этому списку:

  1. Определите источник истины для идентификации клиента (API Key, JWT, Cookie).
  2. Выберите алгоритм (рекомендую Token Bucket для интерактивных приложений).
  3. Настройте хранилище (Redis Cluster для отказоустойчивости).
  4. Реализуйте возврат заголовков X-RateLimit-* или RateLimit.
  5. Обработайте случай превышения лимита корректным кодом 429 и телом ответа с инструкциями.
  6. Напишите интеграционные тесты, эмулирующие спам-атаку.
  7. Мониторьте метрики: сколько запросов было отклонено? Какой процент трафика попадает под лимит?

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

Что делать, если Redis упал? Блокировать ли весь трафик?

Нет, блокировать трафик из-за недоступности стора лимитов - плохая практика. Используйте стратегию Fail-Open. Если Redis недоступен, считайте, что лимит не достигнут, и пропускайте запросы дальше. Да, на короткое время защита ослабнет, но сервис останется живым. После восстановления соединения начните учет заново или синхронизируйте состояние, если это критично.

Как ограничить анонимных пользователей без регистрации?

Для анонимного трафика комбинируйте IP-адрес и User-Agent. Это создаст уникальный fingerprint для каждого браузера. Однако помните, что этот метод менее надежен, чем авторизация по ключу. Для таких случаев лучше ставить более жесткие лимиты (например, 10 запросов в минуту), так как риск злоупотреблений выше.

Стоит ли использовать Nginx limit_req вместо приложения?

Nginx хорош для грубой защиты от DDoS и фильтрации явно подозрительного трафика на уровне L7. Но он плохо понимает бизнес-логику. Например, он не знает, что запрос к эндпоинту /expensive-data тяжелее, чем к /healthcheck. Используйте Nginx как первый фильтр, а тонкую настройку лимитов делайте внутри приложения или через отдельный сервис-гейткипер.

Как рассчитать оптимальный лимит для нового API?

Начните с анализа целевой аудитории. Если это мобильное приложение, где пользователь листает ленту, ему может потребоваться 5-10 запросов в секунду при активном скроллинге. Заложите запас x2-x3. Для внутренних сервисов ориентируйтесь на пиковую нагрузку в часы пик. Лучше начать с generous лимита (щедрого) и ужесточать его по мере роста нагрузки и понимания паттернов использования.