Real-Time Backend

Rate Limiting Real-Time

Один WebSocket-бот с bagom в коде создал 50 000 соединений в минуту на production Discord. Без rate limiting это положило бы сервер для 150 миллионов пользователей.

  • **Discord** лимитирует 120 events/мин на Gateway соединение и бессрочно банит ботов которые игнорируют rate limit signals; это защищает 19 млн серверов от случайного и намеренного abuse
  • **Binance WebSocket API** использует token bucket: 5 подписок/сек с burst до 10; превышение ведёт к закрытию соединения с кодом 1008; API ключи получают independent bucket
  • **Cloudflare** обрабатывает 100 000+ WebSocket соединений/сек через Workers; sliding window counter в distributed key-value store защищает от connection flooding без single point of failure
  • **Twitch** slowmode и follower-only mode - UX-обёртка над per-room rate limiting; реализованы через Redis sorted sets; включаются стримерами при 50 000+ одновременных зрителей

Per-Connection Rate Limiting

Per-connection rate limiting ограничивает количество сообщений от одного WebSocket соединения за единицу времени. Discord лимитирует до 120 events/минуту на соединение; при превышении - временный ban с кодом 4008 (Rate limited). Binance WebSocket: не более 10 subscriptions/секунду на соединение, до 1024 активных подписок. Цель - защитить сервер от одного агрессивного клиента, не влияя на остальных.

Важно различать rate limiting на уровне сообщений и на уровне новых соединений. Cloudflare WebSocket: до 100 новых WebSocket соединений/сек с одного IP (connection rate limit) и до 1000 сообщений/мин на соединение (message rate limit). Оба уровня нужны: connection flooding через быстрые connect/disconnect атаки не поймать только message limiter.

  • **Message rate limit**: 120-1000 msg/мин типичный диапазон; зависит от типа приложения
  • **Connection rate limit**: 10-100 новых WS/сек с одного IP; защита от connection flooding
  • **Graceful response**: не обрывать соединение сразу; вернуть ошибку с `retryAfter` и продолжить
  • **Backpressure**: если клиент отправляет быстрее чем сервер обрабатывает - буфер растёт; нужен explicit signal

Discord устанавливает лимит 120 events/мин на соединение. Бот отправил 121-е сообщение. Что должен сделать сервер?

Per-Room Rate Limiting

Per-room rate limiting ограничивает общую активность в канале/комнате независимо от числа участников. Twitch ограничивает chat до 20-100 сообщений/30 секунд на канал (зависит от верификации бота). Это защищает от coordinated spam: 1000 ботов по 1 сообщению в секунду каждый - легально с точки зрения per-connection, но разрушительно для канала.

Redis ZSET (sorted set) - стандартный инструмент для distributed rate limiting в real-time системах. Ключ - идентификатор ресурса, score - timestamp, member - уникальный ID запроса. ZREMRANGEBYSCORE удаляет устаревшие записи, ZCARD возвращает текущее количество. Весь pipeline выполняется атомарно. Discord использует Redis cluster для rate limiting 150 млн пользователей с latency <1ms.

  • **Two-tier limiting**: per-user (предотвращает spam от одного) + per-room (предотвращает coordinated attack)
  • **Tiered limits по роли**: moderators - 50 msg/мин, regular users - 5 msg/мин, verified bots - 100 msg/мин
  • **Slowmode**: Twitch и Slack вводят задержку между сообщениями одного пользователя (1-120с) вместо hard limit
  • **Redis pipeline**: группировать ZREMRANGEBYSCORE + ZADD + ZCARD в один round-trip; критично для latency

Twitch-стример включил slowmode 30 секунд в чате с 50 000 зрителей. Как это реализовано технически?

Sliding Window Algorithm

Sliding window - алгоритм rate limiting, который точно считает события за скользящий интервал времени. В отличие от fixed window ("не более 100/мин, сброс в 00:00"), sliding window не имеет проблемы двойного spike на границе периода. Cloudflare использует sliding window counter для защиты 19 млн сайтов, обрабатывая 100 000+ запросов/сек через Redis Cluster.

На практике используют Sliding Window Counter (не Log) для баланса точности и памяти. Погрешность не превышает limit/window_size - для 100 req/мин ошибка максимум 1-2 запроса. Cloudflare использует именно этот алгоритм в своём WAF. Для финансовых систем где важна точность - ZSET в Redis (sliding window log) несмотря на больший overhead.

  • **Fixed Window**: прост, O(1), уязвим к burst на границе периода
  • **Sliding Window Log**: точен, O(N) где N - число событий в окне; Redis ZSET реализация
  • **Sliding Window Counter**: компромисс O(1), погрешность <1%; рекомендуется для большинства случаев
  • **Leaky Bucket**: сглаживает burst, постоянная скорость потребления; используется в network QoS

