Транспорт бэкенда

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?

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

  • net-36-websocket
System Design: Real-Time Chat

0

1

Войти