Чат становится сложным не на отправке одного сообщения, а там, где нужно удерживать миллионы постоянных соединений, сохранять порядок сообщений и синхронизировать несколько устройств после переподключения.
Кейс помогает собрать WebSocket-шлюзы, маршрутизацию между серверами, хранение истории, онлайн-статус, офлайн-доставку и пуш-уведомления в одну рабочую архитектуру.
В интервью и архитектурных обсуждениях он полезен тем, что заставляет явно определить, что должно доставляться мгновенно, что обязано сохранять порядок, а что можно догрузить после восстановления связи.
Бюджет задержки
Каждый переход от WebSocket-шлюза до получателя должен укладываться в понятный бюджет, иначе чат перестаёт ощущаться мгновенным.
Состояние сессий
Важно знать, на каком сервере живёт активное соединение пользователя и когда система должна считать его офлайн.
Офлайн-доставка
Нужно отдельно проектировать хранение истории, пуш-уведомления и синхронизацию после переподключения, а не смешивать это в один путь.
Групповая раздача
С ростом группы приходится отделять запись сообщения от его доставки и контролировать стоимость массовой раздачи на каждом уровне.
Чат-система становится сложной не на отправке одного сообщения, а там, где нужно держать миллионы постоянных соединений, укладываться в низкую , синхронизировать несколько устройств и восстанавливать историю после переподключения. Поэтому на собеседованиях по системному дизайну этот кейс проверяет не только знание WebSocket, но и умение собрать онлайн-доставку, хранение сообщений, офлайн-сценарии и пуш-уведомления в одну рабочую архитектуру.
Связанная глава
Обзор книги Alex Xu
Подробный разбор чат-системы есть в главе 12 книги Alex Xu.
Примеры реальных систем
1Функциональные требования
Личные чаты между пользователями.
Групповые чаты с ограничением по числу участников.
Отправка текстовых сообщений и медиафайлов.
Онлайн-статус и индикатор набора текста.
Подтверждения прочтения и синхронизация истории между устройствами.
Push-уведомления для пользователей вне активной сессии.
2Нефункциональные требования
Задержка: < 100 мс
Онлайн-пользователи должны получать сообщения почти мгновенно.
Доступность: 99.99% uptime
Мессенджер должен оставаться доступным даже при частичных сбоях.
Консистентность: доставка и порядок
Сообщения не должны теряться и не должны менять порядок внутри одного чата.
Масштабируемость: 50M одновременных соединений
Архитектура должна горизонтально расти вместе с аудиторией.
Пример масштаба системы
3Выбор протокола коммуникации
Связанная глава
WebSocket Protocol
Подробный разбор WebSocket: handshake, keepalive, reconnect и практические рекомендации.
Сравнение подходов
| Подход | Задержка | Нагрузка на сервер | Лучший сценарий |
|---|---|---|---|
| HTTP Polling | Высокая | Очень высокая | Наследуемый запасной вариант |
| Long Polling | Средняя | Высокая | Простые уведомления |
| WebSocket ✓ | Минимальная | Оптимальная | Чаты и совместная работа в реальном времени |
| Server-Sent Events | Минимальная | Средняя | Однонаправленные уведомления |
Почему обычно выбирают WebSocket
- ✓Двусторонний канал: клиент и сервер могут отправлять сообщения в любой момент, а не ждать нового запроса.
- ✓Одно соединение на сессию: не нужно поднимать новый HTTP-запрос на каждое событие.
- ✓Меньше служебных накладных расходов: снижается объём лишнего трафика и нагрузка на серверный слой.
- ✓Предсказуемая модель доставки: проще держать онлайн-путь, подтверждения и переподключения под одним протоколом.
4Высокоуровневая архитектура
В рабочей архитектуре нужно отдельно описать, как связывает пользователя с конкретным WebSocket-сервером, а подсказывает, можно ли доставить сообщение сразу в открытую сессию или надо переключаться на офлайн-путь.
Чат-система: общая схема
маршрутизация соединений, хранение истории и офлайн-доставкаКонтур постоянных соединений
Контур хранения и офлайн-доставки
Опорная схема чат-системы: слой постоянных соединений, маршрутизация сообщений, долговременное хранение истории и отдельный путь офлайн-доставки.
Путь онлайн-доставки
- Пользователь A отправляет сообщение через WebSocket-соединение.
- WebSocket-шлюз передаёт событие в слой маршрутизации чатов.
- Реестр сессий показывает, на каком сервере сейчас держится пользователь B.
- Если сессия активна, сообщение сразу попадает получателю.
Путь офлайн-доставки
- Сообщение подтверждается и сохраняется в основном хранилище.
- Очередь доставки создаёт задачу на push-уведомление и повторные попытки.
- Push-сервис отправляет событие через APNS или FCM.
- После переподключения клиент догружает всё, что появилось после последней подтверждённой точки.
5Хранение сообщений
Хранение данных
Database Internals
Выбор между SQL и NoSQL зависит от паттернов записи, чтения и пагинации.
Сравнение подходов к хранению
| База данных | Плюсы | Минусы | Лучший сценарий |
|---|---|---|---|
| PostgreSQL | Транзакции и знакомый стек | Горизонтальное масштабирование даётся трудно | Небольшой масштаб |
| Cassandra ✓ | Хорошо держит запись и горизонтально растёт | Нужно аккуратно проектировать согласованность чтения и записи | Крупные чаты и мессенджеры |
| HBase | Wide-column модель и интеграция с Hadoop | Эксплуатационно сложнее | Очень крупные аналитические платформы |
Схема данных в Cassandra
-- Таблица сообщений (разделение по 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` так важен
- •Порядок сообщений: TIMEUUID или Snowflake ID помогают однозначно восстановить последовательность.
- •Пагинация: можно читать историю порциями вроде «дай сообщения до `message_id X`».
- •Идемпотентность: повторная доставка или ретрай не создают дубль.
- •Синхронизация: легко запросить все сообщения после последней известной точки.
6Онлайн-статус
Проверка активности
Обычно клиент отправляет каждые 5-30 секунд. Если система перестаёт его видеть, пользователя считают офлайн и больше не рассчитывают на мгновенную доставку в открытую сессию.
// Redis хранит отметку о последней активности
SET user:{user_id}:last_active {timestamp}
EXPIRE user:{user_id}:last_active 30
// Проверка онлайн-статуса
GET user:{user_id}:last_active
// Если ключ существует, пользователь считается онлайнПроблема массовой раздачи
Даже простое изменение статуса быстро превращается в , если у пользователя сотни контактов или он состоит во многих группах.
- •Подгружать статус только при открытии нужного чата, а не на весь список контактов сразу.
- •Объединять обновления в небольшие пачки вместо отправки каждого события по отдельности.
- •Делать push только для активных диалогов и действительно важных сценариев.
7Групповые чаты
Группы ломают наивную модель «одно сообщение — один получатель». Чем больше участников, тем чаще приходится отделять запись сообщения от его доставки и переходить к более событийной модели раздачи.
Как меняется архитектура с ростом группы
Прямая доставка по WebSocket остаётся простой и управляемой.
Лучше отделять запись сообщения от доставки и раскладывать её по фоновой обработке.
Нужна модель подписки на канал, а не персональная отправка каждому участнику.
Схема данных для групп
-- Группы
CREATE TABLE groups (
group_id UUID PRIMARY KEY,
name TEXT,
created_by UUID,
created_at TIMESTAMP
);
-- Участники группы (для быстрого поиска)
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, -- для счётчика непрочитанных
PRIMARY KEY ((user_id), group_id)
);8Синхронизация и офлайн-доставка
Синхронизация
Last-seen message ID
Паттерн last-seen message ID помогает быстро синхронизировать историю между устройствами.
Протокол синхронизации
Каждое устройство хранит `last_synced_message_id`. При новом подключении клиент сообщает эту точку, а сервер отдаёт всё, что появилось после неё.
- 1Клиент отправляет свой `last_synced_message_id`.
- 2Сервер возвращает все сообщения после этого идентификатора.
- 3Клиент применяет изменения и обновляет свою точку синхронизации.
Офлайн-очередь
Для пользователей вне сети полезно отделять долговременное хранение истории от , которая отвечает именно за отложенную доставку и повторные попытки.
-- Очередь непрочитанных сообщений
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 дней
-- При подключении пользователя
SELECT * FROM offline_messages WHERE user_id = ?;
-- После синхронизации доставленные записи удаляются9Масштабирование WebSocket-серверов
Главная сложность
WebSocket-соединения хранят состояние. Нельзя просто добавить ещё серверов за балансировщиком: нужно понимать, на каком узле сейчас живёт конкретный пользователь.
Реестр сессий
Централизованная таблица в Redis хранит соответствие пользователь → сервер, чтобы маршрутизация не зависела от случайного выбора балансировщика.
// При подключении пользователя
HSET user_sessions user_123 server_5
// При отправке сообщения
target_server = HGET user_sessions user_456
// При отключении
HDEL user_sessions user_123Связь между серверами
Для межсерверной доставки удобно использовать поверх Redis или Kafka, чтобы сервер-получатель сам забирал событие из нужного канала.
// Сервер 1 публикует сообщение
PUBLISH chat_server_5 {
"type": "message",
"to": "user_456",
"content": "Hello!"
}
// Сервер 5 получает событие и доставляет
// его через локальное WebSocket-соединениеЛипкие сессии как альтернатива
Балансировщик может использовать модель , то есть стараться возвращать пользователя на тот же сервер по IP или cookie. Но такой подход усложняет failover и перебалансировку, поэтому отдельный реестр сессий обычно надёжнее и понятнее.
10Ключевые моменты для интервью
Что обязательно проговорить
- •Почему здесь нужен WebSocket и где остаются запасные варианты.
- •Как сообщения маршрутизируются между серверами и как находится активная сессия получателя.
- •Как обеспечиваются порядок сообщений и понятные гарантии доставки.
- •Как устроены офлайн-синхронизация и пуш-уведомления.
- •Как масштабируется слой постоянных соединений.
Дополнительные темы
- •Сквозное шифрование и Signal Protocol.
- •Подтверждения прочтения и индикатор набора текста.
- •Хранение медиафайлов отдельно от текстовых сообщений, например через S3 и сеть доставки контента.
- •Лимиты и защита от спама.
- •Синхронизация между несколькими устройствами.
Типичные ошибки на интервью
- ✗Забыть, что WebSocket-соединения хранят состояние и поэтому не масштабируются «просто как HTTP».
- ✗Не продумать офлайн-сценарий, синхронизацию после переподключения и пуш-уведомления.
- ✗Игнорировать порядок сообщений при распределённой доставке между серверами.
- ✗Не обсудить, как меняется массовая доставка в больших групповых чатах и каналах.
Связанные главы
- Протокол WebSocket - углубляет транспортный слой мгновенной доставки и работу с долгоживущими соединениями.
- Notification System - показывает, как офлайн-доставка и пуш-уведомления дополняют чат, когда пользователь не держит активную сессию.
- Database Internals: A Deep Dive (short summary) - помогает обосновать выбор хранилища, схему сообщений и стратегию чтения истории.
- Twitter/X - даёт соседний кейс очень большого масштаба, где хорошо видно компромиссы массовой раздачи и доставки событий.
- Примеры задач по системному дизайну - помогает сравнить Chat System с другими кейсами и посмотреть, как меняются архитектурные компромиссы по доменам.
- System Design Interview: An Insider's Guide (краткий обзор) - содержит классический разбор чат-системы с понятной поэтапной структурой решения.
- Hacking the System Design Interview (краткий обзор) - дополняет главу практикой интервью-коммуникации и объяснением компромиссов в чатовом домене.
- Лэсли Лэмпорт: причинность, Paxos и инженерное мышление - укрепляет фундамент для разговора о порядке событий, причинности и согласованной доставке сообщений.
- Краткосрочная подготовка к интервью по системному дизайну - помогает упаковать решение чат-кейса в короткий и внятный формат для собеседования.