Лимит: 100 запросов/минуту. Fixed window. В [0:50-0:59] отправлено 100 запросов. В [1:00-1:10] отправлено ещё 100. Нарушен ли лимит?

Token Bucket Algorithm

Token bucket - алгоритм, позволяющий burst активность в разумных пределах. Представь корзину с жетонами: жетоны добавляются со скоростью R/сек, максимум B жетонов. Каждый запрос тратит один жетон. AWS API Gateway использует token bucket: 10 000 запросов/сек baseline + burst до 5 000 дополнительных запросов мгновенно. Это позволяет выдержать пики без деградации.

Token bucket лучше sliding window для real-time систем с burst трафиком. WebSocket-клиент при старте может отправить 50 подписок разом (легитимный burst), затем работать со скоростью 1-2 сообщения/сек. Sliding window одинаково штрафует любой burst. Token bucket разрешает разумный начальный burst (capacity) при соблюдении среднего rate (refillRate). Binance WebSocket API использует именно token bucket: 5 подписок/сек с burst до 10.

  • **Capacity (burst size)**: сколько запросов можно отправить мгновенно при полной корзине
  • **Refill rate**: скорость восполнения; определяет sustained throughput
  • **Distributed bucket**: хранить tokens в Redis (`INCR`, `EXPIRE`, `DECRBY`) для multi-instance сервисов
  • **Variable cost**: тяжёлые операции (subscribe to 100 symbols) тратят больше жетонов чем лёгкие (ping)
  • **Retry-After header**: всегда возвращать когда следующий запрос будет разрешён (tokens/refillRate секунд)

Rate limiting - это просто защита от DDoS и вредоносных клиентов

Rate limiting в real-time системах также защищает добросовестных пользователей от случайного abuse (баги в клиенте, reconnect storm) и обеспечивает fair sharing ресурсов между всеми соединениями

WebSocket-бот с багом в reconnect логике может создать 10 000 соединений в секунду, не имея злого умысла. Без rate limiting на уровне соединений один сломанный клиент деградирует сервис для всех остальных. Discord отключает ботов с кодом 4000 при ошибках в Identify именно из-за случайных reconnect loops.

Token bucket: capacity=10, refillRate=1 жетон/сек. Клиент подключился с полной корзиной и отправил 10 сообщений мгновенно. Через сколько секунд он сможет отправить ещё 5 сообщений?

Итоги

  • **Per-connection + per-room**: два уровня защиты; connection limit защищает от одного агрессора, room limit - от coordinated attack многих клиентов
  • **Sliding window vs Fixed window**: fixed window уязвим к double burst на границе периода; sliding window точен но требует O(N) память или Redis ZSET
  • **Token bucket**: лучший выбор для real-time - разрешает легитимный burst (capacity) при соблюдении sustained rate (refillRate); Binance и Discord используют именно его
  • **Graceful degradation**: возвращать `retryAfter`, не рвать соединение при первом превышении; логировать нарушителей для мониторинга abuse паттернов

Связанные темы

Rate limiting для real-time систем пересекается с безопасностью и архитектурой:

  • WebSocket Security — Rate limiting - часть общей защиты WebSocket: connection rate limit защищает Upgrade endpoint от flooding; message rate limit - от application-layer abuse
  • Financial Trading — Binance и NYSE применяют strict rate limiting к торговым API; превышение ведёт к IP ban на 24h и отзыву API ключей; weight-based billing за тяжёлые запросы
  • IoT Real-Time — AWS IoT Core тарифицирует per-message; edge-агент должен внедрять token bucket чтобы сломанный датчик не израсходовал весь месячный бюджет за минуты

Вопросы для размышления

  • WebSocket-чат: пользователь отправил 50 сообщений за 1 секунду из-за бага в клиенте (двойной submit). Как rate limiting должен реагировать чтобы не навредить добросовестному пользователю, но остановить abuse?
  • Discord использует token bucket capacity=120 при refillRate=2/сек (120/мин). Почему не просто sliding window 120/мин? В каких сценариях bucket capacity критически важна?
  • Распределённая система: 10 WebSocket серверов, rate limit per-user 100 msg/мин. Пользователь подключён к серверу A - как сервер A узнаёт сколько сообщений пользователь уже отправил через серверы B-J?

Связанные уроки

  • sd-17-rate-limiting
  • db-19-redis
Rate Limiting Real-Time

0

1

Войти