Comparison of three messaging brokers side-by-side: Kafka (log-based, partitioned, dumb broker + smart consumer with offset tracking), RabbitMQ (smart broker with topic exchange routing to multiple queues, push delivery, DLX for rejects), and Pulsar (stateless broker + Apache BookKeeper segmented storage with E=3/W=2/A=2 quorum, tiered S3 offload for cold segments, multiple subscription types). Includes 4 scenarios: Kafka log fan-out via consumer groups with replay, RabbitMQ topic exchange routing with DLX, Pulsar segmented storage and broker failover, and ADR decision matrix walkthrough.
Выбор брокера сообщений — одно из самых дорогих архитектурных решений. Миграция между Kafka, RabbitMQ и Pulsar занимает 6-12 месяцев для команды из 5 инженеров: переписать producer'ы и consumer'ы, продумать durability/ordering, перенести retention/replay, поменять observability, переучить SRE. Stripe в 2018 описывал внутреннюю миграцию брокеров как «больно». Поэтому выбирать надо не по хайпу, а по свойствам нагрузки.
Разные брокеры закрывают разные семантики:
Использовать Kafka как RPC-очередь — антипаттерн (latency 5-15 ms vs 0.5 ms у RabbitMQ). Использовать RabbitMQ для триллионов событий в день — невозможно физически (Erlang scheduler ceiling).
«Kafka — дамп лога с указателями consumer'ов. RabbitMQ — умный postman с маршрутизацией. Pulsar — stateless broker + распределённое хранилище. NATS — минимальный pub/sub с опциональным persistence.»
Главный водораздел проходит по тому, где живёт интеллект:
seek(offset=0). Fan-out = N consumer groups читают тот же топик с независимыми курсорами.tenant → namespace → topic. Старые сегменты автоматически offload'ятся в S3.Три региона side-by-side плюс ADR-панель:
KAFKA (верх-лево). Producers (order-svc, user-svc) пишут в топик events с 3 партициями (P0/P1/P2), каждая со своим лидером и репликами (ISR). Две consumer groups: Group A (analytics) с тремя consumer'ами 1
RABBITMQ (верх-право). Publisher order-svc шлёт в topic exchange order.events. Exchange по биндингам разводит на три queue: q.shipping (order.*.created), q.billing (order.*.paid), q.audit (order.# — все). Отдельная DLX queue для rejected. Каждая queue — свой consumer с prefetch=50. После ack сообщение удаляется навсегда.
PULSAR (низ). Producer пишет в брокер-1 (stateless dispatcher, не хранит данные). Брокер пишет в BookKeeper с конфигом E=3 / W=2 / A=2 (Ensemble=3 bookies, Write quorum=2, Ack quorum=2) — striping по ledger'ам. Старые сегменты (>7 дней) offload'ятся в S3 автоматически. Три subscription'а демонстрируют гибридность: Exclusive (log/replay), Shared (queue/round-robin), KeyShared (sticky by key). Broker-2 в standby — failover за <1 сек, потому что данных в брокере нет.
ADR-панель в центре резюмирует решение и подсвечивает анти-кейсы.
Scenario 1: Kafka log fan-out. Производитель считает hash(orderId)%3 = 0, шлёт в P0. Broker аппендит на offset 9876, делает fsync, ждёт acks=all (после ISR-репликации). Дальше одно и то же сообщение независимо вычитывают Group A (analytics) и Group B (alerting) — у каждой свой offset в __consumer_offsets. Завтра нашли баг — Group A перезапускают с auto.offset.reset=earliest, и она перечитывает всё с offset=0 за retention окно (типично 7 дней). Group B при этом ничего не замечает.
Scenario 2: RabbitMQ exchange routing + DLX. Один basic.publish с routing_key=order.42.created — и broker сам матчит binding patterns: order.*.created MATCH (shipping), order.# MATCH (audit), order.*.paid SKIP. Producer не знает, куда поехало. Consumer shipping-worker получает push (prefetch=50, никакого polling), обрабатывает за 3 ms, делает basic.ack — сообщение удалено навсегда. Через секунду — order.42.paid, и роутинг уже другой: billing + audit. Billing-worker не смог charge'нуть карту → basic.nack(requeue=false) → DLX-policy → сообщение в q.dead-letter для ops.
Scenario 3: Pulsar segmented storage. Producer спрашивает proxy: «кто владеет acme/orders/events?» — broker-1. Broker-1 — stateless dispatcher, открывает текущий ledger в BookKeeper. Пишет entry в E=3 bookies, ждёт W=2 ack'ов (любые 2 из 3) — striping по ledger'ам, не по партициям. Любой быстрый кворум подтверждает producer'у; третий bookie догоняет async. Сегменты старше 7 дней прозрачно offload'ятся в S3 ($0.02/GB vs $0.10/GB SSD). Если consumer seek'ает на 30-дневный offset — broker сам подтянет сегмент из S3 (медленнее, но возможно). При падении broker-1 → broker-2 берёт ownership за <1 сек, потому что данные в BookKeeper, не в брокере — это и есть архитектурный win compute/storage decoupling.
Scenario 4: ADR walkthrough. Decision matrix: Kafka — для replay/throughput/event-sourcing/CDC. RabbitMQ — для routing/RPC/work-queue с low latency. Pulsar — для multi-tenant SaaS / geo-replication / разделения compute и storage. Анти-кейсы: Kafka как RPC (request-topic + response-topic + correlation_id = 10-50 ms + сложность), Pulsar в команде <5 SRE (brokers + BookKeeper + ZK = три слоёных системы).
ADR-001: Event log vs queue vs hybrid — выбор по нагрузке, не по хайпу.
| Аспект | Kafka | RabbitMQ | Pulsar |
|---|---|---|---|
| Throughput per node | 100-300 MB/s NVMe | 20-50K msg/s | 50-200 MB/s |
| Latency p99 | 5-15 ms | <1 ms (classic) / 5 ms (quorum) | 5-10 ms |
| Persistence | always (disk) | optional (durable + persistent) | always (BookKeeper) |
| Ordering | per-partition strict | per-queue best-effort | per-partition / per-key |
| Routing | none (subscribe) | rich (exchanges + DLX) | sub types |
| Replay | first-class (offset reset) | требует Streams (3.9+) | first-class |
| Multi-tenancy | manual ACL | vhosts | first-class (tenant/ns/topic) |
| Geo-replication | MirrorMaker 2 (ручная) | Federation (ограниченная) | built-in |
| Operational complexity | medium (KRaft проще ZK) | low-medium | high (BK + ZK отдельно) |
| Storage model | local disk monolithic partition | local disk | segmented + tiered to S3 |
Главные осознанные жертвы каждого:
Стоимость ошибки: Stripe-style internal migration = 6-12 месяцев для команды из 5 инженеров. Это порядок дороже, чем потратить неделю на нормальный benchmark и ADR заранее.
correlation_id = 10-50 ms latency + дикая сложность. Возьмите gRPC.user_id), ordering сохраняется per-key.prefetch_count. Consumer хапает миллионы сообщений в RAM → OOM. Всегда ставьте prefetch=50-500.Kafka НЕ берите если: latency p99 должна быть <5 ms; нужна сложная маршрутизация на стороне broker'а; нужен RPC-паттерн; работает worker pool / job queue из 1K-100K сообщений; нет data engineering команды.
RabbitMQ НЕ берите если: нужен replay (consumer удалил bug в коде, хочет переобработать неделю данных); нужно >100K msg/s sustained; event sourcing / CDC / audit log; долгое retention (>1 день стандартно неудобно).
Pulsar НЕ берите если: команда <5 SRE; нет требования на multi-tenancy или geo-replication; достаточно managed Kafka. Operational сложность Pulsar не оправдана для типичного use-case Kafka.
NATS НЕ берите если: нужны Kafka-уровни durability/throughput; нужен real schema registry; нужны сложные routing'и через broker; работаете с long-retention аналитическими pipeline'ами.