Rate limiting

12 сентября 2026 · ~16 мин чтения

концепция api сеть надёжность http

Rate limiting

Rate limiting (ограничение частоты запросов) — правило, по которому сервис
пропускает не больше N запросов от одного клиента за единицу времени, а
лишние отклоняет, откладывает или замедляет. Это способ поделить
ограниченную мощность между всеми и не дать одному жадному клиенту положить
систему для остальных.

История

Идея старше интернета в современном виде. Она пришла из телекома, где задача
«не пустить в канал больше, чем он выдержит» стояла с самого начала.

Что это такое

Любой сервис конечен: у него фиксированное число процессорных ядер,
соединений с базой, пропускная способность канала, иногда дорогая внешняя
зависимость (платный поставщик данных, LLM за деньги). Если клиенты вместе
просят больше, чем сервис может дать, начинается деградация: ответы
замедляются, очереди растут, таймауты вызывают повторы, повторы создают ещё
больше нагрузки, и система падает целиком. Rate limiting ставит на входе
«турникет» и заранее решает, кому сколько можно.

Лимит всегда состоит из трёх частей:

  1. Ключ. По какому признаку считать: по IP-адресу, API-ключу, аккаунту,
    паре «аккаунт + метод», номеру телефона (для SMS-кодов), всему сервису
    сразу.
  2. Норма. Сколько за какое окно: 60 запросов в минуту, 5000 в час,
    15 успешных отправок в сутки, 40 000 токенов в минуту.
  3. Реакция на превышение. Отказать сразу (429), поставить в очередь и
    выполнить позже, замедлить ответ, выдать капчу, временно забанить.

Полезно различать соседние понятия, их часто путают.

Со стороны клиента всё это видно как одна неприятность: запрос, который
вчера проходил, сегодня получил отказ. Поэтому у понятия две половины:
как ставить лимиты (задача владельца сервиса) и как жить с чужими
лимитами (задача любого, кто пишет интеграции, парсеры и ботов).

Аналогии из жизни

Кран с фильтром для воды на кухне. Кувшин-фильтр отдаёт воду не быстрее,
чем она проходит через картридж. Можно налить сверху сразу литр, но в нижнюю
часть он будет капать с постоянной скоростью, а если верхняя часть
переполнится, вода польётся через край. Это ровно leaky bucket: вход любой,
выход ровный, излишек теряется.
Где ломается: у фильтра нет понятия «клиентов». Реальный лимитер ведёт
отдельное ведро на каждого, и твоё переполнение не мешает соседу. Кроме
того, фильтр никогда не «отвечает» тебе, почему вода не идёт и когда
попробовать снова, а хороший API обязан это делать (заголовок
Retry-After).

Абонемент в бассейн и турникет. Представь абонемент в бассейн:
каждый месяц на карту кладут 8 посещений, неиспользованные сгорают, а больше
двух раз в день турникет не пустит. Это token bucket плюс квота: жетоны
пополняются с определённой скоростью, ведро имеет потолок, каждое
действие тратит жетон.
Где ломается: в бассейне пополнение раз в месяц и дискретное. В
программном token bucket жетоны капают непрерывно, условно по одному каждые
100 мс, поэтому «накопить» можно только до размера ведра, и после отказа
через секунду уже снова можно. И ещё: у турникета нет ложных срабатываний от
чужих людей, а лимит по IP может задеть тебя из-за соседа по NAT (например,
весь офис или мобильный оператор выходит в сеть с одного адреса).

Светофор на въезде на шоссе (ramp metering). В ряде городов США и Европы
на съездах к загруженным магистралям стоят светофоры, которые пускают по одной
машине каждые несколько секунд. Шоссе не встаёт в пробку, потому что поток
на входе дозирован. Это хорошо объясняет главную мысль: лимит нужен не чтобы
наказать, а чтобы вся система продолжала ехать.
Где ломается: на съезде все ждут в одинаковой очереди, а в API у разных
клиентов разные нормы (бесплатный тариф, платный, внутренний сервис). И
водитель видит светофор, а клиент API часто вообще не знает правил, пока не
получит первый отказ. Нередко лимиты не документированы вовсе, их
приходится выяснять опытным путём.

Как это работает

Все алгоритмы решают одну задачу: хранить для каждого ключа немного
состояния и по нему быстро решать «пустить или нет». Разберём основные пять.

1. Fixed window (фиксированное окно). Держим счётчик на текущую минуту:
key:2026-09-11T12:43. Каждый запрос делает +1. Если счётчик больше
лимита, отказ. В новой минуте счётчик новый.

if INCR(key:minute) > 100: reject
EXPIRE(key:minute, 60)

Плюс: предельно просто, один INCR в Redis. Минус: на стыке окон можно
сделать двойную норму. 100 запросов в 12:43:59 и ещё 100 в 12:44:00, то есть
200 за секунду при лимите 100 в минуту.

2. Sliding log (скользящий журнал). Храним время каждого запроса за
последнюю минуту, на новом запросе выкидываем старые и считаем остаток.
Точно, но дорого по памяти: при лимите 10 000 в час это 10 000 меток на
клиента.

3. Sliding window counter (скользящее окно на двух счётчиках). Компромисс:
держим счётчик текущей и предыдущей минуты и считаем взвешенно.

оценка = prev_count * (1 - доля_прошедшей_минуты) + curr_count

Если прошло 25% текущей минуты, берём 75% прошлой минуты плюс всю текущую.
Cloudflare в 2017 году описывал, что строит свой rate limiting именно так, и
по их замерам ошибка приближения составила тысячные доли процента запросов.

