Обновлено: 25 марта 2026 г. в 04:52

Chat System

medium

Классическая задача: WebSocket, real-time messaging, presence, группы, синхронизация и масштабирование.

Чат становится сложным не на отправке одного сообщения, а на presence, ordering, multi-device sync, reconnect semantics и fanout в реальном времени.

Кейс помогает собрать websocket или session layer, message persistence, delivery acknowledgements, unread state и offline recovery в одну модель.

В интервью и design review он полезен тем, что заставляет явно выбирать guarantees: что должно быть мгновенным, что строго упорядоченным, а что можно восстановить позже.

Latency Budget

Каждый hop в критическом пути должен иметь чёткий тайм-бюджет и предсказуемое поведение.

Fanout Strategy

Выбор push/pull/hybrid напрямую влияет на масштабируемость и сложность системы.

Session State

Важно учитывать presence, reconnect, ordering и delivery semantics для клиента.

Graceful Degradation

При пиках нагрузок система должна понижать качество сервиса без массового отказа.

Проектирование системы мгновенных сообщений (chat system) — одна из классических задач на System Design интервью. Это комплексная задача, которая затрагивает real-time коммуникации, масштабирование stateful-соединений, консистентность сообщений и офлайн-доставку.

Связанная глава

Обзор книги Alex Xu

Подробный разбор чат-систем есть в главе 12 книги Alex Xu.

Читать обзор

Примеры реальных систем

WhatsApp
Telegram
Slack
Discord

1Функциональные требования

1-on-1 чаты между пользователями.

Групповые чаты (до N участников).

Отправка текстовых и медиа сообщений.

Индикатор онлайн-статуса и typing indicators.

Read receipts и синхронизация истории сообщений между устройствами.

Push-уведомления для офлайн-пользователей.

2Нефункциональные требования

Latency: < 100ms

Real-time доставка сообщений для онлайн-пользователей.

Availability: 99.99% uptime

Мессенджер должен оставаться доступным при частичных сбоях.

Consistency: гарантия доставки и порядка

Сообщения не должны теряться или приходить в неверном порядке.

Scalability: 50M concurrent connections

Система должна горизонтально масштабироваться под рост аудитории.

Масштаб системы (пример)

DAU:500M
Сообщений/день:100B
Concurrent connections:50M
Средний размер:100 bytes

3Выбор протокола коммуникации

Связанная глава

WebSocket Protocol

Подробный разбор WebSocket: handshake, keepalive, reconnect и best practices.

Читать обзор

Сравнение подходов

ПодходLatencyServer LoadUse Case
HTTP PollingВысокий (интервал)Очень высокийLegacy fallback
Long PollingСреднийВысокийПростые уведомления
WebSocket ✓МинимальныйОптимальныйReal-time чаты
Server-Sent EventsМинимальныйСреднийОднонаправленный поток

Почему WebSocket?

  • Bidirectional: Клиент и сервер могут отправлять сообщения в любой момент
  • Persistent connection: Одно соединение на сессию, минимум overhead
  • Low latency: Нет HTTP handshake на каждое сообщение
  • Efficient: Меньше трафика и нагрузки на сервер

4High-Level архитектура

Chat System: High-Level Map

realtime routing + durable storage + offline push delivery

Realtime Plane

Client -> WS Gateway -> Chat Router
realtime message path
Session Registry + Presence
routing + online status

Durable + Offline Plane

Message Store -> Delivery Queue
durable persistence + async fanout
Push Service -> APNS/FCM
offline notifications

Базовая topology chat system: realtime WebSocket-контур, durable storage и отдельный offline push pipeline.

Online delivery path

  1. User A отправляет сообщение через WebSocket-соединение.
  2. WS Gateway передаёт payload в Chat Router.
  3. Session Registry находит целевой chat server для User B.
  4. При активной сессии сообщение мгновенно доставляется получателю.

Offline delivery path

  1. Сообщение сначала подтверждается и сохраняется в Message Store.
  2. Delivery Queue формирует задачу push-уведомления с retry-политикой.
  3. Push Service отправляет событие в APNS/FCM.
  4. При reconnection клиент синхронизируется по last-seen message ID.

5Хранение сообщений

Хранение данных

Database Internals

Выбор между SQL и NoSQL зависит от паттернов доступа.

Читать обзор

Сравнение подходов к хранению

DatabaseПлюсыМинусыUse Case
PostgreSQLACID, знакомыйШардинг сложенМалый масштаб
Cassandra ✓Горизонтальное масштабирование, отличная записьEventual consistencyЧаты Facebook/Discord
HBaseWide-column, интеграция с HadoopСложность операцийFacebook Messenger

Схема данных (Cassandra)

