Real-Time Backend
HTTP и его ограничения
От документов к приложениям: 30 лет эволюции протокола
1991 год: Тим Бернерс-Ли создаёт HTTP/0.9 в CERN - протокол для загрузки HTML-документов. Один запрос, один документ. 2005-2009: Web 2.0. Gmail, Google Maps, Facebook. Разработчики придумывают Comet, Long Polling, Bayeux protocol - всё это костыли поверх HTTP. 2009: Райан Даль создаёт Node.js, показывая что event-loop эффективнее thread-per-request для I/O-intensive задач. 2011: RFC 6455 стандартизирует WebSocket - протокол, изначально созданный для full-duplex коммуникации. Ограничения HTTP не исчезли - они стали мотивацией для нового протокола.
Slack в 2013 году тратил сотни тысяч долларов на инфраструктуру polling, обслуживая 15,000 запросов в секунду - из которых 99.9% возвращали пустой ответ. Каждый из этих запросов тащил 600 байт заголовков. После перехода на WebSocket нагрузка упала в 100 раз при той же функциональности.
- **Facebook чат (2008)** - изобрёл Long Polling для миллионов пользователей, пока WebSocket не был стандартом
- **Slack** - работал на polling до 2015 года, потом перешёл на WebSocket: нагрузка упала в 100 раз
- **Биржи (Binance, Coinbase)** - WebSocket API для live orderbook: HTTP polling на таких объёмах физически невозможен
- **Google Docs** - Operational Transformation через Long Polling до 2013 года, потом WebSocket
- **GitHub Actions** - live logs через SSE поверх HTTP/2: Server-Sent Events без WebSocket overhead
- **Notion** - real-time collaboration через WebSocket: каждое нажатие клавиши синхронизируется за <50 мс
Предварительные знания
Request-Response: однонаправленная модель
1991 год. Тим Бернерс-Ли в CERN создаёт HTTP для обмена научными статьями. Одна страница - один запрос - один ответ. Идеально для физиков-ядерщиков. Через 30 лет на этом же протоколе работают WhatsApp, Slack и торговые терминалы. Фундаментальное ограничение никуда не делось: **сервер физически не может отправить данные без предварительного запроса от клиента**.
HTTP строго построен на принципе **клиент инициирует - сервер отвечает**. Это half-duplex: данные идут в одном направлении за раз. Сервер не хранит информацию о клиенте между запросами (stateless). Каждый запрос - с нуля: заголовки, cookies, авторизация - заново.
| Характеристика | HTTP Request-Response | Real-time идеал |
|---|---|---|
| Инициатор | Только клиент | Обе стороны |
| Направление | Half-duplex | Full-duplex |
| Состояние | Stateless (каждый запрос с нуля) | Persistent connection |
| Латентность | Зависит от частоты запросов | Мгновенная доставка |
| Overhead | 500-800 байт заголовков при каждом запросе | Минимальный после handshake |
Каждый HTTP-запрос несёт 500-800 байт заголовков, даже если полезные данные - пустая строка. Cookie на крупном сайте добавляет ещё 200-500 байт. При polling каждые 2 секунды 10,000 пользователей генерируют ~5,500 байт/с только на метаданные протокола.
HTTP/2 и HTTP/3 решают проблему real-time через Server Push
Server Push в HTTP/2 предназначен для предзагрузки ресурсов (CSS, JS, images), а не для push-уведомлений. Модель остаётся request-response. Фактически браузеры убирают поддержку HTTP/2 Push как неиспользуемую функцию.
HTTP/2 Server Push отправляет ресурс проактивно в контексте уже открытого запроса - оптимизация загрузки страницы. Механизм push-уведомлений требует persistent connection без привязки к конкретному запросу - это WebSocket или SSE.
Почему HTTP-сервер не может отправить push-уведомление клиенту о новом сообщении?
Short Polling: наивный подход
Раз сервер не может уведомить клиента - что если клиент будет спрашивать постоянно? Short Polling: запрос каждые N секунд. Принципы LMAX: каждый тик торгового движка - polling на новые ордера. Slack в 2013 году до WebSocket: браузеры опрашивали серверы каждые 500 мс. Просто, работает везде. Цена считается ниже.
Посчитаем нагрузку. Чат-приложение: **10,000 пользователей онлайн**, polling каждые **2 секунды**, ~50 сообщений в минуту в группе.
| Интервал polling | Req/s (10k пользователей) | Средняя латентность | Трафик впустую/час |
|---|---|---|---|
| 500 мс | 20,000 | 250 мс | 68.4 GB |
| 1 с | 10,000 | 500 мс | 34.2 GB |
| 2 с | 5,000 | 1 с | 17.1 GB |
| 5 с | 2,000 | 2.5 с | 6.8 GB |
| 30 с | 333 | 15 с | 1.1 GB |
Дилемма Short Polling: уменьшить интервал - растёт нагрузка и трафик. Увеличить - растёт латентность. Для dashboard с обновлением раз в 30 секунд Short Polling - нормальный выбор. Для live trading или чата - катастрофа.
Short Polling всегда плохо, его нигде не используют
Short Polling - хорошее решение для редко обновляемых данных: статус заказа, курсы валют раз в минуту, мониторинг раз в 30 секунд. Там где простота важнее мгновенной доставки.
Не каждое приложение требует миллисекундную задержку. Dashboard с обновлением раз в минуту не нуждается в WebSocket. Short Polling прост, надёжен, работает за любыми прокси и файрволами, не требует сложной серверной инфраструктуры.
10,000 пользователей с polling-интервалом 2 секунды. Сколько запросов в секунду получит сервер?
Long Polling: хитрый трюк
2008 год. Facebook чат обслуживает десятки миллионов пользователей. Short Polling - слишком дорого. WebSocket - ещё не стандарт. Инженеры Facebook изобретают **Long Polling**: вместо мгновенного ответа 'ничего нового' - сервер молчит, держит соединение открытым и отвечает только когда появятся данные. Задержка доставки стремится к нулю.
**Comet** - так в 2006 году назвали набор техник server push поверх HTTP. Long Polling - самая популярная из них. Название игра слов: Ajax и Comet - оба бренды чистящих средств. Twitter использовал Long Polling до 2013 года. Gravatar, Basecamp - тоже. Сейчас это легаси-паттерн: WebSocket или SSE делают то же самое без overhead HTTP handshake на каждое переподключение.
| Свойство | Short Polling | Long Polling |
|---|---|---|
| Задержка доставки | 0 до N секунд (среднее N/2) | ~0 мс (как только данные готовы) |
| Запросов в секунду | N пользователей / интервал | ~0 в idle, spike при событии |
| Открытых соединений | Короткие, закрываются | До 1 на пользователя постоянно |
| Сложность сервера | Минимальная | Нужно хранить waitingClients map |
| Совместимость | Везде, любой прокси | Везде, но прокси может обрывать длинные запросы |
В чём ключевое отличие Long Polling от Short Polling?
Цена HTTP: заголовки, handshake, bandwidth
Short Polling расточителен, Long Polling - сложен. Но у обоих общая боль: **каждый HTTP-запрос тащит сотни байт заголовков**, и каждое новое TCP-соединение требует рукопожатия. Биржевой движок Binance обрабатывает 100,000 ордеров/с. Если бы каждый ордер стоил HTTP handshake - 2.5-3.5 RTT по 50 мс - система работала бы с задержкой в секунды.
| Компонент | Overhead | Примечание |
|---|---|---|
| HTTP Request Headers | 600-800 байт | При каждом запросе |
| HTTP Response Headers | 300-500 байт | При каждом ответе |
| TCP Handshake | 1.5 RTT | Новое соединение |
| TLS Handshake | 1-2 RTT | HTTPS (обязателен в продакшне) |
| TCP Slow Start | ~10 пакетов разгон | Ограничивает пропускную способность вначале |
| Cookie (типичный) | 100-500 байт | Каждый запрос к домену |
HTTP/2 решает часть проблем: мультиплексирование нескольких запросов по одному TCP, сжатие заголовков HPACK. Но фундаментальная модель request-response остаётся. Сервер по-прежнему не может инициировать отправку данных без запроса клиента - это решает только WebSocket или SSE.
HTTP/2 мультиплексирование полностью решает проблему real-time
HTTP/2 оптимизирует передачу данных: сжатие заголовков HPACK, мультиплексирование запросов по одному TCP. Но модель не меняется - сервер не может инициировать передачу.
Мультиплексирование позволяет параллельно отправлять несколько запросов по одному TCP-соединению - это ускоряет загрузку страниц. Но каждый запрос всё равно инициирует клиент. Для настоящего real-time нужен WebSocket или SSE.
При Short Polling чата (10k пользователей, интервал 2с, ~50 сообщений/мин), какой процент трафика составляют полезные данные?
Ключевые выводы
- HTTP - request-response протокол 1991 года: сервер не может инициировать отправку данных клиенту
- Short Polling (запрос каждые N секунд): 99.998% трафика - пустые заголовки при 10k пользователях
- Long Polling: сервер держит соединение до данных - нулевая задержка, но каждое переподключение = HTTP overhead
- Каждый HTTP-запрос несёт 500-800 байт заголовков; TCP+TLS handshake = 2.5-3.5 RTT на новое соединение
- HTTP/2 HPACK сжимает заголовки и мультиплексирует запросы - но request-response модель не меняется
- Short Polling - нормально для раз-в-минуту обновлений; WebSocket/SSE - для настоящего real-time
Связь с другими темами
HTTP-ограничения - мотивация для появления WebSocket, SSE и других real-time протоколов:
- WebSocket — Решает все проблемы HTTP для real-time: full-duplex, минимальный overhead, persistent connection
- Server-Sent Events (SSE) — Компромисс: server push поверх HTTP без WebSocket upgrade
- HTTP Fundamentals — Подробная механика HTTP/1.1, HTTP/2 и HPACK
Вопросы для размышления
- При каком сценарии Short Polling - лучший выбор, несмотря на неэффективность?
- Почему HTTP создали как stateless request-response? Какие преимущества это даёт для веб-страниц?
- Как бы выглядело API чата на базе Long Polling при 1 миллионе одновременных пользователей?
Связанные уроки
- rt-01-what-is-realtime — Определение real-time latency из предыдущего урока
- rt-03-sse — SSE - следующий шаг: server push поверх HTTP
- bt-05-http-fundamentals — Детали HTTP/1.1 и HTTP/2 механики
- bt-08-websocket — WebSocket решает все ограничения, разобранные здесь
- bt-07-http2-http3 — HTTP/2 мультиплексирование и его реальные пределы
- isd-18-case-chat-system
- net-21-http-basics
- net-24-http2-http3