Системы уведомлений
В последние годы система уведомлений стала очень популярной функцией во многих приложениях. Уведомления сообщают пользователю важную информацию: срочные новости, обновления продукта, события, предложения и многое другое. Они стали неотъемлемой частью повседневной жизни. В этой главе нужно спроектировать систему уведомлений.
Уведомление — это не только push на мобильное устройство. Мы будем рассматривать три формата: mobile push notification, SMS и email. На рисунке 1 показан пример каждого из них.
Рисунок 1
Шаг 1 — Понять задачу и определить рамки дизайна
Построить масштабируемую систему, которая рассылает миллионы уведомлений в день, непросто. Для этого нужно хорошо понимать всю экосистему уведомлений. Такой вопрос на интервью специально делается открытым и неоднозначным, поэтому именно кандидат должен задавать вопросы и прояснять требования.
Кандидат: Какие типы уведомлений должна поддерживать система?
Интервьюер: Push notification, SMS message и email.
Кандидат: Это система реального времени?
Интервьюер: Будем считать, что это soft real-time система. Мы хотим, чтобы пользователь
получал уведомления как можно быстрее. Однако при высокой нагрузке небольшая задержка допустима.
Кандидат: Какие устройства поддерживаются?
Интервьюер: iOS, Android и laptop/desktop.
Кандидат: Что инициирует уведомления?
Интервьюер: Уведомления могут инициироваться клиентскими приложениями, а также
планироваться на серверной стороне.
Кандидат: Смогут ли пользователи отказаться от уведомлений?
Интервьюер: Да, если пользователь выбрал opt-out, он больше не должен их получать.
Кандидат: Сколько уведомлений отправляется в день?
Интервьюер: 10 миллионов mobile push, 1 миллион SMS и 5 миллионов email.
Шаг 2 — Предложить высокоуровневый дизайн и согласовать его
В этом разделе показан высокоуровневый дизайн, который поддерживает разные типы уведомлений: iOS push, Android push, SMS и email. Раздел состоит из следующих частей:
-
Разные типы уведомлений
-
Поток сбора контактной информации
-
Поток отправки и получения уведомлений
Разные типы уведомлений
Начнем с того, как на высоком уровне работает каждый вид уведомлений.
iOS push notification

Рисунок 2
Чтобы отправить push-уведомление на iOS, нам в первую очередь нужны три компонента:
-
Provider. Провайдер собирает и отправляет запросы в Apple Push Notification Service (APNS). Для формирования push-уведомления он передает такие данные:
-
Device token: уникальный идентификатор для доставки push-уведомлений.
-
Payload: JSON-словарь с содержимым уведомления. Пример:
{
"aps":{
"alert":{
"title":"Game Request",
"body":"Bob wants to play chess",
"action-loc-key":"PLAY"
},
"badge":5
}
}
-
APNS: удаленный сервис Apple, который доставляет push-уведомления на iOS-устройства.
-
iOS Device: конечное устройство, получающее push-уведомления.
Android push notification
Для Android поток похожий. Вместо APNs обычно используется Firebase Cloud Messaging (FCM).

Рисунок 3
SMS message
Для SMS обычно используют сторонние сервисы вроде Twilio [1], Nexmo [2] и других. В основном это коммерческие сервисы.

Рисунок 4
Email
Хотя компании могут поднять собственные email-серверы, многие предпочитают коммерческие сервисы. Sendgrid [3] и Mailchimp [4] — одни из самых популярных решений, предлагающие более высокий delivery rate и аналитику.

Рисунок 5
На рисунке 6 показан дизайн после добавления всех сторонних сервисов.

Рисунок 6
Поток сбора контактной информации
Чтобы отправлять уведомления, нам нужно собирать device token, номера телефонов и email-адреса. Как показано на рисунке 7, когда пользователь устанавливает приложение или регистрируется впервые, API-серверы собирают его контактные данные и сохраняют их в базе данных.

Рисунок 7
На рисунке 8 показаны упрощенные таблицы БД для хранения контактной информации. Email-адреса и телефоны хранятся в таблице user, а device token — в таблице device. У одного пользователя может быть несколько устройств, а значит, push-уведомление может быть отправлено на все его устройства.
Рисунок 8
Поток отправки и получения уведомлений
Сначала представим начальный вариант дизайна, а затем предложим его улучшения.
Высокоуровневый дизайн
На рисунке 9 показан дизайн, а ниже объясняется каждый компонент.

