сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · FastAPI, Django и API

Rate limiting в API

Rate limiting: защита частотой

Ограничение частоты - то, что отделяет сервис, переживший всплеск и абьюз, от лежащего. Собес проверяет, знаешь ли ты алгоритмы (token bucket против sliding window), правильный код ответа и главную ловушку - локальные счётчики на нескольких инстансах.

Типовые формулировки: «как ограничить частоту запросов?», «какой код вернуть при превышении?», «как считать лимит на нескольких серверах».

Зачем, код ответа и на что вешать

Rate limiting ограничивает число запросов за окно: защита от перегрузки и DDoS, пресечение абьюза (скрейпинг, перебор паролей) и справедливость между клиентами. Превышение это 429 Too Many Requests с заголовком Retry-After, чтобы клиент знал, когда повторить; 403 или 503 вместо 429 оставляют его в неведении.

Лимит вешают на ИЗМЕРЕНИЕ клиента - API-ключ, пользователь, IP, а не в общий котёл на всех. Логин лимитируют строже (brute-force), а дорогие запросы считают по стоимости (cost-based), а не поштучно: один тяжёлый запрос способен незаметно исчерпать ресурс, пока счётчик показывает единицу.

// Базовый лимит удобно поставить на gateway или nginx, не доводя нагрузку до приложения.

429
статус превышения лимита частоты запросов
cost-based
квота по весу запроса, а не по числу

Алгоритмы и общий счётчик

Алгоритмы. Token bucket: токены капают с постоянной скоростью, запрос тратит токен, допускается всплеск до ёмкости корзины - удобно, когда короткие пики норма. Leaky bucket даёт ровный выходной поток. Fixed window против sliding window: у fixed есть неприятность - всплеск на стыке окон (в конце одного и начале следующего пролезает двойная порция), sliding window его сглаживает.

Главная ловушка масштаба: в нескольких инстансах ЛОКАЛЬНЫЕ счётчики множатся на их число - реальный лимит становится в N раз больше задуманного. Нужен ОБЩИЙ стор (Redis) с атомарным инкрементом, чтобы счётчик был один на всех.

// Клиент со своей стороны должен уважать Retry-After и делать экспоненциальный backoff, а не долбить сразу.

token bucket
токены пополняются, запрос тратит; допускает всплеск
sliding window
скользящее окно, сглаживает граничный всплеск

Как отвечать: «Как считать rate limit на нескольких инстансах?»

Через общий стор, обычно Redis с атомарным инкрементом. Если каждый инстанс считает у себя в памяти, счётчики независимы и реальный лимит множится на число инстансов - задумал 100 запросов в минуту, а при пяти подах получил 500. Поэтому счётчик должен быть один на всех: инстансы инкрементируют ключ в Redis атомарно, скажем через INCR с TTL или скрипт, реализующий token bucket. Ключом беру измерение клиента - API-ключ или пользователя. При превышении отвечаю 429 с Retry-After. Базовый грубый лимит можно ещё и на gateway повесить, чтобы не пускать флуд до приложения.

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

На чём валят

  • Считать все запросы поштучно: дорогой запрос исчерпывает ресурс незаметно - нужен cost-based учёт.
  • Локальные счётчики в нескольких инстансах: реальный лимит множится на их число - нужен общий Redis.
  • Отвечать 403/503 вместо 429 или не давать Retry-After - клиент не знает, когда повторить.
  • Fixed window без сглаживания: на стыке окон пролезает двойная порция запросов.

