Rate Limits и контроль нагрузки
TL;DR
Надежный контур — это комбинация лимитов и квот на нескольких уровнях (edge→BFF→сервис), справедливое распределение ресурсов (per-tenant/ключ/роут), SLO-адаптивный троттлинг и бэкпрешер вместо молчаливых тайм-аутов. Используйте token/leaky bucket для «скорости», скользящее окно для бухгалтерских квот, конкурентные лимиты для тяжелых операций, dynamic throttling при деградации и circuit-breaker к хрупким апстримам. Все — под наблюдаемостью и с плейбуками.
1) Зачем лимиты в iGaming/fintech
SLO и устойчивость: защита от лавин ретраев, пиков турниров/эвентов, всплесков платежей.
Справедливость: один тенант или партнер не «высасывает» весь бюджет.
Антиабьюз/боты: дорилимитинг логина/регистрации, спама, скрейпинга каталогов.
Стоимость: сдерживание дорогих вызовов (KYC, отчеты, агрегации).
Комплаенс/добросовестное использование: формальные «fair use» квоты в договорах.
2) Таксономия лимитов
3) Алгоритмы и где применять
3.1 Token Bucket (по умолчанию)
Параметры: `rate` (токенов/сек), `burst` (макс запас).
Отлично для API read, оплаты/статусов, BFF.
При пустом бакете → 429 + `Retry-After`.
3.2 Leaky Bucket (усреднение)
Гарантированный «снос» RPS, полезно для вебхуков, чтобы не забить воркеры.
3.3 Fixed Window vs Sliding Window
Fixed — прост, но «границы»; Sliding — честный учет в окне (мин/час/сутки).
Применяйте Sliding для договорных квот.
3.4 Concurrent Limits
Лимит одновременно активных задач. Идеально для экспортов/репортов, KYC-пакетов, переобработок.
При нехватке — 429/503 + очередь/поллинг.
3.5 Cost/Complexity Limiter
GraphQL/поиск: считаем «стоимость» по глубине/кардинальности/расширениям.
Отсечение/деградация «дорогих» запросов, ответ с подсказкой.
4) Ключи лимитирования (dimensioning)
per-tenant (мультиаренда, справедливость),
per-api_key/client_id (партнеры),
per-route (критичные мутации жестче),
per-user/device/IP/ASN/geo (антибот/антискрейп),
per-BIN/country (платежные методы, защита эмитентов и провайдеров),
per-method (GET мягче, POST/PUT строже).
Композиция: основной ключ + «мультипликатор риска» (новый аккаунт, TOR/прокси, высокий chargeback-риск).
5) SLO-адаптивный троттлинг
Включайте dynamic throttling, когда SLO в опасности:- Триггеры: `p95 latency↑`, `5xx↑`, `queue len↑`, `CPU/IO saturation`.
- Действия: понизить rate/burst, включить outlier-ejection, урезать «дорогие» роуты, временный degrade (без тяжелых полей/агрегаций).
- Возврат: ступенчато (25→50→100%) при нормализации сигналов N интервалов подряд.
6) Интеграция в архитектуру
API Gateway (edge): первичные rate/quotas, гео/ASN, HMAC/ JWT-валидация, 429/`Retry-After`.
BFF/Service Mesh: тонкие per-route/per-tenant лимиты, concurrent-limits, circuit-breakers к апстримам.
Внутри сервиса: семафоры на тяжелые операции, бэкпрешер на очередях, «рабочие пулы» с bound размером.
Вебхуки: отдельный ingress-эндпоинт с leaky bucket и буфером ретраев.
7) Конфигурации (фрагменты)
Kong / NGINX-style (rate + burst):yaml plugins:
- name: rate-limiting config:
policy: local minute: 600 # 10 rps limit_by: consumer fault_tolerant: true
- name: response-ratelimiting config:
limits:
heavy: { minute: 60 }
Envoy (circuit + outlier + rate):
yaml circuit_breakers:
thresholds: { max_connections: 1000, max_requests: 800 }
outlier_detection:
consecutive_5xx: 5 interval: 5s base_ejection_time: 30s http_filters:
- name: envoy. filters. http. local_ratelimit typed_config:
token_bucket: { max_tokens: 100, tokens_per_fill: 100, fill_interval: 1s }
filter_enabled: { default_value: 100% }
filter_enforced: { default_value: 100% }
Concurrent-limits (псевдо):
pseudo sema = Semaphore(MAX_ACTIVE_EXPORTS_PER_TENANT)
if! sema. tryAcquire(timeout=100ms) then return 429 with retry_after=rand(1..5)s process()
sema. release()
GraphQL cost guard (идея):
pseudo cost = sum(weight(field) cardinality(arg))
if cost > tenant. budget then reject(429,"query too expensive")
8) Политики для разных каналов
REST
GET — мягче, POST/PATCH/DELETE — строже; «идемпотентные» статусы/проверки можно ретраить.
Для платежей: лимиты на `auth/capture/refund` per-user/tenant/BIN/страна.
GraphQL
Depth/complexity caps, persisted/whitelisted queries, лимиты на «алиасы».
WebSocket/SSE
Лимит частоты `subscribe/unsubscribe`, кап на количество топиков, контроль размера событий и send-queue → при переполнении `policy_disconnect`.
Вебхуки
Leaky bucket на приеме, per-sender квоты, dead-letter очередь, детерминированные 2xx/429.
9) Обратная связь клиентам
Всегда возвращайте четкий 429 с заголовками:- `Retry-After:
` - `X-RateLimit-Limit/Remaining/Reset`
- Для квот — 403 с кодом `quota_exceeded` и ссылкой на апгрейд плана.
- Документация: лимиты в OpenAPI/SDL + страницы «Fair Use».
10) Мониторинг и дашборды
Метрики:- Хиты лимитов: `rate.limit.hit` по ключам/роутам/тенантам.
- 429/503 доля, latency p50/p95/p99, error rate, queue length, open circuits.
- Fair-share: топ-тенанты по потреблению, «bully detector».
- Вебхуки: прием/ретраи, drop-rate, средний лаг.
- 429 не более 1–3% от общего RPS (без ботов).
- p95 добавка лимитера ≤ 5–10 мс на edge.
- Время восстановления после деградации ≤ 10 мин.
sql
SELECT ts::date d, tenant, route,
SUM(hits) AS limit_hits,
SUM(total) AS total_calls,
SUM(hits)::decimal/NULLIF(SUM(total),0) AS hit_rate
FROM ratelimit_stats
GROUP BY 1,2,3
ORDER BY d DESC, hit_rate DESC;
11) Плейбуки инцидентов
Шторм ретраев (падение апстрима): включить global throttling, поднять backoff, открыть circuit-breaker, вернуть «быстрые ошибки» вместо тайм-аутов.
Бот-атака/скрейпинг: жесткий кап по IP/ASN/гео, включить WAF/JS-челлендж, ограничить каталоги/поиск.
Пик турнира/эвента: превентивно поднять лимиты чтения, снизить «дорогие поля», включить кеш/денормализацию.
Досыл вебхуков от PSP: временный leaky bucket, приоритизация критичных типов, расширить dead-letter и ретраи.
12) Тестирование и UAT
Нагрузочные: RPS лестницей, бурсты ×10 от нормального.
Справедливость: эмуляция 1 «жадного» тенанта — не более X% глобального бюджета.
Деградация: SLO-адаптация сокращает лимиты и держит p95 в коридоре.
Граничные кейсы: смена окна (мин→час), дрожание часов (clock skew), масштабирование Redis/шардинг ключей.
Контракт: заголовки 429 и Retry-After присутствуют, SDK корректно бэк-оффит.
13) Хранилище для лимитов
In-memory для локальных лимитов (малые кластеры).
Redis/Memcached для распределенных (Lua-скрипты для атомарности).
Шардирование ключей по хэшу; TTL под окна; бэкап-метрика для потери кэша.
Idempotency: лимитер не должен ломать идемпотентные повторные вызовы (учет по ключу запроса).
14) Управление политиками (Governance)
Каталог лимитов: кто владелец, какие ключи/порог/рационал.
Feature-flags для быстрых переключателей (crisis mode).
Версионирование политик и RFC-процесс на изменения договорных квот.
A/B эксперименты на подбор оптимальных порогов.
15) Анти-паттерны
Глобальный один лимит «на все API».
Только фиксированные окна → «краевые» скачки.
Лимит без обратной связи (нет `Retry-After`/headers).
Молчаливые тайм-ауты вместо быстрых 429/503.
Отсутствие per-tenant fair-share — один клиент душит остальных.
Нет защиты GraphQL/поиска по сложности.
Нули в concurrent-guard → «пылесос» БД/PSP.
16) Мини-шпаргалка выбора
По умолчанию: token bucket (rate+burst) per-tenant+route.
Квоты по деньгам/отчетам: sliding window сутки/месяц.
Тяжелые операции: concurrent-limits + очередь.
GraphQL/поиск: complexity-budgets + persisted queries.
WS/вебхуки: leaky bucket + backpressure.
Кризис: dynamic throttling + circuit-breaker + degrade.
Резюме
Контроль нагрузки — это многоуровневая дисциплина: правильные алгоритмы (bucket/окна/конкурентность), справедливые ключи лимитирования, SLO-адаптация и прозрачная обратная связь. Вшив лимиты в gateway/mesh/сервисы, вооружив GraphQL/WS/вебхуки профильными полисами и подключив наблюдаемость с плейбуками, вы превращаете пиковые события и чужие сбои в управляемые ситуации — без крашей, сорванных выплат и просадок конверсии.