Рисунок 9
Service 1 to N: это могут быть микросервисы, cron-задачи или распределенные системы, инициирующие события отправки уведомлений. Например, billing service рассылает email-напоминания об оплате, а интернет-магазин отправляет SMS, что посылка будет доставлена завтра.
Notification system: центральный компонент отправки и получения уведомлений. Начнем с простого
варианта, в котором используется только один notification server. Он предоставляет API для
сервисов 1..N и формирует payload для сторонних сервисов.
Third-party services: отвечают за доставку уведомлений пользователям. При интеграции с ними важно уделять внимание extensibility. Хорошая extensibility означает, что систему можно гибко расширять, легко подключая и отключая сторонние сервисы. Еще один важный момент — сторонний сервис может быть недоступен в некоторых странах или перестать быть доступным в будущем. Например, FCM недоступен в Китае, поэтому там используют альтернативы вроде Jpush и PushY.
iOS, Android, SMS, Email: пользователи получают уведомления на своих устройствах.
У такого дизайна есть три проблемы:
-
Single point of failure (SPOF): один notification server означает единую точку отказа.
-
Система плохо масштабируется: один сервер обрабатывает все, что связано с уведомлениями, и становится трудно независимо масштабировать БД, кэши и разные компоненты обработки.
-
Узкое место по производительности: обработка и отправка уведомлений могут требовать много ресурсов. Например, сборка HTML-писем и ожидание ответа от third-party services занимают время. Если все свалено в одну систему, она может перегружаться, особенно в пиковые часы.
Улучшенный высокоуровневый дизайн
После того как мы перечислили проблемы исходного варианта, улучшим дизайн:
-
Вынесем базу данных и кэш из notification server.
-
Добавим больше notification server и настроим автоматическое горизонтальное масштабирование.
-
Введем message queues, чтобы ослабить связность компонентов.
На рисунке 10 показан улучшенный высокоуровневый дизайн.

Рисунок 10
Эту схему удобнее читать слева направо.
Service 1 to N: это разные сервисы, которые отправляют уведомления через API, предоставленные notification servers.
Notification servers: выполняют следующие функции:
-
Предоставляют API для отправки уведомлений. Эти API доступны только внутри системы или проверенным клиентам, чтобы защититься от спама.
-
Выполняют базовую валидацию email, телефонов и т. д.
-
Запрашивают из базы данных или кэша информацию, необходимую для рендеринга уведомления.
-
Кладут данные уведомлений в message queues для параллельной обработки.
Пример API для отправки email:
POST https://api.example.com/v/sms/send
Request body:
{
"to":[
{
"user_id":123456
}
],
"from":{
"email":"from_address@example.com"
},
"subject":"Hello World!",
"content":[
{
"type":"text/plain",
"value":"Hello, World!"
}
]
}
Cache: в кэше хранятся user info, device info и шаблоны уведомлений.
DB: хранит данные о пользователях, уведомлениях, настройках и т. д.
Message queues: снимают жесткие зависимости между компонентами. Очереди также выступают в роли буфера, когда нужно отправить большие объемы уведомлений. Для каждого типа уведомлений используется отдельная очередь, поэтому сбой в одном стороннем сервисе не влияет на другие типы уведомлений.
Workers: набор серверов, которые читают события уведомлений из очередей и отправляют их в соответствующие third-party services.
Third-party services: уже были описаны в исходном дизайне.
iOS, Android, SMS, Email: тоже уже были описаны выше.
Теперь посмотрим, как все компоненты работают вместе:
-
Один из сервисов вызывает API notification servers для отправки уведомления.
-
Notification servers получают метаданные: user info, device token и notification settings — из кэша или базы данных.
-
Событие уведомления отправляется в соответствующую очередь. Например, iOS push notification попадает в iOS PN queue.
-
Workers считывают события из message queues.
-
Workers отправляют уведомления в third-party services.
-
Third-party services доставляют уведомления на устройства пользователей.
Шаг 3 — Детальный разбор дизайна
В высокоуровневом дизайне мы обсудили разные типы уведомлений, поток сбора контактной информации и поток отправки/получения уведомлений. Теперь подробно рассмотрим:
-
Надежность
-
Дополнительные компоненты и важные соображения: notification template, notification settings, rate limiting, retry mechanism, security в push notifications, мониторинг очередей и event tracking
-
Обновленный дизайн
Надежность
При проектировании системы уведомлений в распределенной среде нужно ответить на несколько важных вопросов о надежности.
Как избежать потери данных?
Одно из ключевых требований к системе уведомлений — она не должна терять данные. Уведомления можно задерживать или переупорядочивать, но нельзя терять. Чтобы выполнить это требование, система сохраняет данные уведомлений в базе данных и реализует механизм повторной отправки. Для долговременного хранения используется notification log database, как показано на рисунке 11.
Рисунок 11
Получит ли пользователь уведомление ровно один раз?
Короткий ответ — нет. Хотя в большинстве случаев уведомление действительно доставляется ровно один раз, распределенная природа системы может приводить к дубликатам. Чтобы уменьшить их число, мы добавляем dedupe mechanism и аккуратно обрабатываем каждый сценарий отказа.
Простейшая dedupe-логика такова: когда приходит новое notification event, мы проверяем, видели ли
мы уже этот event ID. Если да, событие отбрасывается. Если нет — уведомление отправляется.
Подробнее о том, почему exactly-once delivery недостижима, можно прочитать в материале [5].
Дополнительные компоненты и соображения
Мы уже обсудили, как собирать контактные данные пользователей, отправлять и получать уведомления. Но полноценная система уведомлений этим не ограничивается. Ниже разберем дополнительные части: повторное использование шаблонов, пользовательские настройки, event tracking, мониторинг системы, rate limiting и т. д.
Notification template
Крупная система уведомлений рассылает миллионы уведомлений в день, и многие из них имеют похожий формат. Notification templates позволяют не собирать каждое уведомление с нуля. Шаблон уведомления — это предформатированная заготовка, которая настраивается через параметры, стили, tracking links и т. д. Пример push notification template:
BODY:
You dreamed of it. We dared it. [ITEM NAME] is back — only until [DATE].
CTA:
Order Now. Or, Save My [ITEM NAME]
Плюсы шаблонов:
-
Единый и согласованный формат
-
Меньше вероятность ошибок
-
Экономия времени
Notification setting
Обычно пользователи и так получают слишком много уведомлений каждый день и быстро устают от них. Поэтому многие сайты и приложения дают тонкую настройку каналов уведомлений. Эта информация хранится в таблице notification settings со следующими полями:
user_id bigInt
channel varchar # push notification, email or SMS
opt_in boolean # opt-in to receive notification
Перед отправкой любого уведомления система сначала проверяет, подписан ли пользователь на этот тип уведомлений.
Rate limiting
Чтобы не перегружать пользователей слишком большим количеством уведомлений, можно ограничивать число уведомлений, которое они получают. Это важно, потому что в противном случае пользователи могут просто полностью отключить уведомления.
Retry mechanism
Если third-party service не смог отправить уведомление, оно возвращается в message queue для повторной попытки. Если проблема сохраняется, разработчикам отправляется alert.
Безопасность push-уведомлений
Для iOS- и Android-приложений для защиты API push-уведомлений используются appKey и
appSecret [6]. Только аутентифицированные или верифицированные клиенты могут отправлять push
через наши API.
Мониторинг очередей уведомлений
Одна из ключевых метрик — общее количество уведомлений в очередях. Если оно слишком велико, значит workers не успевают обрабатывать события достаточно быстро. Чтобы не допустить задержки в доставке, нужно увеличивать число workers. На рисунке 12 показан пример графика количества queued messages.
Рисунок 12
Event tracking
Метрики уведомлений — например open rate, click rate и engagement — очень важны для понимания поведения пользователей. Analytics service отвечает за сбор таких событий. Обычно требуется тесная интеграция notification system с analytics service. На рисунке 13 показан пример событий, которые можно отслеживать.
Рисунок 13
Обновленный дизайн
Если собрать все вместе, получится обновленный дизайн notification system на рисунке 14.