Проверьте себя

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

  1. #rate_limiting1 / 5
    Клиент превысил лимит. Какой статус и заголовок ему вернуть?
    A)400 Bad Request — запрос сверх лимита следует считать некорректным по форме
    B)403 Forbidden — у клиента просто нет прав слать столько запросов подряд
    C)429 Too Many Requests с Retry-After — когда можно повторить
    D)503 Service Unavailable — сервер ведь фактически отказал в обслуживании запроса
    показать ответ и разбор
    +C)429 Too Many Requests с Retry-After — когда можно повторить

    // разбор: Стандартный ответ на превышение лимита — 429 Too Many Requests. Заголовок Retry-After (секунды или дата) подсказывает клиенту, когда безопасно повторить, чтобы он не долбил сервер вслепую. Часто добавляют X-RateLimit-Limit/Remaining/Reset для прозрачности остатка. Хорошо ведущий себя клиент читает эти заголовки и делает backoff.

  2. #rate_limiting2 / 5
    Как работает алгоритм token bucket?
    A)Токены раздаются поровну всем клиентам сразу в начале каждой новой минуты
    B)Токены капают с фиксированной скоростью, запрос тратит токен; пусто — отказ
    C)Каждый запрос кладёт токен в ведро, и при переполнении сервер начинает отказывать
    D)Ведро жёстко пропускает ровно один запрос в секунду без всякого запаса на всплеск
    показать ответ и разбор
    +B)Токены капают с фиксированной скоростью, запрос тратит токен; пусто — отказ

    // разбор: Token bucket: в «ведро» с фиксированной ёмкостью токены добавляются с постоянной скоростью. Каждый запрос забирает токен; нет токенов — запрос отклоняют или ставят в ожидание. Накопленные токены позволяют кратковременный всплеск (burst) до ёмкости ведра, при этом средняя скорость держится на заданном уровне. Популярный и гибкий алгоритм.

  3. #rate_limiting3 / 5
    Чем фиксированное окно (fixed window) хуже скользящего (sliding window) для лимитирования?
    A)На стыке двух окон возможен двойной всплеск — граница обнуляет счётчик
    B)Фиксированное окно вообще не способно ограничивать частоту запросов в принципе
    C)Скользящее окно пропускает больше запросов, чем фиксированное, при тех же настройках
    D)Фиксированное окно требует заметно больше памяти на хранение счётчиков запросов
    показать ответ и разбор
    +A)На стыке двух окон возможен двойной всплеск — граница обнуляет счётчик

    // разбор: Fixed window считает запросы в жёстких интервалах (например, за календарную минуту) и обнуляет счётчик на границе. Клиент может выдать лимит в конце одного окна и сразу лимит в начале следующего — до 2× за короткий промежуток вокруг стыка. Sliding window (скользящее) учитывает недавнюю историю и сглаживает этот всплеск ценой чуть большей сложности/памяти.

  4. #rate_limiting4 / 5
    Сервис работает в 4 инстанса, лимит считают в памяти каждого. Почему реальный лимит вчетверо больше?
    A)Инстансы дублируют каждый запрос между собой, и счётчик срабатывает четырежды
    B)Это нормально и ожидаемо: суммарный лимит и должен расти пропорционально инстансам
    C)Балансировщик умножает лимит на число инстансов намеренно, ради отказоустойчивости
    D)У каждого инстанса свой счётчик — нужен общий стор (Redis) на все инстансы
    показать ответ и разбор
    +D)У каждого инстанса свой счётчик — нужен общий стор (Redis) на все инстансы

    // разбор: Локальный счётчик видит только свои запросы. Балансировщик размазывает трафик по 4 инстансам, каждый независимо разрешает «лимит» — суммарно до 4× задуманного. Для глобального лимита счётчики выносят в общее хранилище (Redis с атомарными инкрементами/скриптами) либо применяют распределённые алгоритмы. Иначе гарантия лимита ломается при масштабировании.

  5. #rate_limiting5 / 5
    По какому ключу обычно считают лимит?
    A)Один общий счётчик на всех сразу — так проще всего реализовать ограничение
    B)По случайному идентификатору, который сервер генерирует на каждый новый запрос
    C)По измерению клиента: API-ключ, пользователь или IP — чтобы изолировать их
    D)По номеру эндпоинта без учёта того, кто именно шлёт эти запросы к нему
    показать ответ и разбор
    +C)По измерению клиента: API-ключ, пользователь или IP — чтобы изолировать их

    // разбор: Лимит навешивают на измерение, идентифицирующее источник: API-ключ, аутентифицированного пользователя, IP (для анонимных). Так один агрессивный клиент не исчерпывает квоту для всех. Часто комбинируют уровни: глобальный предохранитель сервиса + персональные лимиты + отдельные лимиты на дорогие эндпоинты. Ключ выбирают так, чтобы его нельзя было дёшево подменять.

дальше

Теорию прочитали. Навык ставится повторением

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