Классический system design — спроектировать rate limiter для защиты API. Token bucket, Redis-backed, atomic Lua. Пилот #2 нового /cases формата.
Функциональные:
429 Too Many Requests при превышении лимитаНефункциональные:
Client → API Gateway → Rate Limiter → Redis (token bucket счётчики).
При allow: Gateway перенаправляет запрос в API Backend, ответ возвращается клиенту. При reject: Gateway немедленно возвращает 429 — Backend не вызывается.
ADR'ы объясняют: выбор алгоритма (ADR-001 на Rate Limiter), место применения (ADR-002 на API Gateway), атомарность операций (ADR-003 на Redis).
Запрос в пределах лимита. Token bucket не заполнен — проходит насквозь:
Запрос отклонён. Лимит превышен — 429, Backend не получает запрос:
Каждый пользователь — «ведро» ёмкостью N токенов (burst limit). На каждый запрос атомарно через Lua:
local count = redis.call('INCR', KEYS[1])
if count == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1]) -- TTL = окно в секундах
end
return count
Если count > limit → 429. Иначе — пропускаем запрос и считаем токен потраченным.
Заголовки ответа при 429:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Retry-After: 14
Когда это ломается:
Hot keys (один пользователь, много RPS) → один Redis-шард под нагрузкой. Решение: tier-2 счётчик в памяти каждого gateway-инстанса + периодический flush в Redis (eventual consistency на счётчиках допустима — небольшой overshoot лучше, чем hotspot).
Redis недоступен → fail-open или fail-closed? Решение: fail-open (пропускать запросы, логировать алерты). DDoS лучше пережить с алертами, чем уронить весь продукт из-за недоступного Redis.
Несколько инстансов gateway → локальные счётчики расходятся.
Решение: client-IP hashing на уровне LB перед Gateway (sticky routing) или Redis Cluster со slot-based keying по user_id.