4. Token bucket (ведро с жетонами). Самый популярный. У каждого ключа
есть ведро ёмкостью B жетонов, жетоны прибывают со скоростью r в
секунду. Каждый запрос забирает жетон, если его нет, отказ. Хитрость в том,
что жетоны не нужно реально «доливать» таймером: достаточно хранить два числа
и пересчитывать при обращении.

now = time()
tokens = min(B, tokens + (now - last) * r)
last = now
if tokens >= 1: tokens -= 1; allow
else: reject, retry_after = (1 - tokens) / r

Смысл двух параметров: r задаёт среднюю скорость, B задаёт допустимый
всплеск (burst). Клиент, который долго молчал, может сразу сделать B
запросов, а дальше живёт на средней скорости. Именно так, по документации,
считают лимиты в API Anthropic.

5. Leaky bucket / GCRA. Запросы встают в очередь и обрабатываются с
постоянной скоростью. Это даёт идеально ровный выход, что важно, когда за
тобой хрупкая система (старая база, внешний провайдер с жёстким лимитом).
nginx в модуле limit_req реализует именно этот подход:

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
    limit_req zone=api burst=20 nodelay;
}

Здесь 10m — это 10 МБ разделяемой памяти под счётчики (по документации
nginx, один мегабайт вмещает около 16 тысяч состояний), burst=20 — сколько
запросов можно принять сверх нормы, а nodelay означает «принять всплеск
сразу, а не растягивать его по времени».

Что происходит при превышении: протокол. Грамотный сервер отвечает так:

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

Retry-After (через сколько секунд повторить, или конкретная дата) —
стандартный заголовок HTTP. X-RateLimit-* — де-факто соглашение, которое
популяризовали GitHub и Twitter; точные имена у каждого свои.

Что делает вежливый клиент.

  1. Смотрит на Retry-After и ждёт ровно столько, если заголовок есть.
  2. Если нет, использует exponential backoff (экспоненциальную задержку):
    1 с, 2 с, 4 с, 8 с... с потолком.
  3. Добавляет jitter (случайный разброс): вместо «ровно 4 секунды» ждёт
    случайное время от 0 до 4. Марк Брукер из AWS в 2015 году показал в
    статье «Exponential Backoff And Jitter», что без разброса тысячи клиентов
    просыпаются одновременно и снова бьют в сервер хором.
  4. Ограничивает число попыток и честно падает с понятной ошибкой.
  5. Кэширует ответы, чтобы не спрашивать одно и то же дважды.
  6. Сам себе ставит лимит ниже серверного (client-side throttling), чтобы не
    доходить до отказов вообще.

Лимит — это часть контракта API, а не ошибка

Отказ по лимиту нельзя лечить «ещё одним повтором прямо сейчас». Каждый немедленный повтор сдвигает окно и продлевает блокировку. Правильная реакция всегда одна: замедлиться и подождать. Некоторые сервисы за настойчивость ещё и увеличивают штрафную паузу, иногда до десятков минут.

Распределённый случай. Если у сервиса 20 серверов за балансировщиком,
счётчик должен быть общим, иначе каждый сервер пропустит свою норму и
реальный лимит станет в 20 раз больше. Обычно состояние держат в Redis
(атомарные INCR или Lua-скрипт для token bucket). Альтернатива —
приблизительный лимит: каждый узел получает долю нормы и не ходит в общее
хранилище на каждом запросе. Это быстрее, но менее точно.

Где встречается в обычной жизни

Где встречается в IT и бизнесе

Не все сервисы отвечают честным 429

Сплошь и рядом лимит маскируется под другие коды: 403 Forbidden («нельзя»), 401 Unauthorized («плохой ключ»), 503 или даже 200 с ошибкой в теле. Если скрипт реагирует на 401 тем, что «перевыпускает токен», а на 403 — «ключ забанен, всё пропало», можно долго чинить не то. Всегда читай тело ответа и логируй его: там часто прямо написано rate_limit_exceeded.

Кто пользуется

По сути все, у кого есть публичный API. Несколько показательных примеров
(цифры по публичной документации, актуальные на момент, который я знаю;
лимиты часто меняются, проверяй у первоисточника):

Альтернативы и конкуренты

Это не «вместо», а скорее соседние инструменты, которые решают похожую
задачу иначе. В реальных системах их комбинируют.

Когда НЕ стоит использовать

Чек-лист для любого скрипта, который ходит в чужой API

Заранее найди документацию по лимитам. Поставь свой лимит ниже чужого. Обрабатывай 429 (и подозрительные 401/403 с текстом про лимит) через ожидание по Retry-After, иначе экспоненциальный backoff с jitter и потолком попыток. Кэшируй то, что не меняется. Логируй тело ответа при каждой ошибке. И планируй объём работы на сутки от лимита, а не от желания.

Связанные понятия

Литература и источники

Где встретилось у меня

Вчера лимиты всплыли сразу в двух местах. Проверка акций застройщиков через
внешний MCP-сервис с данными агрегатора упиралась в «лимит запросов»:
сервер отвечал 401 с текстом rate_limit_exceeded, скрипт ждал по 300 секунд
и в итоге сдался; пришлось добавлять кэш и ожидание, а прогон закрыли
запасным способом. Параллельно разбирали, что отказы 403 от сервиса квизов
на сайтах — это суточный лимит успешных отправок с одного адреса, а не
поломка, и под него подстраивали дневные потолки и уведомление в Telegram.

Краткое резюме