Система уведомлений усложняется не в момент отправки одного сообщения, а тогда, когда одно событие нужно надёжно разложить по многим пользователям, каналам и устройствам.
Глава помогает связать очереди, шаблоны, пользовательские настройки, защиту внешних провайдеров и идемпотентность в один рабочий контур доставки, который выдерживает пики нагрузки.
В интервью и инженерных обсуждениях этот кейс полезен тем, что заставляет явно выбрать гарантию доставки, стратегию повторных попыток и границы допустимой деградации.
Бюджет задержек
Для каждого канала нужно заранее определить допустимое время доставки и поведение системы, если канал перестаёт в него укладываться.
Распределение по каналам
То, как одно событие раскладывается по получателям и каналам, напрямую влияет на масштабируемость и цену всей системы.
Состояние пользователя
Нужно явно учитывать подключённые устройства, quiet hours, порядок доставки и актуальные пользовательские настройки.
Мягкая деградация
При перегрузке или сбое внешнего провайдера система должна терять второстепенное качество, а не весь канал доставки.
Система уведомлений перестаёт быть просто набором каналов в тот момент, когда одно событие нужно корректно доставить многим пользователям, в несколько каналов и на разные устройства с учётом приоритета, предпочтений и состояния каждого канала. В хорошем решении мало перечислить push, email и SMS: нужно выбрать гарантию доставки, стратегию повторных попыток, защиту внешних провайдеров и понятный путь деградации.
Chapter 7
Acing SDI: Notification
Разбор кейса в книге Zhiyong Tan
Типы уведомлений
Push на мобильных устройствах
APNs для iOS и FCM для Android. Доставка возможна даже когда приложение закрыто.
Транзакционные и маркетинговые письма. SendGrid, SES, Mailgun.
SMS
Twilio, Vonage. 2FA и критические оповещения.
Уведомления в приложении
Доставка внутри приложения и через WebSocket, пока пользователь онлайн.
Требования
Функциональные
- FR1Отправка push-уведомлений, email, SMS и уведомлений внутри приложения
- FR2Поддержка шаблонов с персонализацией
- FR3Настройки предпочтений пользователя
- FR4Отложенная отправка по расписанию
- FR5Ограничение частоты и пакетирование похожих уведомлений
Нефункциональные
- NFR110 млн+ уведомлений в день
- NFR2Доставка в пределах нескольких секунд
- NFR3Доставка по модели at-least-once с дедупликацией
- NFR4Доступность 99,9%
Архитектура верхнего уровня
Архитектура верхнего уровня
Цветом выделены каналы доставки: Push (синий), Email (зелёный), SMS (янтарный)
Бизнес-сервисы
Создают события
Сервис уведомлений
Маршрутизация и шаблоны
Очередь сообщений
Kafka / SQS
Push-воркеры
APNs + FCM
Email-воркеры
SendGrid / SES
SMS-воркеры
Twilio / Vonage
APNs
Apple
FCM
SendGrid / SES
Почтовые провайдеры
Twilio
SMS-провайдер
Apple Push Notification Service (APNs)
Архитектура APNs
APNs работает поверх постоянного HTTP/2-соединения и поддерживает аутентификацию по сертификату или токену.
Провайдер → APNs → устройство
POST /3/device/<device_token>
Ключевые особенности
- Device Token — уникальный токен устройства, который может меняться
- Payload — JSON-пакет до 4 KB с полями alert, badge и sound
- Priority — 10 для немедленной доставки или 5 для режима энергосбережения
- Expiration — TTL для устройств, которые сейчас не в сети
- Collapse ID — позволяет заменить предыдущее уведомление той же группы
Пример payload для APNs
{
"aps": {
"alert": {
"title": "New Message",
"body": "You have a new message from John"
},
"badge": 5,
"sound": "default",
"mutable-content": 1 // для Notification Service Extension
},
"custom_data": {
"conversation_id": "abc123"
}
}Firebase Cloud Messaging (FCM)
Архитектура FCM
FCM поддерживает Android, iOS и web-клиенты. Для отправки используется HTTP v1 API с аутентификацией через OAuth 2.0.
POST /v1/projects/<project_id>/messages:send
Типы сообщений
- Notification — сообщение, которое FCM показывает автоматически
- Data — сообщение, которое приложение обрабатывает самостоятельно
- Hybrid — оба типа в одном сообщении
Пример запроса FCM HTTP v1
{
"message": {
"token": "device_registration_token",
"notification": {
"title": "New Order",
"body": "Your order #1234 has been shipped"
},
"data": {
"order_id": "1234",
"click_action": "OPEN_ORDER_DETAIL"
},
"android": {
"priority": "high",
"ttl": "86400s"
},
"apns": {
"headers": {
"apns-priority": "10"
}
}
}
}WebSocket
Chat System
Доставка в реальном времени через WebSocket
Управление токенами устройств
Управление токенами устройств — критически важная часть системы. Токены могут меняться, устройства уходят офлайн, а у одного пользователя часто бывает несколько устройств и клиентских приложений.
Таблица реестра устройств
| Колонка | Тип | Описание |
|---|---|---|
| user_id | BIGINT | Ссылка на пользователя |
| device_token | VARCHAR(255) | Токен APNs или FCM |
| platform | ENUM | ios, android, web |
| app_version | VARCHAR(20) | Версия приложения для совместимости и поэтапного запуска функций |
| last_active_at | TIMESTAMP | Помогает очищать неактуальные токены |
| created_at | TIMESTAMP | Дата регистрации |
Важно: обрабатывайте обратную связь от APNs и FCM о недействительных токенах. Регулярно очищайте неактуальные токены устройств, которые давно не появлялись в системе.
Настройки пользователя
Уровни настроек
- Global — включить или выключить все уведомления
- Channel — управлять push, email и SMS по отдельности
- Category — разделять маркетинговые, транзакционные и социальные уведомления
- Quiet Hours — задавать тихие часы и временные ограничения
Ограничение частоты
- Лимит N уведомлений в час или день
- Объединение похожих уведомлений в один пакет
- Отдельная приоритетная очередь для критических оповещений
Архитектура очередей сообщений
Kafka или SQS развязывает сервисы по времени и помогает сохранить понятную гарантию доставки. Для каждого канала обычно выделяют свой топик и отдельную группу потребителей.
Топики: ├── notifications.push.ios → Воркеры push-канала (APNs) ├── notifications.push.android → Воркеры push-канала (FCM) ├── notifications.email → Email-воркеры ├── notifications.sms → SMS-воркеры ├── notifications.websocket → Воркеры WebSocket └── notifications.dlq → Dead Letter Queue Партиционирование: по user_id для порядка в рамках одного пользователя Хранение: 7 дней (для повторных попыток и разбора инцидентов)
Rate Limiting
Rate Limiter
Защита внешних провайдеров от перегрузки
Гарантии доставки
At-Most-Once
Отправили и забыли. Самый простой подход.
⚠️ Сообщения могут теряться
At-Least-Once
Повторные попытки с подтверждением. Требует дедупликации.
✓ Подходит для большинства практических случаев
Exactly-Once
Идемпотентный ключ плюс паттерн transactional outbox.
Сложно, дорого и нужно нечасто
Стратегия дедупликации
// Идемпотентный ключ = hash(user_id + notification_type + content_hash + date)
Redis SET with TTL:
SETNX notification:dedupe:{idempotency_key} 1 EX 86400
Если ключ уже существует → отправку пропускаемПовторные попытки и обработка ошибок
Стратегия повторных попыток
- Exponential Backoff: 1s → 2s → 4s → 8s...
- Max Retries: 3-5 попыток
- Jitter: случайное смещение, чтобы размазать нагрузку
- Circuit Breaker: при массовых ошибках у провайдера
Классификация ошибок
- Повторяемые: 429, 503, таймаут сети
- Неповторяемые: 400, 401, недействительный токен
- DLQ: после исчерпания повторных попыток
Patterns
Release It!
Circuit Breaker и другие паттерны устойчивости
Масштабирование
Горизонтальное масштабирование
• Stateless-воркеры — автомасштабирование по глубине очереди
• Разбиение по user_id для сохранения порядка доставки одному пользователю
• Отдельные пулы воркеров для каждого канала
Ограничения провайдеров
• APNs: HTTP/2-мультиплексирование, около 4000 запросов в секунду на соединение
• FCM: до 600 тыс. сообщений в минуту на платных планах
• Используйте пул соединений
Массовые уведомления: для рассылки на миллионы пользователей используйте FCM Topics или раскладывайте отправку через Kafka с пакетной обработкой.
Наблюдаемость
Метрики
- Отправленные и неуспешные уведомления по каждому каналу
- Задержка доставки (p50, p99)
- Глубина очереди и отставание потребителей
- Доля ошибок у внешних провайдеров
Логи
- Структурированные логи с корреляционным идентификатором
- Коды ответов провайдеров
- Применённые пользовательские настройки
Оповещения
- Всплеск ошибок
- Рост очереди
- Деградация внешнего провайдера
Что важно проговорить на интервью
Что показать в ответе
• Чем отличаются APNs и FCM
• Как устроен жизненный цикл device token
• Когда достаточно at-least-once и зачем нужна дедупликация
• Как защищать внешних провайдеров лимитами запросов
• Где применять пользовательские настройки и quiet hours
Типичные дополнительные вопросы
• Как сохранить порядок уведомлений для одного пользователя?
• Как обработать массовую рассылку на 10 млн пользователей?
• Как избежать дублирующихся уведомлений?
• Как приоритизировать критические оповещения?
Связанные главы
- Chat System - даёт прикладной сценарий доставки в реальном времени, где уведомления внутри приложения и WebSocket становятся частью основного пользовательского потока.
- Событийно-ориентированная архитектура: Event Sourcing, CQRS, Saga - объясняет асинхронную модель обработки событий, на которой обычно строят конвейер доставки уведомлений.
- Distributed Message Queue - углубляет устройство очередей, партиционирование и гарантии доставки для масштабной рассылки уведомлений.
- Rate Limiter - нужен для защиты внешних провайдеров, таких как APNs, FCM, SMS- и email-шлюзы, от всплесков нагрузки и злоупотреблений.
- Паттерны устойчивости: Circuit Breaker, Bulkhead, Retry - дополняет подходы к повторным попыткам, DLQ и мягкой деградации при сбоях каналов доставки.
- Протокол WebSocket - покрывает канал доставки внутри приложения через постоянное соединение и обновления с низкой задержкой.
- Release It! (short summary) - даёт практики инженерной устойчивости для надёжной доставки уведомлений в рабочей системе.