-- Таблица сообщений (partitioned by chat_id)
CREATE TABLE messages (
    chat_id       UUID,
    message_id    TIMEUUID,  -- Snowflake ID или TIMEUUID
    sender_id     UUID,
    content       TEXT,
    created_at    TIMESTAMP,
    PRIMARY KEY ((chat_id), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);

-- Быстрый доступ к последним сообщениям чата
SELECT * FROM messages 
WHERE chat_id = ? 
ORDER BY message_id DESC 
LIMIT 50;

Почему message_id важен?

  • Ordering: TIMEUUID или Snowflake ID гарантируют порядок
  • Pagination: "Загрузить сообщения до message_id X"
  • Deduplication: Idempotent операции при retry
  • Sync: "Дай все сообщения после message_id Y"

6Presence Service (Онлайн-статус)

Heartbeat механизм

Клиент периодически отправляет heartbeat (каждые 5-30 секунд). Если heartbeat не получен — пользователь считается офлайн.

// Redis хранение статуса
SET user:{user_id}:last_active {timestamp}
EXPIRE user:{user_id}:last_active 30

// Проверка онлайн-статуса
GET user:{user_id}:last_active
// Если ключ существует — онлайн

Fanout проблема

Если у пользователя 500 друзей, каждое изменение статуса требует 500 уведомлений. Решения:

  • Lazy loading: статус запрашивается при открытии чата
  • Batch updates: отправка изменений раз в N секунд
  • Selective push: только для активных чатов

7Групповые чаты

Масштабирование групп

Small
< 100 участников

Direct fanout через WebSocket. Каждый получает сообщение напрямую.

Medium
100-10K участников

Message Queue + async workers. Batch доставка.

Large
>10K (channels)

Pub/Sub модель. Подписка на канал, не на индивидуальные сообщения.

Схема данных для групп

-- Группы
CREATE TABLE groups (
    group_id    UUID PRIMARY KEY,
    name        TEXT,
    created_by  UUID,
    created_at  TIMESTAMP
);

-- Участники группы (для быстрого lookup)
CREATE TABLE group_members (
    group_id    UUID,
    user_id     UUID,
    joined_at   TIMESTAMP,
    role        TEXT,  -- admin, member
    PRIMARY KEY ((group_id), user_id)
);

-- Группы пользователя (обратный индекс)
CREATE TABLE user_groups (
    user_id     UUID,
    group_id    UUID,
    last_read   TIMEUUID,  -- для unread count
    PRIMARY KEY ((user_id), group_id)
);

8Синхронизация и офлайн-доставка

Синхронизация

Last-seen message ID

Паттерн last-seen message ID — ключ к эффективной синхронизации.

Читать обзор

Sync Protocol

Каждое устройство хранит last_synced_message_id. При подключении:

  1. 1Клиент отправляет свой last_synced_message_id
  2. 2Сервер возвращает все сообщения после этого ID
  3. 3Клиент применяет изменения и обновляет last_synced_message_id

Offline Message Queue

Для офлайн-пользователей сообщения накапливаются:

-- Очередь непрочитанных сообщений
CREATE TABLE offline_messages (
    user_id     UUID,
    message_id  TIMEUUID,
    chat_id     UUID,
    sender_id   UUID,
    content     TEXT,
    PRIMARY KEY ((user_id), message_id)
) WITH default_time_to_live = 2592000; -- 30 дней TTL

-- При подключении пользователя
SELECT * FROM offline_messages WHERE user_id = ?;
-- После синхронизации — удаляем доставленные

9Масштабирование WebSocket серверов

⚠️ Главная сложность

WebSocket соединения stateful. Нельзя просто добавить серверов за Load Balancer — нужно знать, на каком сервере находится конкретный пользователь.

Session Registry

Централизованный реестр (Redis) хранит маппинг user → server:

// При подключении пользователя
HSET user_sessions user_123 server_5

// При отправке сообщения
target_server = HGET user_sessions user_456

// При отключении
HDEL user_sessions user_123

Pub/Sub между серверами

Серверы общаются через Redis Pub/Sub или Kafka:

// Server 1 публикует сообщение
PUBLISH chat_server_5 {
  "type": "message",
  "to": "user_456",
  "content": "Hello!"
}

// Server 5 получает и доставляет
// через локальное WebSocket соединение

Sticky Sessions как альтернатива

Load Balancer может использовать sticky sessions (привязка пользователя к серверу по IP или cookie). Но это усложняет failover и перебалансировку. Рекомендуется Session Registry.

10Ключевые моменты для интервью

✓ Обязательно обсудить

  • Выбор протокола (WebSocket vs alternatives)
  • Как роутить сообщения между серверами
  • Message ordering и delivery guarantees
  • Offline sync и push notifications
  • Scaling stateful connections

💡 Дополнительные темы

  • End-to-end encryption (Signal Protocol)
  • Read receipts и typing indicators
  • Media storage (S3 + CDN)
  • Rate limiting для spam prevention
  • Multi-device sync

Типичные ошибки на интервью

  • Забыть про stateful природу WebSocket и сложности масштабирования
  • Не продумать offline сценарий и push notifications
  • Игнорировать message ordering при распределённой доставке
  • Не обсудить fanout проблему для групповых чатов

Связанные главы

Чтобы отмечать прохождение, включи трекинг в Настройки