Ключевые выводы
- Kafka не нужна для триггерных рассылок в CDP с базой до 700 тысяч профилей — outbox pattern на PostgreSQL закрывает задачу
- Outbox pattern обеспечивает exactly-once семантику без внешнего брокера сообщений
- PostgreSQL обрабатывает 3-5 тысяч событий в секунду — этого достаточно для 50-100 триггерных сценариев
- Меньше компонентов — проще эксплуатация, ниже TCO, меньше точек отказа в on-prem контуре
Триггерные рассылки — один из первых сценариев, который enterprise-команда хочет запустить после развёртывания CDP. Однако классическая архитектура с Kafka, Zookeeper и consumer-группами превращает «запуск первой рассылки» в отдельный инфраструктурный проект. В результате вместо бизнес-задачи команда тратит 2-3 месяца на обслуживание стека.
Мы считаем, что для on-prem CDP с базой до 700 тысяч клиентов это избыточно. Есть архитектурный приём, который решает ту же задачу на порядок проще — outbox pattern. Вот как он работает и почему подходит для enterprise-контура.
Почему Kafka — не первый инструмент для триггерных рассылок
Kafka проектировалась для обработки миллионов событий в секунду. Это мощный инструмент — но именно поэтому он привносит операционную сложность, которая не оправдана на старте.
Вот что появляется в инфраструктуре вместе с Kafka:
- Zookeeper или KRaft — координатор кластера, требует отдельных ресурсов и мониторинга
- Consumer groups — логика перебалансировки, offset management, повторная обработка
- Retention и compaction — настройка политик хранения, чистка топиков
- Минимум 3 брокера — для production-ready кластера с репликацией
Для enterprise-компании с on-prem контуром каждый дополнительный компонент — это ещё один объект для ИТ-сопровождения, мониторинга, резервного копирования и согласования с ИБ. Поэтому вопрос не «Kafka или не Kafka», а «какой минимальный стек решает задачу триггерных рассылок надёжно».
Outbox pattern: архитектура за 5 минут
Outbox pattern — это способ гарантированной доставки событий без внешнего брокера сообщений. Идея простая: событие записывается в таблицу-«исходящий ящик» в той же транзакции, что и бизнес-операция.
| Шаг | Что происходит | Где |
|---|---|---|
| 1 | Клиент совершает действие (покупка, регистрация, брошенная корзина) | Приложение / CDP |
| 2 | В одной транзакции: обновляется профиль + в таблицу outbox записывается событие | PostgreSQL |
| 3 | Worker (polling или LISTEN/NOTIFY) забирает новые события из outbox | Go-сервис |
| 4 | Worker выполняет сценарий: email, push, webhook, SMS | Канал коммуникации |
| 5 | Событие помечается как обработанное (или удаляется) | PostgreSQL |
Ключевое преимущество — атомарность. Поскольку бизнес-операция и запись события происходят в одной транзакции PostgreSQL, невозможна ситуация, когда профиль обновился, а событие потерялось. Или наоборот — событие отправлено, но данные не сохранились.
Гарантия доставки: exactly-once без Kafka
Скептики возражают: «Outbox — это at-least-once, а нам нужен exactly-once». Однако на практике exactly-once в outbox pattern достигается просто.
Вот три механизма, которые это обеспечивают:
- Идемпотентный обработчик. Каждое событие имеет уникальный
event_id. Worker проверяет, было ли оно уже обработано, прежде чем отправлять рассылку. Повторная доставка не приводит к дублированию - Транзакционный commit. Событие удаляется из outbox только после успешной отправки. Если worker упал — событие остаётся в таблице и будет обработано при следующем цикле
- Dead letter queue (DLQ). После N неудачных попыток событие перемещается в отдельную таблицу для ручного разбора. Это проще, чем настраивать DLQ-топик в Kafka
Для 95% триггерных сценариев в enterprise-CDP (welcome-цепочка, брошенная корзина, реактивация, day-N retention) идемпотентная обработка через outbox — это ровно тот уровень надёжности, который нужен.
Производительность: сколько событий выдерживает PostgreSQL
Типичный вопрос от CTO: «А не станет ли PostgreSQL узким местом?» Короткий ответ — нет, если речь о CDP с базой до 700 тысяч профилей.
Вот конкретные цифры для одного узла (8 vCPU, 32 ГБ RAM, NVMe SSD):
| Метрика | Значение | Комментарий |
|---|---|---|
| INSERT в outbox | 3 000-5 000/сек | С индексами, в рамках транзакции |
| Polling worker | 1 000-2 000 событий/цикл | Batch SELECT + DELETE, цикл 500 мс |
| Параллельные сценарии | 50-100 | Каждый — отдельный тип события |
| Латентность | 500 мс — 2 сек | От события до отправки рассылки |
Для сравнения: даже агрессивная триггерная стратегия в retail — 20 типов событий, 200 тысяч активных клиентов, 5 касаний в месяц на клиента — генерирует порядка 1 миллиона событий в месяц. Это ~0,4 события в секунду. PostgreSQL справляется без усилий.
Тем не менее если нагрузка выходит за 10 тысяч событий в секунду — это сигнал к подключению событийной шины (NATS или аналог) как опционального модуля. Подробнее о модульном масштабировании — в разделе Внедрение и развёртывание CDP.
Практика: как это работает в on-prem CDP
В архитектуре lean on-prem CDP outbox pattern встраивается естественно, потому что PostgreSQL уже является основной базой данных. Не нужен ни дополнительный сервис, ни отдельный кластер.
Вот как выглядит типичный сценарий «брошенная корзина» на outbox pattern:
- Событие
cart_abandonedзаписывается в outbox при превышении таймаута неактивности (30 минут) - Worker забирает событие, проверяет условия сценария (сумма корзины > 3 000 ₽, клиент не отписан)
- Проверка подавления: не было ли рассылки этому клиенту за последние 24 часа (frequency cap)
- Рендеринг шаблона с данными профиля и корзины
- Отправка через интеграцию (email, push, webhook на внутреннюю систему)
- Логирование результата в audit trail
Каждый шаг — внутри контура компании. Данные не покидают периметр. Сценарий настраивается через UI без необходимости писать код. При этом ИБ-команда видит полный audit trail каждого действия — от события до отправки.
Когда outbox pattern — не лучший выбор
Архитектурная честность важнее любого паттерна. Однако есть конкретные ситуации, когда outbox недостаточен:
- Fan-out на 5+ потребителей. Если одно событие должно одновременно уйти в email, push, SMS, аналитику и DWH — polling из одной таблицы становится неудобным. Здесь нужен pub/sub
- Реал-тайм требования < 100 мс. Outbox с polling даёт латентность 500 мс — 2 секунды. Для критичных по времени сценариев (fraud detection) нужен прямой event stream
- Более 10 000 событий в секунду. PostgreSQL выдержит, но начнёт конкурировать за ресурсы с основными запросами CDP. На этом масштабе стоит вынести события в отдельный компонент
Важный нюанс: все три ограничения возникают при масштабе, который для 90% enterprise-внедрений наступает не раньше второго-третьего года эксплуатации. На старте — outbox закрывает задачу полностью.
Подробнее о различиях подходов к архитектуре CDP — в нашем разборе On-prem vs SaaS CDP.
TCO: меньше компонентов — ниже стоимость владения
Финансовый аргумент прост. Каждый дополнительный компонент инфраструктуры — это не только лицензия или железо, но и:
- Операционные расходы — мониторинг, обновления, бэкапы
- Компетенции — команде нужен инженер, который понимает Kafka на production-уровне
- Время на согласование — ИБ должен проверить каждый новый компонент
- Точки отказа — чем больше элементов, тем выше вероятность инцидента
Отказ от Kafka в пользу outbox pattern экономит от 300 до 600 тысяч рублей в год на сопровождении инфраструктуры — при профиле Minimum (1 узел). А главное — сокращает time-to-value: первый триггерный сценарий запускается в тот же спринт, когда развернули CDP. Подробнее о стоимости — в разделе Независимость от вендора.
Итого
Триггерные рассылки — задача бизнеса, а не инфраструктуры. Outbox pattern позволяет запустить полноценные сценарии коммуникаций на базе PostgreSQL без развёртывания Kafka, Zookeeper или тяжёлого event-стека.
Для on-prem CDP с базой до 700 тысяч профилей и 50-100 сценариями — это архитектурно обоснованный выбор с доказанной производительностью. Когда нагрузка вырастет — событийная шина подключается как отдельный модуль, без переписывания архитектуры.
Если вы планируете запуск триггерных коммуникаций внутри контура и хотите оценить, какой профиль мощности подходит — запросите архитектурную консультацию. Разберём вашу нагрузку и покажем, как это работает на демо-стенде.
FAQ о триггерных рассылках
Можно ли запустить триггерные рассылки без Kafka?
Да. Outbox pattern с PostgreSQL обеспечивает гарантированную доставку событий без отдельного брокера. Для CDP с нагрузкой до 500-700 тысяч профилей этого достаточно — Kafka появляется только при масштабировании до миллионов событий в секунду.
Что такое outbox pattern и зачем он нужен в CDP?
Outbox pattern — это архитектурный приём, при котором событие записывается в специальную таблицу PostgreSQL в той же транзакции, что и бизнес-операция. Отдельный worker забирает события и отправляет их в канал коммуникации. Результат — атомарность и гарантия exactly-once обработки.
Какую нагрузку выдерживает outbox pattern без Kafka?
PostgreSQL с правильной индексацией обрабатывает 3-5 тысяч событий в секунду на одном узле. Для CDP с базой до 700 тысяч клиентов и 50-100 триггерных сценариев этого хватает с запасом.
Когда всё-таки нужна событийная шина вроде NATS или Kafka?
Когда количество событий превышает 10 тысяч в секунду, или когда требуется fan-out на 5+ потребителей одновременно. На практике это означает базу от 2-3 миллионов активных профилей с десятками параллельных сценариев.