Rate limiting
Rate limiting
Rate limiting (ограничение частоты запросов) — правило, по которому сервис
пропускает не больше N запросов от одного клиента за единицу времени, а
лишние отклоняет, откладывает или замедляет. Это способ поделить
ограниченную мощность между всеми и не дать одному жадному клиенту положить
систему для остальных.
История
Идея старше интернета в современном виде. Она пришла из телекома, где задача
«не пустить в канал больше, чем он выдержит» стояла с самого начала.
- 1970-е, Ethernet. Роберт Меткалф и Дэвид Боггс в Xerox PARC (статья
1976 года) придумали, что делать, когда два компьютера одновременно
начинают передавать в общий кабель: оба замолкают и ждут случайное время,
а при каждой следующей коллизии вдвое увеличивают диапазон ожидания. Это
binary exponential backoff (двоичная экспоненциальная задержка), главная
стратегия «вежливого клиента», к которой мы ещё вернёмся. - 1986, leaky bucket. Джонатан Тёрнер (Jonathan Turner) описал алгоритм
«дырявого ведра» для сетей с коммутацией пакетов. Воду (пакеты) можно лить
в ведро с любой скоростью, но вытекает она через дырку ровно с постоянной.
Если ведро переполнилось, лишнее выливается через край (пакеты
отбрасываются). - Начало 1990-х, ATM и GCRA. Для сетей ATM (Asynchronous Transfer Mode,
телеком-сеть с ячейками фиксированного размера) ITU-T и ATM Forum
стандартизовали Generic Cell Rate Algorithm (GCRA, обобщённый алгоритм
контроля скорости ячеек). По сути это математически аккуратный leaky
bucket. Примерно тогда же в практику вошёл token bucket (ведро с
жетонами), который до сих пор остаётся самым популярным алгоритмом. - 2000-е, публичные веб-API. Когда у Twitter, Flickr, Google Maps
появились открытые API, rate limiting стал частью бизнес-модели. Лимит
теперь не только защищал сервер, но и отделял бесплатного пользователя от
платного. - 2012, RFC 6585. Марк Ноттингем и Рой Филдинг ввели HTTP-статус
429 Too Many Requests, официальный ответ «ты слишком часто
спрашиваешь». До этого каждый отвечал как придётся: 503, 403, 400, иногда
200 с текстом ошибки внутри. - 2020-е, LLM-API. У OpenAI, Anthropic и Google лимиты считаются уже не
только в запросах, но и в токенах в минуту, раздельно для входа и выхода.
В IETF давно обсуждается черновик стандарта заголовковRateLimit, чтобы
сервер сообщал остаток лимита единообразно. На момент написания, насколько
я знаю, это всё ещё draft (черновик), а не RFC.
Что это такое
Любой сервис конечен: у него фиксированное число процессорных ядер,
соединений с базой, пропускная способность канала, иногда дорогая внешняя
зависимость (платный поставщик данных, LLM за деньги). Если клиенты вместе
просят больше, чем сервис может дать, начинается деградация: ответы
замедляются, очереди растут, таймауты вызывают повторы, повторы создают ещё
больше нагрузки, и система падает целиком. Rate limiting ставит на входе
«турникет» и заранее решает, кому сколько можно.
Лимит всегда состоит из трёх частей:
- Ключ. По какому признаку считать: по IP-адресу, API-ключу, аккаунту,
паре «аккаунт + метод», номеру телефона (для SMS-кодов), всему сервису
сразу. - Норма. Сколько за какое окно: 60 запросов в минуту, 5000 в час,
15 успешных отправок в сутки, 40 000 токенов в минуту. - Реакция на превышение. Отказать сразу (429), поставить в очередь и
выполнить позже, замедлить ответ, выдать капчу, временно забанить.
Полезно различать соседние понятия, их часто путают.
- Rate limit vs quota (квота). Rate limit про скорость: «не больше
10 в секунду». Квота про объём за длинный период: «100 000 запросов в месяц
по тарифу». Можно укладываться в квоту и при этом упираться в rate limit,
если всё отправлять пачкой за минуту. - Rate limit vs concurrency limit (лимит параллельности). Первый считает
запросы за время, второй считает запросы, выполняющиеся одновременно. Один
тяжёлый отчёт на 30 секунд почти не трогает rate limit, но занимает слот
параллельности. - Rate limiting vs throttling (дросселирование). В бытовой речи это
синонимы. В строгом смысле throttling скорее замедляет (очередь,
задержка), а rate limiting режет (отказ). Провайдеры используют слова как
хотят, поэтому смотри на поведение, а не на название. - Rate limiting vs load shedding (сброс нагрузки). Rate limiting ограничивает
конкретного клиента, даже если сервер свободен. Load shedding включается,
когда сервер реально перегружен, и отбрасывает наименее важные запросы
независимо от того, кто их прислал. - Rate limiting vs антифрод и капча. Лимит не пытается понять, «человек
ли это». Он просто считает. Антифрод и капча отвечают на вопрос «кто ты»,
лимит отвечает на вопрос «сколько тебе можно».
Со стороны клиента всё это видно как одна неприятность: запрос, который
вчера проходил, сегодня получил отказ. Поэтому у понятия две половины:
как ставить лимиты (задача владельца сервиса) и как жить с чужими
лимитами (задача любого, кто пишет интеграции, парсеры и ботов).
Аналогии из жизни
Кран с фильтром для воды на кухне. Кувшин-фильтр отдаёт воду не быстрее,
чем она проходит через картридж. Можно налить сверху сразу литр, но в нижнюю
часть он будет капать с постоянной скоростью, а если верхняя часть
переполнится, вода польётся через край. Это ровно 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; точные имена у каждого свои.
Что делает вежливый клиент.
- Смотрит на
Retry-Afterи ждёт ровно столько, если заголовок есть. - Если нет, использует exponential backoff (экспоненциальную задержку):
1 с, 2 с, 4 с, 8 с... с потолком. - Добавляет jitter (случайный разброс): вместо «ровно 4 секунды» ждёт
случайное время от 0 до 4. Марк Брукер из AWS в 2015 году показал в
статье «Exponential Backoff And Jitter», что без разброса тысячи клиентов
просыпаются одновременно и снова бьют в сервер хором. - Ограничивает число попыток и честно падает с понятной ошибкой.
- Кэширует ответы, чтобы не спрашивать одно и то же дважды.
- Сам себе ставит лимит ниже серверного (client-side throttling), чтобы не
доходить до отказов вообще.
Лимит — это часть контракта API, а не ошибка
Отказ по лимиту нельзя лечить «ещё одним повтором прямо сейчас». Каждый немедленный повтор сдвигает окно и продлевает блокировку. Правильная реакция всегда одна: замедлиться и подождать. Некоторые сервисы за настойчивость ещё и увеличивают штрафную паузу, иногда до десятков минут.
Распределённый случай. Если у сервиса 20 серверов за балансировщиком,
счётчик должен быть общим, иначе каждый сервер пропустит свою норму и
реальный лимит станет в 20 раз больше. Обычно состояние держат в Redis
(атомарные INCR или Lua-скрипт для token bucket). Альтернатива —
приблизительный лимит: каждый узел получает долю нормы и не ходит в общее
хранилище на каждом запросе. Это быстрее, но менее точно.
Где встречается в обычной жизни
- «Отправить код повторно через 59 секунд». Когда входишь в Госуслуги,
банк или маркетплейс по SMS, кнопка повторной отправки заблокирована. Это
rate limit по номеру телефона: SMS стоят денег, а без лимита злоумышленник
может заваливать чужой номер кодами (так называемый SMS-бомбинг). - Экран блокировки телефона. После нескольких неверных PIN-кодов iPhone
предлагает подождать минуту, потом 5, 15, час. Это экспоненциальный
backoff, только применённый к тебе, чтобы перебор паролей занял годы. - Три попытки PIN у банковской карты. Лимит с «вечным» баном: после
третьей ошибки карту блокируют до обращения в банк. - Лимиты на переводы и снятие наличных. Суточный лимит по карте — та же
идея: ограничение объёма за окно, чтобы украденная карта не опустошила
счёт за минуту. - «Слишком много попыток, попробуйте позже» на сайте. Когда форма
обратной связи или квиз на сайте перестаёт принимать заявки после
нескольких отправок, внутри почти всегда лимит по IP или по устройству.
Где встречается в IT и бизнесе
- Защита от перегрузки и злоупотреблений. Нужно, когда у сервиса есть
публичный вход: логин (защита от перебора паролей), регистрация, формы,
поиск. Без лимита один бот съедает всё. - Тарифы и монетизация API. Нужно, когда API — продукт. Бесплатный план
60 запросов в минуту, платный 1000, корпоративный по договору. Лимит здесь
прямо превращается в прайс-лист. - Защита дорогих зависимостей. Нужно, когда за твоим сервисом стоит
платный поставщик: SMS-шлюз, LLM, геокодер, выгрузка из
агрегатора недвижимости. Внутренний лимит защищает бюджет от бага, который
в цикле зовёт платный метод. - Вежливый сбор данных и интеграции. Нужно, когда ты клиент: парсер,
синхронизация CRM, рассылка через API почтового сервиса, Telegram-бот.
Здесь задача обратная: не получить бан, спланировать объём работы на
день, правильно обработать 429. - Честное распределение внутри компании. Нужно, когда один внутренний
сервис используют несколько команд. Лимит на команду не даёт ночному
аналитическому скрипту уронить продакшн.
Не все сервисы отвечают честным 429
Сплошь и рядом лимит маскируется под другие коды: 403 Forbidden («нельзя»),
401 Unauthorized («плохой ключ»), 503 или даже 200 с ошибкой в теле. Если
скрипт реагирует на 401 тем, что «перевыпускает токен», а на 403 — «ключ
забанен, всё пропало», можно долго чинить не то. Всегда читай тело ответа
и логируй его: там часто прямо написано rate_limit_exceeded.
Кто пользуется
По сути все, у кого есть публичный API. Несколько показательных примеров
(цифры по публичной документации, актуальные на момент, который я знаю;
лимиты часто меняются, проверяй у первоисточника):
- GitHub REST API. 5000 запросов в час для авторизованного пользователя,
60 в час для анонимного. Остаток сообщается в заголовках
X-RateLimit-*в каждом ответе. - Telegram Bot API. Ориентиры из FAQ: не больше примерно одного
сообщения в секунду в один чат, около 20 в минуту в группу и около 30
сообщений в секунду суммарно. При превышении приходит ошибка 429 с полем
retry_after. Для рассылок по большой базе это ключевое ограничение. - Stripe. В 2017 году инженер Stripe Пол Тарджан опубликовал в блоге
компании статью «Scaling your API with rate limiters» о четырёх уровнях
защиты: лимит частоты запросов, лимит параллельных запросов и два
механизма load shedding для перегрузки всего флота серверов. По умолчанию
у Stripe, по документации, порядка 100 операций в секунду в боевом режиме. - Anthropic, OpenAI и другие LLM-API. Лимиты по уровням (tiers), которые
растут с объёмом оплат: запросы в минуту, входные токены в минуту,
выходные токены в минуту. Ответ при превышении 429 и заголовок
retry-after. - Twitter/X, 2023. Показательный бизнес-пример. В июле 2023 года
компания ввела дневные лимиты на чтение постов (стартовые цифры были
порядка 600 постов в день для непроверенных аккаунтов и 6000 для
верифицированных, потом их поднимали) как ответ на массовый сбор данных для
обучения ИИ. Лимит, который пользователь видит лично, вызвал волну
недовольства: хорошая иллюстрация, что это ещё и продуктовое решение. - Cloudflare, nginx, AWS API Gateway. Rate limiting у них — готовая
функция на уровне инфраструктуры, до кода приложения запрос даже не
доходит.
Альтернативы и конкуренты
Это не «вместо», а скорее соседние инструменты, которые решают похожую
задачу иначе. В реальных системах их комбинируют.
- Очередь с backpressure (обратным давлением).
Плюс: ничего не теряется, запросы просто ждут своей очереди.
Минус: ожидание растёт без границ, пользователь видит «вечную загрузку»
вместо честного отказа. - Автомасштабирование.
Плюс: вместо отказа добавляем серверы, клиент не страдает.
Минус: деньги растут линейно с нагрузкой, а бот может сжечь бюджет; не
масштабирует внешние зависимости и базу. - Кэширование.
Плюс: самый дешёвый способ снять повторяющуюся нагрузку, сервер вообще не
работает на повторных запросах.
Минус: не помогает с уникальными запросами и записью, есть риск отдать
устаревшие данные. - Капча и антибот-проверки.
Плюс: отсекают автоматику, не мешая человеку делать много действий.
Минус: раздражают людей, ухудшают конверсию форм, современные боты их всё
равно решают за копейки.
Когда НЕ стоит использовать
- Внутри доверенного конвейера с жёсткими сроками. Если один твой
сервис синхронно зовёт другой, и оба под твоим контролем, лимит между ними
часто просто добавляет отказы. Потому что здесь лучше работают
лимит параллельности, таймауты и circuit breaker: они реагируют на
реальное состояние системы, а не на абстрактную норму. - Лимит по IP для аудитории за общим NAT. Потому что офис, университет,
мобильный оператор или корпоративный VPN выводит тысячи людей через один
адрес, и ты заблокируешь их всех из-за одного активного. Для
авторизованных пользователей лимитируй по аккаунту или ключу. - Как единственная защита от целенаправленной атаки. Потому что
распределённая атака идёт с тысяч адресов, каждый из которых укладывается в
норму. Против DDoS нужны фильтрация на уровне сети и специализированные
сервисы, а rate limiting — лишь один слой.
Чек-лист для любого скрипта, который ходит в чужой API
Заранее найди документацию по лимитам. Поставь свой лимит ниже чужого.
Обрабатывай 429 (и подозрительные 401/403 с текстом про лимит) через
ожидание по Retry-After, иначе экспоненциальный backoff с jitter и
потолком попыток. Кэшируй то, что не меняется. Логируй тело ответа при
каждой ошибке. И планируй объём работы на сутки от лимита, а не от
желания.
Связанные понятия
- Backpressure (обратное давление) — механизм, при котором медленный
потребитель сигнализирует быстрому производителю «притормози». - Circuit breaker (автоматический выключатель) — клиентский паттерн:
после серии ошибок перестать звать сервис на время, чтобы дать ему
подняться. - Exponential backoff with jitter — стратегия повторов с растущей
случайной паузой, стандарт вежливого клиента. - Load shedding (сброс нагрузки) — сознательный отказ части запросов
при перегрузке, чтобы остальные обслужились нормально. - Идемпотентность — свойство операции, которую безопасно повторить; без
неё повтор после отказа может, например, создать заказ дважды. - Thundering herd (эффект стада) — ситуация, когда много клиентов
одновременно просыпаются и бьют в сервис, частый результат повторов без
jitter.
Литература и источники
- RFC 6585, Additional HTTP Status Codes (Nottingham, Fielding, 2012,
en) — где введён статус 429.
https://datatracker.ietf.org/doc/html/rfc6585 - Документация nginx, модуль ngx_http_limit_req_module (en/ru) —
практичное описание leaky bucket в боевом веб-сервере.
https://nginx.org/ru/docs/http/ngx_http_limit_req_module.html - Google SRE Book, главы «Handling Overload» и «Addressing Cascading
Failures» (Beyer и др., O'Reilly, 2016, en) — как Google думает о
перегрузке, квотах и повторах. Бесплатно на https://sre.google/sre-book/ - Alex Xu, «System Design Interview», том 1, глава «Design a Rate Limiter»
(2020, en; есть русский перевод «System Design. Подготовка к сложному
интервью») — понятный разбор всех алгоритмов с картинками. - Marc Brooker, «Exponential Backoff And Jitter» (AWS Architecture Blog,
2015, en) — классика про повторы. Искать в Google по названию статьи. - Wikipedia: Rate limiting и Token bucket (en) —
https://en.wikipedia.org/wiki/Rate_limiting,
https://en.wikipedia.org/wiki/Token_bucket
Где встретилось у меня
Вчера лимиты всплыли сразу в двух местах. Проверка акций застройщиков через
внешний MCP-сервис с данными агрегатора упиралась в «лимит запросов»:
сервер отвечал 401 с текстом rate_limit_exceeded, скрипт ждал по 300 секунд
и в итоге сдался; пришлось добавлять кэш и ожидание, а прогон закрыли
запасным способом. Параллельно разбирали, что отказы 403 от сервиса квизов
на сайтах — это суточный лимит успешных отправок с одного адреса, а не
поломка, и под него подстраивали дневные потолки и уведомление в Telegram.
Краткое резюме
- Rate limiting — это «не больше N за время T на ключ», способ честно
поделить конечную мощность и защитить сервис и бюджет. - Основные алгоритмы: fixed window (просто, но дырявое на стыках), sliding
window (точнее), token bucket (средняя скорость плюс допустимый всплеск,
самый популярный), leaky bucket/GCRA (ровный выход). - Стандартный ответ — HTTP 429 с заголовком
Retry-After, но в жизни лимит
нередко прячется за 401, 403 или 503, поэтому читай тело ответа. - Вежливый клиент ждёт по
Retry-After, использует экспоненциальный backoff
с jitter, кэширует и ставит себе лимит ниже серверного. - Лимит — одновременно техническая защита и бизнес-инструмент: на нём
строятся тарифы API и продуктовые решения вроде лимитов чтения в X.