Apache Kafka для системного аналитика — это не софт-скилл. Это хард-скилл, который спасает архитектуру.
Kafka для аналитика — это не «пусть разрабы разбираются». Это инструмент, с которым вы либо проектируете интеграции осмысленно, либо коллекционируете баги в продакшене. Примечательно, что на собеседованиях вопрос «как устроена Kafka» задают системным аналитикам всё чаще — и не для галочки. Когда вы пишете ТЗ на микросервис, который должен отправлять события о заказах, а получать их будет пяток других сервисов — вы обязаны понимать, кто кому что шлёт, как гарантируется порядок и что делать с ошибками.
Если событийная архитектура — это философия, то Kafka — это станок, на котором эта философия воплощается в железобетонные интеграции.
Что такое Apache Kafka и почему это не «просто очередь»
На первый взгляд — ну брокер сообщений, подумаешь. RabbitMQ тоже брокер. Но Kafka проектировалась не для «передал и забыл», а для того, чтобы быть центральной нервной системой компании. События в ней не исчезают после обработки — они хранятся. Часами, днями, неделями. Это значит, что новый сервис может прийти через полгода, прочитать всю историю заказов с самого начала и построить свою аналитику так, будто он был там с первого дня.
Apache Kafka — это распределённый лог событий. Продюсеры пишут в него сообщения, консьюмеры читают. Сообщения хранятся на диске, партицируются по ключу, реплицируются между нодами кластера.
В RabbitMQ сообщение после обработки удаляется. В Kafka оно лежит, пока не истечёт retention. Это принципиальная разница: Kafka — не столько очередь, сколько immutable log. Представьте бухгалтерский журнал, куда операции записываются подряд и никогда не затираются. Новый сотрудник открывает журнал и видит всю историю.
Терминология за 2 минуты
Kafka имеет свой понятийный аппарат, и путаница в терминах на встрече с разработчиками — классика. Давайте зафиксируем:
| Термин | Что это | Бытовая аналогия |
|---|---|---|
| Producer (продюсер) | Сервис, который публикует сообщения | Человек, вешающий объявление на доску |
| Consumer (консьюмер) | Сервис, который читает сообщения | Человек, читающий объявления |
| Topic (топик) | Именованная категория сообщений | Доска объявлений «Заказы» |
| Partition (партиция) | Часть топика, лежащая на конкретном диске | Ящик на доске с порядковым номером |
| Offset (офсет) | Порядковый номер сообщения внутри партиции | Номер строки в ящике |
| Consumer Group | Группа консьюмеров, делящих нагрузку | Отдел сотрудников, читающих одну доску |
| Broker | Нода Kafka, хранящая партиции | Сервер, на котором висит доска |
| Partition Key | Ключ, определяющий, в какую партицию попадёт сообщение | Фамилия, по которой объявление кладут в нужный ящик |
Как устроен кластер Kafka: продюсеры, брокеры, партиции, консьюмеры
Архитектуру проще всего понять через схему — кто куда пишет, кто откуда читает и куда деваются ошибки.
На схеме хорошо видно три главных принципа. Первое: продюсеры ничего не знают о консьюмерах. Order Service отправляет OrderCreated и идёт дальше — кто прочитает, когда прочитает, его не волнует. Второе: сообщения распределяются по партициям на основе Partition Key. Все события с одним orderId гарантированно попадают в одну и ту же партицию и обрабатываются строго по порядку — это критично для бизнес-логики. Третье: есть отдельный топик для ошибок — Dead Letter Queue (DLQ). Если сообщение после N попыток не обработалось, оно уходит туда, и инцидент-менеджер (или дежурный аналитик) разбирает его руками.
Как работает Partition Key: почему аналитик должен его выбирать
Когда разработчик спрашивает «по какому ключу партицировать?» — это не технический вопрос. Это аналитический.
Допустим, у вас есть топик orders. В него летят события OrderCreated и PaymentCompleted. Если в качестве ключа вы возьмёте случайный UUID — события одного заказа разлетятся по разным партициям. Тогда PaymentCompleted может обработаться раньше, чем OrderCreated — и платёжный сервис попытается списать деньги за ещё не существующий заказ.
Правильный ключ в этом примере — orderId. Тогда все события одного заказа попадают в одну партицию, и их порядок гарантирован. Это называется ordering guarantee — одно из главных преимуществ Kafka, но работает оно только при правильно выбранном ключе.
Вот чеклист для аналитика при выборе Partition Key:
- Бизнес-идентификатор —
orderId,userId,policyNumber. Никогда неtimestamp. - Покрывает все события сущности — если заказ проходит 5 стадий, все 5 событий должны иметь одинаковый ключ.
- Равномерное распределение — если у вас один VIP-клиент генерирует 90% событий, его партиция перегрузится (hot partition).
Последний пункт особенно важен. Знаю случай: в страховой компании в качестве ключа взяли insuranceType, и все события по ОСАГО улетали в одну партицию — потому что ОСАГО было 70% бизнеса. Результат: одна партиция дымилась, остальные четыре простаивали. Это не баг Kafka, это неправильное проектирование.
Путь сообщения: от продюсера до консьюмера с обработкой ошибок
Давайте посмотрим на полный жизненный цикл сообщения — включая failure-сценарий с ретраями и DLQ. Это та самая диаграмма, которую полезно вставлять в ТЗ на интеграцию, чтобы и разработчики, и тестировщики понимали нефункциональное поведение.
На диаграмме два принципиальных сценария. Успешный: Order публикует событие, Payment и Inventory обрабатывают параллельно, Notify отправляет письмо. Провальный: Inventory не может зарезервировать товар — делает ретрай с экспоненциальной задержкой (backoff), и после третьей неудачи сообщение уходит в DLQ.
Обратите внимание: Payment при этом ничего не знает о проблемах Inventory. Он списал деньги и опубликовал PaymentCompleted. А вот что с этим делать дальше — это уже задача Saga-паттерна, который мы разберём в отдельном посте (спойлер: придётся публиковать RefundPaymentRequested и компенсировать транзакцию).
Ретраи и идемпотентность: о чём обязательно писать в ТЗ
Механизм ретраев — это не «настроим потом». Это архитектурное решение, которое аналитик должен зафиксировать в требованиях. Что нужно описать:
- Максимальное количество попыток — обычно 3, реже 5.
- Стратегия задержки между попытками — фиксированная (1 секунда) или экспоненциальная (1с → 2с → 4с).
- Куда попадает сообщение после исчерпания ретраев — в DLQ.
- Идемпотентность консьюмера — критично. Если консьюмер обработал сообщение, но не смог закоммитить офсет (упал), брокер пришлёт то же сообщение ещё раз. Консьюмер должен понять, что это дубликат, и не списать деньги второй раз.
Последний пункт — про идемпотентность — я выделяю жирным, потому что это первое, что ломается в продакшене. И первое, что потом чинят в режиме «никому не говорим, быстро фиксим». Шаблон требования для ТЗ:
Требование KF-01: Консьюмер сервиса «Платежи» должен быть идемпотентным: повторная обработка сообщения с уже обработанным
eventIdне должна приводить к повторному списанию средств. Для проверки идемпотентности использовать таблицуprocessed_eventsс уникальным индексом поevent_id.
Да, звучит как техническая деталь. Но если аналитик этого не написал — разработчик может и не сделать. А потом платёжный сервис спишет с клиента дважды, и проблемы начнутся уже у юристов.
Топики и партиции: сколько и зачем
Сколько партиций нужно топику
Партиция — это юнит параллелизма в Kafka. Один консьюмер в группе читает одну или несколько партиций, но одну партицию читает ровно один консьюмер. Отсюда железное правило:
Максимальный параллелизм обработки топика = количеству его партиций. Если у вас 3 партиции — больше 3 консьюмеров в группе не имеют смысла: четвёртый будет простаивать.
Формула для оценки количества партиций:
partitions = max( expected_throughput / throughput_per_partition, max_consumers_in_group)То есть смотрим на ожидаемую нагрузку и делим на производительность одной партиции, а потом сравниваем с желаемым количеством консьюмеров — и берём максимум. На практике для большинства проектов 3–6 партиций хватает за глаза. 20 партиций для топика, куда приходит 10 сообщений в день — это оверинжиниринг, который усложняет мониторинг без пользы.
Retention и cleanup policy: как долго живут сообщения
По умолчанию сообщения в Kafka хранятся 7 дней. Но это конфигурируется:
- Time-based retention — «хранить N дней». Для аналитики часто ставят 30–90 дней.
- Size-based retention — «хранить, пока партиция не достигнет N гигабайт».
- Compaction — вместо удаления старых сообщений Kafka хранит по одному — последнему — для каждого ключа. Полезно для хранения текущего состояния (справочники, снапшоты).
Аналитику важно понимать, какой retention нужен для его сценария. Если вы проектируете интеграцию, где консьюмер может отключиться на месяц (например, партнёрский сервис) — retention должен это покрывать. Иначе после возвращения консьюмер обнаружит, что события уже удалены, и единственный способ наверстать — ручная переливка данных из бэкапа (и неделя нервотрёпки).
Практика: что аналитик должен зафиксировать в требованиях к Kafka-интеграции
Хватит теории. Вот минимальный набор артефактов, которые системный аналитик должен подготовить при проектировании интеграции через Kafka. Я за годы вывел для себя такой шаблон:
1. Реестр топиков
Для каждого топика опишите:
| Поле | Пример |
|---|---|
| Название | orders |
| Назначение | События жизненного цикла заказа |
| Ключ партицирования | orderId |
| Количество партиций | 5 |
| Retention | 7 дней |
| Cleanup policy | delete |
| Продюсеры | Order Service |
| Консьюмеры (consumer group) | payment-processors, inventory-processors, notification-service |
| Ожидаемый throughput | ~500 msg/sec пиковая нагрузка |
| Размер сообщения | ≤ 5 KB |
Это не техническая документация в духе Confluence-простыни на 40 страниц. Это одна таблица, которую читают и разработчик, и тестировщик, и девопс. Работает.
2. Схема сообщения (schema)
Обязательно зафиксируйте структуру каждого события. Пример для OrderCreated:
{ "eventId": "uuid", "eventType": "OrderCreated", "eventVersion": "1.0", "timestamp": "2026-06-12T10:00:00Z", "payload": { "orderId": 42, "customerId": 17, "items": [ {"sku": "ABC-1", "qty": 2, "price": 1500} ], "totalAmount": 3000, "currency": "RUB" }}Поля eventId, eventType, eventVersion и timestamp — обязательная обвязка каждого события. Это не прихоть, это то, на чём держится идемпотентность, трассировка и версионирование.
3. Требования к DLQ
Что писать в ТЗ про очередь мёртвых писем:
- Название DLQ-топика — обычно
<основной-топик>.DLQ, напримерorders.DLQ. - Retention DLQ — дольше основного, например 30 дней (чтобы успеть разобрать).
- Регламент обработки — кто мониторит DLQ, с какой периодичностью, по какому алерту.
- Формат «умершего» сообщения — оригинал + заголовки с причиной ошибки и количеством попыток.
Без регламента DLQ превращается в свалку, которую никто не читает. События копятся, место на диске тает, админ молча расширяет диски — и так до первой аварии.
4. Нефункциональные требования
Обязательный минимум:
- Гарантия доставки: at-least-once (стандарт для Kafka).
- Идемпотентность консьюмеров: обязательна (см. выше).
- Максимальная задержка обработки (SLA): например, 95-й перцентиль — не более 2 секунд.
- Ретраи: 3 попытки, exponential backoff 1с/2с/4с, затем DLQ.
- Мониторинг: consumer lag, throughput партиций, размер DLQ.
Kafka и архитектурные паттерны
Kafka редко живёт сама по себе — обычно она ядро событийной архитектуры. Связка с другими паттернами:
- CQRS: Kafka идеально подходит как транспорт для синхронизации write- и read-моделей. Write-сторона публикует событие → read-сторона обновляет проекцию.
- Event Sourcing: Kafka — это готовый event store. События пишутся в топик, состояние восстанавливается реплеем. Но осторожно: без Schema Registry и стратегии версионирования через год это превратится в неразгребаемый ком событий.
- Outbox Pattern: не пишите в Kafka из транзакции БД напрямую. Сначала сохраните событие в outbox-таблицу внутри той же транзакции, а отдельный процесс пусть читает outbox и публикует в Kafka. Иначе неизбежны ситуации «данные в БД есть, а событие потерялось» — и наоборот.
Отдельно скажу про выбор между монолитом и микросервисами в контексте Kafka. Если у вас модульный монолит — Kafka может использоваться для асинхронной коммуникации между модулями внутри одного приложения, и это абсолютно легитимный сценарий. Не надо думать, что Kafka = микросервисы. Модульный монолит с Kafka-шиной внутри — архитектура, которую я видел в продакшене не раз, и она прекрасно работала.
Когда Kafka не нужна
Это не серебряная пуля. Вот случаи, когда Kafka — избыточное решение:
- У вас меньше 100 событий в час. Поднимите RabbitMQ или вообще обойдитесь REST-вызовами. Kafka создавалась для throughput’а в сотни тысяч сообщений в секунду — на 10 сообщениях в час она похожа на карьерный самосвал, которым возят пакет молока.
- Вам нужен немедленный ответ. Kafka асинхронна. Если пользователь ждёт результат здесь и сейчас — это запрос-ответ, а не событие.
- Нет экспертизы в команде. Kafka сложнее, чем кажется. Если никто никогда не работал с ней в продакшене — начните с managed-решения (Confluent Cloud, AWS MSK), а не с self-hosted кластера из трёх нод, который упадёт в первый же день под нагрузкой.
- Одно событие — один консьюмер. Если вы публикуете событие строго для одного подписчика — это не EDA, это асинхронный RPC. Вам не нужна Kafka, вам нужна очередь.
Заключение
Kafka — это не софт-скилл и не «техническая деталь, которую пусть разрабы сами решают». Для системного аналитика это инструмент, без которого невозможно грамотно спроектировать асинхронные интеграции в современной системе. Партиции, топики, ключи, DLQ, schema, retention — это всё аналитические артефакты, просто записанные в YAML-конфигах, а не в Confluence-таблицах.
Если после этого поста вы пойдёте и допишете в своё ТЗ раздел «Требования к Kafka-интеграции» с реестром топиков и схемами сообщений — пост свою задачу выполнил. Если нет — ну, хотя бы на собеседовании не растеряетесь, когда спросят, чем команда отличается от события.
PS. Я намеренно не углублялся в настройку кластера, репликацию, ISR, ack-и и прочие вещи, которыми занимаются Kafka-админы. Аналитику важно понимать модель данных и контракты, а не unclean.leader.election.enable. Для погружения в админскую часть есть отличная документация на сайте Confluent — она написана людьми и для людей.