Транспорт бэкенда
System Design: Real-Time Chat
WhatsApp 2014: 450M пользователей, 10B сообщений в день, команда 55 инженеров. Секрет: правильная архитектура с первого раза. Erlang OTP: 2M WebSocket соединений на один сервер. Redis Pub/Sub для fanout. PostgreSQL для persistence. Понимание этих паттернов - ответ на главный вопрос интервью: 'Design WhatsApp'.
- **WhatsApp** использует Erlang/OTP: каждое WS соединение = легковесный Erlang process (~500 bytes). 2M connections per server vs ~10K для Node.js/Java. Mnesia distributed DB для presence.
- **Discord** Go сервисы + Redis для pub/sub + Cassandra для message history. Для больших серверов (100K+ участников): переход на fanout-on-read.
- **Slack** использует Kafka для internal message routing между datacenter. WebSocket -> Kafka -> delivery service -> WebSocket fanout. Дополнительно: push notifications через Apple/Google при offline.
Требования: chat система
Real-time chat - ключевой пример системы с WebSocket, presence, offline delivery. Масштаб: WhatsApp 2B users, Slack 20M DAU, Discord 150M users. Fundamental challenge: stateful соединения + масштабирование.
WhatsApp в 2014 обслуживал 450M users с командой 55 инженеров. Erlang/OTP: каждое WS соединение = легковесный процесс. 2M concurrent connections на одном сервере. Архитектура важнее hardware.
10M concurrent WebSocket соединений @ 10K per server = 1000 серверов. Как масштабировать?
WebSocket Fanout Architecture
Fanout - распространение сообщения от одного отправителя нескольким получателям. В чате: user A пишет в group chat -> 1000 участников должны получить. Ключевая проблема: участники могут быть подключены к разным WS серверам.
Discord: для серверов с 10K+ участников переходит от fanout-on-write к fanout-on-read: сохраняют одно сообщение, клиент тянет при открытии канала. При < 100 участников - fanout-on-write через internal pub/sub.
Почему нужно сначала сохранить сообщение в БД, а потом publish в Redis Pub/Sub?
Presence System
Presence - знание онлайн-статуса пользователей. Heartbeat: клиент каждые 30s отправляет ping, сервер обновляет TTL в Redis. Если TTL истёк - пользователь offline. Проблема масштабирования: 10M online users = 10M heartbeats/30s = 333K updates/s.
WhatsApp presence: Last Seen timestamp вместо real-time online indicator. Намного дешевле: одно поле в user profile, обновляется при disconnect. Privacy-friendly: пользователь может скрыть Last Seen.
Typing indicator в групповом чате: 50 участников видят 'User A typing...' Как реализовать эффективно?
Гарантии доставки сообщений
Три уровня гарантий: at-most-once (потеря допустима), at-least-once (дубликаты допустимы), exactly-once (самое сложное). Для чата: at-least-once + idempotent delivery = практически exactly-once.
WhatsApp delivery: двойная галочка = доставлено на устройство (не просмотрено). Синяя = прочитано. При группах: синяя когда ВСЕ участники прочитали. Технически: receiver device sends read receipt, server aggregates.
Клиент отправил сообщение, соединение прервалось до получения ACK. Клиент reconnects и retry отправки. Как предотвратить дубликат?
Offline Queue и Sync
Когда получатель offline: сообщение должно быть доставлено при его следующем подключении. Push notifications (APNs, FCM) для мобильных. При reconnect - sync missed messages.
Telegram offline delivery: до 30 дней хранение в cloud. При reconnect: клиент тянет всё с последнего seq. Push notifications только для wake-up, не несут content. Content arrives через WS/HTTP при открытии.
Real-time chat требует WebSocket для всех операций включая историю и поиск
WebSocket только для real-time доставки новых сообщений. История, поиск, media upload - через обычный HTTP REST API.
WebSocket оптимален для server-push и real-time bidirectional. История - простой GET запрос. Смешивать их в одном протоколе усложняет без пользы. WhatsApp, Telegram: WS для real-time, HTTP для всего остального.
Пользователь offline 7 дней, получил 1000 сообщений. При reconnect как эффективно синхронизировать?
Итоги
- **WebSocket + Redis Pub/Sub** для fanout: сообщение сначала в БД, затем PUBLISH. Online users получают через WS, offline через push + sync при reconnect.
- **Presence через TTL**: heartbeat каждые 30s обновляет SETEX. Typing через Redis PUBLISH - ephemeral, не сохраняется.
- **Client ID для idempotency**: UUID генерируется на клиенте перед отправкой. ON CONFLICT DO NOTHING предотвращает дубликаты при retry.
Связанные темы
Chat система комбинирует WebSocket, Pub/Sub и очереди:
- WebSocket: двусторонняя связь — WebSocket - основной транспорт для real-time доставки. Масштабирование через Redis Pub/Sub - из этого урока
- Dead Letter Queue и Retry — Undelivered messages при offline/error -> retry queue -> DLQ при исчерпании попыток
Вопросы для размышления
- Как обеспечить ordering сообщений в групповом чате при concurrent отправке от нескольких участников?
- Как сохранить privacy (end-to-end encryption) при необходимости server-side push notifications?
- Как разработать presence систему которая точна, но не перегружает Redis при 10M online users?