System Design Space

    Глава 30

    Обновлено: 16 февраля 2026 г. в 03:00

    Chat System

    Прогресс части0/21

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

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

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

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

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

    Читать обзор

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

    WhatsApp
    Telegram
    Slack
    Discord

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

    Core Features

    • 1-on-1 чаты между пользователями
    • Групповые чаты (до N участников)
    • Отправка текстовых сообщений
    • Индикатор онлайн-статуса
    • Уведомления о прочтении (read receipts)

    Extended Features

    • Отправка медиа-файлов (изображения, видео)
    • Push-уведомления для офлайн-пользователей
    • End-to-end шифрование
    • История сообщений и синхронизация
    • Typing indicators ("печатает...")

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

    Low Latency

    Доставка сообщений < 100ms для онлайн-пользователей. Real-time experience критичен.

    High Availability

    99.99% uptime. Мессенджер должен работать всегда — это критическая инфраструктура.

    Consistency

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

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

    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 проблему для групповых чатов