Logo
Overview

Apache Kafka для системного аналитика: брокеры сообщений, топики, партиции

June 12, 2026
11 min read

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: продюсеры, брокеры, партиции, консьюмеры

Архитектуру проще всего понять через схему — кто куда пишет, кто откуда читает и куда деваются ошибки.

100%
graph TD
  subgraph Producers["Продюсеры источники сообщений"]
      A["Order Service"]
      B["Payment Service"]
      C["User Service"]
  end

  subgraph Kafka["Apache Kafka Cluster"]
      T["topic orders 5 партиций"]
      P0["Partition 0"]
      P1["Partition 1"]
      P2["Partition 2"]
      P3["Partition 3"]
      P4["Partition 4"]
  end

  subgraph Consumers["Консьюмеры consumer group"]
      D["Inventory Service"]
      E["Notification Service"]
      F["Analytics Service"]
  end

  subgraph DLQ["Dead Letter Queue"]
      DL["topic orders DLQ"]
  end

  A -->|"OrderCreated key orderId"| P0
  A -->|"OrderCancelled key orderId"| P1
  B -->|"PaymentCompleted key orderId"| P2
  C -->|"UserUpdated key userId"| P3

  P0 --> D
  P1 --> D
  P2 --> E
  P3 --> F
  P4 --> D

  D -.->|"ошибка после N ретраев"| DL

  style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style P0 fill:#f0a500,stroke:#c88400,color:#fff
  style P1 fill:#f0a500,stroke:#c88400,color:#fff
  style P2 fill:#f0a500,stroke:#c88400,color:#fff
  style P3 fill:#f0a500,stroke:#c88400,color:#fff
  style P4 fill:#f0a500,stroke:#c88400,color:#fff
  style D fill:#50c878,stroke:#3a9a5c,color:#fff
  style E fill:#50c878,stroke:#3a9a5c,color:#fff
  style F fill:#50c878,stroke:#3a9a5c,color:#fff
  style DL fill:#e0e0e0,stroke:#999,color:#333

На схеме хорошо видно три главных принципа. Первое: продюсеры ничего не знают о консьюмерах. 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:

  1. Бизнес-идентификаторorderId, userId, policyNumber. Никогда не timestamp.
  2. Покрывает все события сущности — если заказ проходит 5 стадий, все 5 событий должны иметь одинаковый ключ.
  3. Равномерное распределение — если у вас один VIP-клиент генерирует 90% событий, его партиция перегрузится (hot partition).

Последний пункт особенно важен. Знаю случай: в страховой компании в качестве ключа взяли insuranceType, и все события по ОСАГО улетали в одну партицию — потому что ОСАГО было 70% бизнеса. Результат: одна партиция дымилась, остальные четыре простаивали. Это не баг Kafka, это неправильное проектирование.

Путь сообщения: от продюсера до консьюмера с обработкой ошибок

Давайте посмотрим на полный жизненный цикл сообщения — включая failure-сценарий с ретраями и DLQ. Это та самая диаграмма, которую полезно вставлять в ТЗ на интеграцию, чтобы и разработчики, и тестировщики понимали нефункциональное поведение.

100%
sequenceDiagram
  participant Order as Order Service
  participant Broker as Kafka Broker
  participant Payment as Payment Service
  participant Inventory as Inventory Service
  participant Notify as Notification Service
  participant DLQ as Dead Letter Queue

  Order->>Broker: publish OrderCreated orderId 42 items ...
  Broker-->>Payment: consume OrderCreated partition 0 offset 15
  Payment->>Payment: валидация списание средств
  Payment->>Broker: publish PaymentCompleted orderId 42 amount 5000

  Broker-->>Inventory: consume OrderCreated partition 0 offset 15
  Inventory->>Inventory: резервирование товара
  Inventory->>Broker: publish StockReserved orderId 42 items ...

  Broker-->>Notify: consume OrderCreated partition 0 offset 15

  rect rgb(255, 230, 230)
      Note over Broker,Inventory: failure path товар не найден
      Broker-->>Inventory: consume OrderCreated offset 16 retry 1
      Inventory->>Inventory: товар всё ещё не найден
      Inventory-->>Broker: retry backoff 2s
      Broker-->>Inventory: consume OrderCreated offset 16 retry N
      Inventory-xDLQ: после 3 ретраев отправить в DLQ
  end

  rect rgb(230, 255, 230)
      Note over DLQ,Notify: success path уведомление
      Notify->>Notify: формирование письма
      Notify->>Broker: publish NotificationSent orderId 42
  end

На диаграмме два принципиальных сценария. Успешный: 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
Retention7 дней
Cleanup policydelete
Продюсеры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 — избыточное решение:

  1. У вас меньше 100 событий в час. Поднимите RabbitMQ или вообще обойдитесь REST-вызовами. Kafka создавалась для throughput’а в сотни тысяч сообщений в секунду — на 10 сообщениях в час она похожа на карьерный самосвал, которым возят пакет молока.
  2. Вам нужен немедленный ответ. Kafka асинхронна. Если пользователь ждёт результат здесь и сейчас — это запрос-ответ, а не событие.
  3. Нет экспертизы в команде. Kafka сложнее, чем кажется. Если никто никогда не работал с ней в продакшене — начните с managed-решения (Confluent Cloud, AWS MSK), а не с self-hosted кластера из трёх нод, который упадёт в первый же день под нагрузкой.
  4. Одно событие — один консьюмер. Если вы публикуете событие строго для одного подписчика — это не EDA, это асинхронный RPC. Вам не нужна Kafka, вам нужна очередь.

Заключение

Kafka — это не софт-скилл и не «техническая деталь, которую пусть разрабы сами решают». Для системного аналитика это инструмент, без которого невозможно грамотно спроектировать асинхронные интеграции в современной системе. Партиции, топики, ключи, DLQ, schema, retention — это всё аналитические артефакты, просто записанные в YAML-конфигах, а не в Confluence-таблицах.

Если после этого поста вы пойдёте и допишете в своё ТЗ раздел «Требования к Kafka-интеграции» с реестром топиков и схемами сообщений — пост свою задачу выполнил. Если нет — ну, хотя бы на собеседовании не растеряетесь, когда спросят, чем команда отличается от события.

PS. Я намеренно не углублялся в настройку кластера, репликацию, ISR, ack-и и прочие вещи, которыми занимаются Kafka-админы. Аналитику важно понимать модель данных и контракты, а не unclean.leader.election.enable. Для погружения в админскую часть есть отличная документация на сайте Confluent — она написана людьми и для людей.