Рисунок 14
По сравнению с предыдущим вариантом здесь появилось много новых компонентов:
-
Notification servers получили еще две критически важные функции: authentication и rate limiting.
-
Мы добавили retry mechanism для обработки сбоев. Если системе не удалось отправить уведомление, оно возвращается в очередь сообщений, а workers пробуют снова ограниченное число раз.
-
Notification templates обеспечивают единообразный и эффективный процесс создания уведомлений.
-
Monitoring и tracking помогают контролировать здоровье системы и находить будущие улучшения.
Шаг 4 — Подведем итоги
Уведомления незаменимы, потому что держат нас в курсе важной информации. Это может быть push о любимом фильме на Netflix, email о скидке на новые товары или сообщение с подтверждением оплаты в интернет-магазине.
В этой главе мы описали дизайн масштабируемой системы уведомлений, поддерживающей несколько форматов: push notification, SMS и email. Мы использовали message queues, чтобы ослабить связи между компонентами системы.
Помимо высокоуровневого дизайна мы глубже разобрали ряд дополнительных компонентов и оптимизаций:
-
Reliability: предложили надежный retry mechanism для снижения числа сбоев.
-
Security: используем пару
AppKey/AppSecret, чтобы только проверенные клиенты могли отправлять уведомления. -
Tracking и monitoring: добавляем их на разных этапах потока уведомлений, чтобы собирать важные метрики.
-
Respect user settings: если пользователь выбрал opt-out, система сначала проверяет его настройки и только потом решает, отправлять ли уведомление.
-
Rate limiting: пользователи оценят ограничение частоты уведомлений.
Поздравляем, вы дошли до этого места! Можно похлопать себя по плечу. Отличная работа!
Справочные материалы
[1] Twilio SMS: https://www.twilio.com/sms
[2] Nexmo SMS: https://www.nexmo.com/products/sms
[3] Sendgrid: https://sendgrid.com/
[4] Mailchimp: https://mailchimp.com/
[5] You Cannot Have Exactly-Once Delivery:
https://bravenewgeek.com/you-cannot-have-exactly-once-delivery/
[6] Security in Push Notifications:
https://cloud.ibm.com/docs/event-notifications?topic=event-notifications-en-push-apns
[7] Key metrics for RabbitMQ monitoring:
https://www.datadoghq.com/blog/rabbitmq-monitoring/