Real-Time Backend
Что такое Real-Time
В 2013 году Facebook столкнулся с проблемой: мобильное приложение каждые 5 секунд спрашивало сервер «есть новые сообщения?». При 500 миллионах пользователей это 6 миллиардов бессмысленных запросов в минуту. Батареи разряжались, серверы горели. Решение - MQTT push-протокол - сократило трафик в 40 раз и сделало Messenger по-настоящему мгновенным.
- Telegram, WhatsApp - мгновенная доставка сообщений через WebSocket и push
- Google Docs, Figma - одновременное редактирование документа десятками людей
- Uber, Яндекс.Такси - live-позиция водителя обновляется каждые 2 секунды
- Fortnite, CS2 - 128 tick rate, состояние мира обновляется 128 раз в секунду
- Bloomberg Terminal - котировки акций обновляются в реальном времени по всему миру
От Comet к WebSocket
До 2011 года real-time в вебе был хаком. Техника Comet (2006) использовала скрытый iframe с бесконечным ответом сервера - браузер рендерил <script> теги по мере их поступления. Другой хак - Flash Socket - требовал плагина. В 2011 году RFC 6455 стандартизировал WebSocket, дав вебу полноценный двусторонний канал. Это изменило архитектуру бекендов навсегда.
WebSocket превратил браузер из «запрашивающего клиента» в полноценного участника real-time коммуникации
Что значит «real-time» для бекенда
Вы набираете сообщение в Telegram. Собеседник видит «печатает...» - мгновенно. Вы отправили - он прочитал через полсекунды. Это **real-time**: сервер доставляет данные клиенту в момент их появления, а не когда клиент решит спросить.
В классическом HTTP клиент всегда инициатор: послал запрос - получил ответ. Сервер **не может** сам постучаться к клиенту. Это как почтовый ящик: письмо появится только когда вы его проверите. Real-time переворачивает эту модель - сервер **сам** push'ит данные клиенту.
- Request-Response (классический HTTP) — Клиент спрашивает → сервер отвечает. Клиент не спросил - данных нет. Задержка = интервал между запросами.
- Real-Time (push-модель) — Сервер отправляет данные сразу при их появлении. Клиент подписан и ждёт. Задержка = сеть + обработка.
Пользователи **не думают** о протоколах. Они думают в терминах ожиданий: набираю текст - собеседник видит сразу. Лайкнул пост - счётчик обновился. Переместился на карте - курьер видит мою точку. Если задержка превышает ожидания - продукт ощущается «сломанным».
| Действие пользователя | Ожидание | Если медленнее |
|---|---|---|
| Печатает сообщение | < 100 мс индикатор | Индикатор мерцает, раздражает |
| Отправил сообщение | < 300 мс доставка | Ощущение «зависло» |
| Лайкнул пост | < 500 мс обновление | Нажимает повторно |
| Двигается на карте | < 1 с позиция | Курьер «прыгает» |
| Получил уведомление | < 3 с после события | Пропустил важное |
**Real-time не означает мгновенно.** Это означает «достаточно быстро для конкретного use case». Чат допускает 200 мс, а торговля акциями - нет.
Real-time значит нулевая задержка
Real-time значит задержка ниже порога восприятия для конкретного use case
Физика сети не позволяет нулевую задержку. «Real-time» - это когда пользователь не замечает задержку. Для чата это 200 мс, для игр - 50 мс, для торговли - микросекунды.
В чём ключевое отличие real-time от классического HTTP?
Polling vs Push: две модели получения данных
Представьте: вы ждёте посылку. **Polling** - вы каждые 5 минут выходите к двери и проверяете, не пришла ли. **Push** - курьер звонит в дверь сам. Какой подход эффективнее?
**Long Polling** - компромисс. Клиент отправляет запрос, но сервер **не отвечает сразу**, а держит соединение открытым, пока не появятся данные (или не истечёт таймаут).
| Подход | Задержка | Нагрузка на сервер | Когда использовать |
|---|---|---|---|
| Short Polling | 0..interval (среднее = interval/2) | Высокая (пустые запросы) | Редкие обновления, простой API |
| Long Polling | ~сетевая задержка | Средняя (открытые соединения) | Когда WebSocket недоступен |
| WebSocket | ~сетевая задержка | Низкая (одно соединение) | Чат, игры, live-данные |
| SSE (Server-Sent Events) | ~сетевая задержка | Низкая (однонаправленный) | Уведомления, ленты, дашборды |
**Правило большого пальца:** если данные обновляются чаще раза в 10 секунд - polling не подходит. Нужен push.
Long polling решает все проблемы polling
Long polling - компромисс, который создаёт свои проблемы: каждый клиент держит открытое HTTP-соединение
При 50,000 подключений long polling создаёт 50,000 висящих HTTP-соединений. Это потребляет память и file descriptors на сервере. WebSocket использует более лёгкий протокол после handshake.
Чат-приложение с 10,000 пользователей использует short polling каждые 2 секунды. Сколько HTTP-запросов в минуту получает сервер?
Latency Budget: бюджет задержки
Когда пользователь отправляет сообщение в чате, до получателя оно проходит путь: клиент → сеть → сервер (обработка) → сеть → получатель. Каждый шаг добавляет задержку. **Latency budget** - это разбиение допустимой задержки по компонентам.
| Use Case | Допустимая задержка | Бюджет на сервер | Бюджет на сеть |
|---|---|---|---|
| Typing indicator | < 100 мс | 10 мс (только relay) | 40 мс (2 hop) |
| Чат-сообщение | < 200 мс | 40 мс (validate + store) | 40 мс |
| Уведомление | < 2 с | 500 мс (generate + route) | 200 мс |
| Live dashboard | < 1 с | 200 мс (aggregate) | 100 мс |
| Multiplayer игра | < 50 мс | 10 мс (game loop tick) | 20 мс |
| HFT трейдинг | < 1 мс | 0.1 мс | 0.5 мс (colocation) |
Бюджет задержки - инструмент проектирования. Он помогает понять, **где оптимизировать**. Если сеть занимает 80% бюджета - оптимизация кода на сервере бессмысленна, нужно менять архитектуру (CDN, edge computing, colocation).
- **Определить допустимую задержку** из требований продукта и UX-исследований
- **Разложить на компоненты:** клиент → сеть → сервер → сеть → клиент
- **Измерить реальные значения** каждого компонента (не угадывать!)
- **Найти bottleneck** - компонент, который занимает больше всего бюджета
- **Оптимизировать bottleneck** или пересмотреть архитектуру
**P99, а не среднее!** Средняя задержка 50 мс - звучит отлично. Но если 1% запросов занимает 5 секунд, каждый сотый пользователь страдает. При 1 млн пользователей - это 10,000 человек. Всегда считайте бюджет по P99 (99-й перцентиль).
Почему Discord перешёл с Go на Rust
Garbage collector в Go создавал паузы в 1-10 мс каждые несколько секунд. Для чата - допустимо. Для voice - слышно. Discord переписал критичные сервисы на Rust (без GC) и получил стабильный P99 < 1 мс. Latency budget определил выбор языка.
Допустимая задержка для чата - 200 мс. Сеть (RTT) занимает 80 мс, клиентский рендер - 20 мс. Сколько остаётся серверу на обработку?
Карта real-time use cases
Real-time - это не одна технология. Это спектр задач с разными требованиями к задержке, надёжности и масштабу. Понимание карты use cases помогает выбрать правильную технологию для конкретной задачи.
| Use Case | Задержка | Направление | Технология | Примеры |
|---|---|---|---|---|
| Messaging / Chat | < 200 мс | Bidirectional | WebSocket | Telegram, Slack, WhatsApp |
| Typing indicators | < 100 мс | Bidirectional | WebSocket | «Печатает...» в мессенджерах |
| Notifications | < 3 с | Server → Client | SSE / Push API | Лайки, комменты, алерты |
| Live dashboard | < 1 с | Server → Client | SSE / WebSocket | Grafana, торговые терминалы |
| Collaborative editing | < 100 мс | Bidirectional | WebSocket + CRDT/OT | Google Docs, Figma |
| Multiplayer games | < 50 мс | Bidirectional | WebSocket / UDP | Fortnite, CS2 |
| Live location | < 2 с | Bidirectional | WebSocket | Uber, Яндекс.Такси |
| Stock trading | < 1 мс | Bidirectional | Custom TCP / FPGA | NASDAQ, биржи |
| Live streaming | < 5 с | Server → Client | WebRTC / HLS | Twitch, YouTube Live |
Обратите внимание на колонку **«Направление»**. Не все use cases требуют двустороннюю связь. Уведомления и дашборды - только **server → client**. Для них SSE проще и достаточно. WebSocket нужен, когда клиент тоже активно отправляет данные.
Каждый use case предъявляет разные требования не только к задержке, но и к **гарантиям доставки**:
- At-most-once (не более одного раза) — Typing indicator, cursor position. Потеря одного события - незаметна. Следующее обновление всё исправит.
- At-least-once (хотя бы один раз) — Уведомления, события в ленте. Лучше показать дубль, чем потерять важное уведомление.
- Exactly-once (ровно один раз) — Платежи, сообщения в чате. Дубли - проблема (двойное списание). Требует идемпотентности и подтверждений.
Ключевые идеи урока
- Real-time - сервер push'ит данные клиенту, не дожидаясь запроса
- Short polling - простой, но расточительный: 99% запросов впустую
- Long polling - компромисс, но каждый клиент занимает соединение
- WebSocket - полнодуплексный канал, стандарт для двусторонней real-time связи
- SSE - простой однонаправленный поток, идеален для уведомлений и дашбордов
- Latency budget - разбиение допустимой задержки по компонентам (сеть, сервер, клиент)
- Всегда считайте по P99, а не по среднему - хвост распределения убивает UX
Что дальше
Теперь вы понимаете зачем нужен real-time и какие задачи он решает. Следующий шаг - разобраться в конкретных протоколах и их внутреннем устройстве.
- WebSocket протокол — Основной двусторонний протокол real-time
- Server-Sent Events — Однонаправленный push от сервера
- Pub/Sub паттерн — Масштабирование real-time на несколько серверов
Вопросы для размышления
- Какие real-time фичи есть в приложениях, которыми вы пользуетесь каждый день? Какую технологию они скорее всего используют?
- Если бы вам нужно было добавить real-time уведомления в существующий REST API - какой подход вы бы выбрали и почему?
- Как изменится latency budget, если ваши пользователи находятся на другом континенте?
Связанные уроки
- rt-02-http-limits — HTTP limitations discussed next explain why real-time architectures were invented
- bt-01-overview — Real-time protocols are a specialization of the transport overview covered in backend-transport
- st-01-feedback-loops — WebSocket creates a closed feedback loop; polling is an open loop with high latency
- alg-01-big-o — Push O(1) vs polling O(n) is a direct application of complexity analysis to protocol choice
- sd-01-intro — Real-time requirements appear in System Design estimation: QPS, connection counts, fan-out
- net-21-http-basics
- net-63-realtime-compare