Multi-channel notification system (push, email, SMS, in-app) handling 100M DAU with 5.8K rps avg / 50K rps peak. Producers publish events to Kafka, orchestrator loads user preferences, dedups via Redis, renders templates, then dispatches to per-channel queues (q-push, q-email, q-sms). Channel workers (push/email/sms) post to APNs/FCM/SES/Twilio with retry-backoff and DLQ. Provider webhooks update ClickHouse for delivery status analytics.
Система принимает команды и доменные события, применяет пользовательские настройки, фиксирует durable intent и только затем публикует задания в каналы push, email и SMS. Главная гарантия — не «ровно одна доставка», а отсутствие потери принятого intent и идемпотентная обработка повторов.
Проектное допущение: 500 млн уведомлений в сутки. Средняя частота равна 500 000 000 / 86 400 ≈ 5 787 сообщений/с. Пик 50 тыс./с — отдельное допущение примерно 8,6× среднего; его нужно подтвердить реальными часовыми и минутными профилями. Каналы планируются отдельно: у SMS есть стоимость и региональные лимиты, у email — reputation и bounce policy, у push — provider/device ограничения.
Если сначала записать SET NX в Redis, затем упасть до публикации, повтор будет ошибочно отброшен, а уведомление потеряется. Поэтому Orchestrator одной транзакцией:
Outbox Relay читает непубликованные строки и публикует их at-least-once. Повторная публикация безопасна, потому что worker дедуплицирует notification_id + channel + attempt. Redis допустим как быстрый hint, но не как источник истины.
Kafka упорядочивает записи только в пределах partition и не даёт универсальной отложенной очереди или строгого приоритета «partition 0 раньше остальных». Поэтому:
DLQ replay выполняется только после исправления причины, с прежним notification_id и контролируемым новым attempt. Слепой replay может повторно списать деньги или создать шторм.
Ответ APNs на HTTP/2 POST сообщает, принят или отклонён конкретный запрос. Для FCM возвращённый message ID также означает acceptance, а не доказанную доставку на устройство. Поэтому push worker пишет accepted_by_provider или rejected. Device delivery может оцениваться отдельной opt-in телеметрией FCM/клиента, но это другой, часто задержанный сигнал.
Amazon SES публикует delivery, bounce, complaint и другие события. Twilio отправляет status callbacks; они могут прийти с разной задержкой и не гарантированно по порядку. Status Service проверяет подпись, дедуплицирует provider + event_id и применяет только допустимые монотонные переходы. Неизвестное поле callback не должно ломать endpoint.
Preferences Store хранит opt-in/opt-out, locale, quiet hours и разрешённые каналы. Критические исключения должны быть явно определены продуктом и правом, а не обходить настройки по умолчанию. Template Service экранирует данные и сохраняет version, чтобы повтор воспроизводил тот же контент.
Durable push intent, outbox, provider acceptance и отдельная best-effort доставка на устройство.
Fan-out из одного доменного события в независимые каналы.
Retryable throttling через delay tiers, ограниченный retry budget и per-channel DLQ.
Повтор команды возвращает исходный notification_id благодаря уникальности в базе.
Отдельная critical SMS lane с зарезервированной пропускной способностью.
Введите числа или выберите пресет