Задача 4 из 10
Сервис уведомлений
Спроектируйте сервис, который рассылает пользователям уведомления по нескольким каналам: push, email и SMS — от «заказ доставлен» до маркетинговых рассылок.
Функциональные требования
- Отправка уведомления по событию из других сервисов
- Несколько каналов: push, email, SMS
- Настройки пользователя: какие уведомления и куда слать
- Повтор при сбое доставки
Нефункциональные требования
- 5 млн уведомлений в сутки
- Пик: рассылка на 1 млн получателей за 10 минут
- Транзакционные (код подтверждения) доставляются за секунды, маркетинговые — в течение часа
- Дубли недопустимы: пользователь не должен получить одно и то же дважды
- Внешние провайдеры периодически недоступны или отвечают по 30 секунд
Нарисуйте архитектуру
Не гонитесь за красотой: на собеседовании от схемы нужно, чтобы по ней было видно путь запроса и где лежат данные. Сначала нарисуйте сами — эталон и разбор ниже.
Блок из палитры — добавить. Тащите мышкой или пальцем. Двойной клик по названию — переименовать. «Связь» — щёлкнуть по одному блоку, потом по другому: получится стрелка.
Схема сохраняется в этом браузере — вкладку можно закрыть и вернуться позже.
1 С чего начать, если ступор
- Разделите приём события и доставку: приняли, положили в очередь, ответили источнику.
- Разные очереди для транзакционных и массовых — рассылка не должна задерживать код подтверждения.
- Дедупликация нужна по ключу события, а не по номеру попытки.
- Ретраи с экспоненциальной задержкой и dead-letter очередь для безнадёжных.
- Провайдер тоже лимитирует — понадобится ограничение частоты на исходящих.
2 Эталонная схема
Приняли событие — сразу в очередь и ответили источнику. Срочные и массовые разведены по разным очередям, чтобы рассылка на миллион не задерживала код подтверждения. Безнадёжные попытки уходят в dead-letter, а не крутятся вечно.
Это один из рабочих вариантов, а не единственно верный. Если у вас иначе, но вы можете объяснить почему — на собеседовании это ровно то, что нужно.
3 Разбор: что должно прозвучать
Приём и доставка разделены
- Синхронный вызов провайдера прямо в HTTP-запросе источника — главная ошибка в этой задаче
- API принимает событие, пишет его и кладёт в очередь; доставка живёт отдельно
- Источнику не важно, доставлено ли уже — ему важно, что событие принято
Приоритеты
- Отдельные очереди и отдельные воркеры для транзакционных и массовых
- Одна общая очередь означает, что рассылка на миллион задержит код подтверждения на часы
- Массовую рассылку имеет смысл размазывать по времени, а не вываливать разом
Идемпотентность и дубли
- Очередь даёт at-least-once: одно и то же сообщение придёт воркеру дважды
- Ключ дедупликации на уровне события (источник + тип + получатель + идентификатор)
- Отметка «доставлено» должна ставиться так, чтобы повтор её увидел
Ретраи и отказы провайдера
- Экспоненциальная задержка с джиттером: иначе все ретраи придут одной волной
- Потолок попыток и dead-letter очередь для разбора вручную
- Разделение ошибок: временные (повторяем) и постоянные (невалидный адрес — не повторяем)
Настройки пользователя
- Проверка согласий и отписок до постановки в очередь, а не в момент отправки
- Тихие часы и частотные ограничения на пользователя
- Отписка должна работать мгновенно — это ещё и юридическое требование
Куда копать дальше, если спросят
- Фолбэк между каналами: push не доставлен — шлём email
- Шаблоны, локализация и превью для маркетинга
- Метрики доставляемости и репутация отправителя у почтовых провайдеров
- Что делать, когда провайдер лежит час: копить в очереди или отбрасывать протухшее
Следующая задача: Лента новостей
Главный вопрос: раскладывать пост по лентам подписчиков при записи или собирать ленту при чтении. И что делать со звёздами.