Teu progresso
0 / 83 módulos0%
Estágio 04 · 04-02
BloqueadoSistema síncrono escala até dor: latência cresce com cada hop, falha encadeia. Messaging desacopla. Mas escolher errado é caro: Kafka pra workload trivial vira sledgehammer; RabbitMQ pra event log de altíssima volume estrangula; SQS sem partições força workarounds. Cada broker tem semantics próprias, durability, ordering, delivery guarantees, ack model, e treats de operação distintos.
Este módulo dissecca os principais brokers (Kafka, RabbitMQ, NATS, SQS) com profundidade técnica: como armazenam, como replicam, como deliver, custos operacionais. Você sai sabendo escolher e operar.
LinkedIn 2011, Apache. Distributed log:
__consumer_offsets).ZooKeeper-less (KRaft mode, 3.3+, default 3.5+): Kafka usa Raft próprio em vez de ZK.
Replication:
acks=all espera ISR todos.min.insync.replicas: garante mínimo de ISR ack pra writes serem durables.Retention: dias, semanas, ou compactado (último valor por key, tipo "table"). Compaction permite Kafka como source of truth de state.
Throughput: clusters pequenos sustentam centenas de MB/s; grandes (LinkedIn) vão pra TB/s.
Quando usar:
Quando não:
EOS via:
Mesmo assim, "EOS" requer acordos consumer-side (idempotente em sink). Sempre verifique.
AMQP-based, 2007. Mais flexível em routing.
Acks: consumer confirma processamento; sem ack, message volta a queue (após disconnect ou requeue manual).
DLQ (Dead Letter Queue): messages com falha repetida ou TTL expirado vão pra outra queue.
Mirroring / Quorum Queues: replication. Quorum queues (3.8+) usam Raft, mais robustas.
Throughput: dezenas-centenas de milhares msgs/s. Não sustent multi-MB/s sustained como Kafka.
Quando usar:
Quando não:
Lightweight, fast, simples. Pub/sub puro, request/reply, opcionalmente JetStream pra durability.
Subjects: hierárquicos (orders.created, orders.>). Wildcards.
Throughput: alto (hundreds of MB/s+ em clusters pequenos).
Quando usar:
Managed queue service.
Visibility Timeout: ao receive, message ficou invisible por T; se processada e deleted, OK; senão, volta visible.
DLQ: configura Redrive Policy.
Quando usar:
Quando não:
Em 2025-2026 esses três viraram alternativas sérias a Kafka pra cenários específicos. Vale conhecer pra escolher consciente.
Arquitetura separada de storage e serving: brokers stateless, BookKeeper (Apache) é o storage layer (segments). Brokers podem cair sem perder dados.
Quando vale Pulsar:
Trade-off:
Kafka-compatible binary-único em C++. Sem JVM, sem ZooKeeper, sem KRaft. Implementação from-scratch do protocolo Kafka.
Quando vale Redpanda:
Trade-off:
NATS Core é pub/sub efêmero ultra-rápido. JetStream (2021+) adiciona persistence, streams + consumers + retention.
orders.> consome todo subtree. Padrão poderoso pra event taxonomy.Quando vale NATS JetStream:
nats-server single binary, CLI excelente, latência sub-ms.Trade-off:
| Caso | Escolha 1 | Escolha 2 | Por quê |
|---|---|---|---|
| High-throughput pipeline (>500k msg/s sustentado) | Kafka | Redpanda | Ecossistema vs latency |
| Multi-tenant SaaS B2B com isolation | Pulsar | Kafka + custom | Multi-tenancy nativa |
| Microservices internos, latency-sensitive | NATS JetStream | Redpanda | Simplicidade / DX |
| AWS-native, ops zero | SQS / Kinesis | EventBridge | Managed |
| Event sourcing com long retention barata | Pulsar (tiered) | Kafka + tiered | Tiered storage built-in |
| IoT/edge | NATS | MQTT brokers | Leaf nodes, footprint baixo |
| Você já tem time com expertise Kafka | Kafka | Redpanda | Reusar conhecimento |
Implicação: ordenação global entre eventos é raro (e caro). Ordene dentro de uma chave (orderId, userId).
Como em 04-01: at-most, at-least, "exactly-once processing".
Kafka transactions ajudam, mas você ainda precisa idempotência.
Producer rápido + consumer lento = build-up.
Kafka: log retention; older messages descartadas conforme política. RabbitMQ: queue cresce; max-length policy ou quorum queue overflow. SQS: dequeue rate limit; messages ficam até 14 dias.
App-level: respeitar lag; alertar; scale consumer; throttle producer.
Eventos sem schema versioned → consumers quebram quando producer evolui.
Sem schema, eventos viram contrato implícito frágil.
Problema clássico: app escreve em DB + emite evento. Se app crashar entre os dois, inconsistency.
Solução resumida: na mesma transação DB, escreva em tabela outbox. Worker separado lê e publica no broker. CDC (Debezium lendo Postgres logical replication slot) é a alternativa zero-touch pelo lado da aplicação.
Aqui interessa o lado messaging: garantia de at-least-once end-to-end exige outbox + idempotência no consumer (§2.10). Sem outbox, "publish após commit" perde eventos em crash; "publish antes do commit" emite eventos fantasma.
Padrão completo (semantics, falhas, projeções, integração com saga) é dono de 04-03 §2.8. Aqui é só o gancho com broker.
Kafka: max consumers em group = partitions. Pra mais paralelismo, mais partitions (cuidado com over-partition).
RabbitMQ: múltiplos consumers em mesma queue dividem messages (round-robin).
SQS: muitos consumers concorrem.
Workers idle vs busy: monitore lag (Kafka) ou queue depth (RabbitMQ/SQS).
Mensagem que sempre falha bloqueia consumer ou repete forever. DLQ separa pra investigação humana.
Operar Kafka self-managed é trabalho. Use Confluent Cloud, MSK, Aiven, Redpanda Cloud.
Tudo broker tem custo de:
Em sistemas onde acoplamento síncrono é OK (1-3 services, latency budget tight), HTTP direto vence.
Eventos significam: "logo, o sistema vai estar coerente". UX precisa lidar com:
Read-your-writes UX pattern: após escrita, mostra optimistic UI imediato; backend confirma async.
"Exactly-once delivery" não existe em sistemas distribuídos (FLP impossibility). O que existe é "at-least-once delivery + idempotent consumer = effectively exactly-once". Sem idempotência, retry de broker (RabbitMQ requeue, Kafka rebalance, SQS visibility timeout) processa mesma mensagem 2x → cobrança duplicada, email duplicado, estoque negativo. Esta seção entrega 4 strategies em código pra Logística + decision tree.
Foundation: por que retries acontecem:
Strategy 1: Idempotency table (canonical):
CREATE TABLE processed_messages (
message_id TEXT PRIMARY KEY,
processed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
result JSONB
);
CREATE INDEX ON processed_messages (processed_at); -- pra cleanup
async function handleMessage(msg: Message) {
await db.transaction(async (tx) => {
const existing = await tx.processedMessages.findFirst({
where: { messageId: msg.id },
});
if (existing) {
log.info({ msgId: msg.id }, 'duplicate, skipping');
return existing.result;
}
const result = await processOrderEvent(msg.payload, tx);
await tx.processedMessages.insert({
messageId: msg.id,
result,
});
return result;
});
}
DELETE FROM processed_messages WHERE processed_at < now() - INTERVAL '30 days' em cron. Sem cleanup, table cresce infinito.Strategy 2: Natural idempotency via UPSERT/conditional update:
// Para messages cujo state é absorvable
async function handleStatusChange(msg: { orderId: string; toStatus: string; eventTime: number }) {
// UPSERT com condição: só atualiza se evento é mais recente
await db.execute(sql`
UPDATE orders
SET status = ${msg.toStatus},
status_updated_at = to_timestamp(${msg.eventTime})
WHERE id = ${msg.orderId}
AND status_updated_at < to_timestamp(${msg.eventTime})
`);
}
Strategy 3: Inbox pattern (transactional dedup + handoff):
-- Inbox table: receives messages, decoupled from processing
CREATE TABLE inbox (
message_id TEXT PRIMARY KEY,
payload JSONB NOT NULL,
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
processed_at TIMESTAMPTZ,
attempts INT NOT NULL DEFAULT 0,
last_error TEXT
);
CREATE INDEX ON inbox (processed_at) WHERE processed_at IS NULL;
// Receiver: insert atomic; commit ack só após insert ok
async function receiveMessage(msg: Message) {
try {
await db.inbox.insert({
messageId: msg.id,
payload: msg.payload,
});
await msg.ack();
} catch (err) {
if (isUniqueViolation(err)) {
await msg.ack(); // duplicate; já temos
return;
}
await msg.nack();
}
}
// Processor: separate worker, FOR UPDATE SKIP LOCKED
async function processInbox() {
while (true) {
const rows = await db.execute(sql`
SELECT message_id, payload FROM inbox
WHERE processed_at IS NULL
ORDER BY received_at
LIMIT 10
FOR UPDATE SKIP LOCKED
`);
for (const row of rows) {
try {
await handlePayload(row.payload);
await db.inbox.update(row.messageId, {
processedAt: new Date(),
});
} catch (err) {
await db.inbox.update(row.messageId, {
attempts: sql`attempts + 1`,
lastError: String(err),
});
}
}
await sleep(100);
}
}
FOR UPDATE SKIP LOCKED: 10 workers paralelos sem step on each other.Strategy 4: External side effect com saga compensação:
async function handlePaymentEvent(msg: PaymentEvent) {
return db.transaction(async (tx) => {
const existing = await tx.processedMessages.findFirst({
where: { messageId: msg.id },
});
if (existing) return existing.result;
// Step 1: idempotent external call com idempotency key
const charge = await stripe.paymentIntents.create({
amount: msg.amount,
currency: 'brl',
customer: msg.customerId,
}, {
idempotencyKey: msg.id, // Stripe garante: 2x mesma key = mesma resposta
});
// Step 2: persist + record processed atomic
await tx.payments.insert({ orderId: msg.orderId, stripeId: charge.id });
await tx.processedMessages.insert({
messageId: msg.id,
result: { stripeId: charge.id },
});
return { stripeId: charge.id };
});
}
msg.id ou hash determinístico de payload.Decision tree:
| Cenário | Strategy |
|---|---|
| State update (status, counter, set) | Strategy 2 (UPSERT condicional) |
| Side effect interno (DB write only) | Strategy 1 (idempotency table) |
| Receiver overwhelmed por processing time | Strategy 3 (inbox pattern, fast ack) |
| External API com state change | Strategy 4 (idempotency key + Strategy 1) |
| Multiple consumers competing | Strategy 1 + Strategy 3 com FOR UPDATE SKIP LOCKED |
DLQ + poison messages handling:
const MAX_ATTEMPTS = 5;
async function processWithRetry(msg: Message) {
try {
await handleMessage(msg);
await msg.ack();
} catch (err) {
const attempts = (msg.headers['x-attempts'] as number) ?? 0;
if (attempts >= MAX_ATTEMPTS) {
await sendToDLQ(msg, err);
await msg.ack(); // ack original; DLQ é separate queue
alertOps({ msgId: msg.id, err, attempts });
return;
}
await msg.nack({
requeue: true,
headers: { ...msg.headers, 'x-attempts': attempts + 1 },
});
}
}
Logística — Order Outbox + Inbox end-to-end:
API Order Service:
POST /orders → DB transaction:
INSERT orders (...)
INSERT outbox (event_type='OrderCreated', payload, message_id=uuid())
Outbox Relay (Debezium/polling):
SELECT FROM outbox WHERE published=false → publish to Kafka → mark published
Notification Service Consumer (Kafka):
Receive msg → Strategy 3 (inbox) → ack Kafka
Worker reads inbox (FOR UPDATE SKIP LOCKED):
Strategy 4 → call Twilio with idempotencyKey=message_id → mark processed
Anti-patterns observados:
message_id derivado de timestamp: 2 mensagens em mesmo ms colidem. Use UUID v7 (timestamp-prefixed) ou ULID.Cruza com 04-02 §2.14 (DLQ foundation), 04-03 §2.8 (outbox pattern producer-side), 04-04 §2.4 (idempotency em retry geral), 04-09 §2.20 (backpressure em consumer), 04-13 §2.16 (exactly-once semantics em pipelines analíticos).
Versions: Kafka 3.7+, Kafka Streams 3.7+, ksqlDB 0.29+ (Confluent Platform). Apache Flink é alternativa para workloads que excedem JVM single-process (stateful aggregations >100GB, complex CEP).
Quando usar stream processing (vs simple consumer):
Kafka Streams architecture:
source → filter → groupBy → aggregate → sink).application.id.processing.guarantee=exactly_once_v2 (Kafka 2.5+, recomendado em 3.7+).Stream vs KTable mental model:
SELECT key, last(value) GROUP BY key.Windowing fundamentals:
TimeWindows.of(Duration.ofMinutes(5)) → 0–5min, 5–10min.TimeWindows.of(Duration.ofMinutes(5)).advanceBy(Duration.ofMinutes(1)) → janelas de 5min deslizando 1min.SessionWindows.with(Duration.ofMinutes(30)) → agrupa eventos dentro de 30min entre si.grace(Duration.ofMinutes(5)) para tolerar late events sem drop silencioso.Pattern Logística — orders/min per tenant (windowed count):
StreamsBuilder builder = new StreamsBuilder();
KStream<String, OrderEvent> orders = builder.stream("orders.events");
KTable<Windowed<String>, Long> ordersPerMinute = orders
.filter((k, v) -> v.getType().equals("OrderPlaced"))
.map((k, v) -> KeyValue.pair(v.getTenantId(), v))
.groupByKey()
.windowedBy(TimeWindows.of(Duration.ofMinutes(1)).grace(Duration.ofMinutes(5)))
.count(Materialized.as("orders-per-minute-store"));
ordersPerMinute.toStream()
.map((wk, count) -> KeyValue.pair(
wk.key() + "_" + wk.window().start(),
new MetricEvent(wk.key(), wk.window().start(), count)
))
.to("dashboard.orders-per-minute");
Stream-Stream join (windowed) — order placed + courier assigned dentro de 5min:
KStream<String, OrderEvent> orders = builder.stream("orders.events");
KStream<String, AssignmentEvent> assigns = builder.stream("assignments.events");
orders
.selectKey((k, v) -> v.getOrderId())
.join(
assigns.selectKey((k, v) -> v.getOrderId()),
(order, assign) -> new EnrichedOrder(order, assign),
JoinWindows.of(Duration.ofMinutes(5)).grace(Duration.ofMinutes(1))
)
.to("orders.enriched");
Co-partitioning obrigatório: ambos topics com mesmo número de partitions e mesma partitioning strategy (key hash). Mismatch → silent data loss.
Stream-KTable join (always-on enrichment) — courier profile mutável:
KTable<String, CourierProfile> couriers = builder.table("couriers.state");
orders
.selectKey((k, v) -> v.getCourierId())
.join(couriers, (order, profile) -> new OrderWithCourier(order, profile))
.to("orders.with-courier");
KTable lookup é local (RocksDB); zero network hop. Updates em couriers.state propagam via changelog.
ksqlDB — SQL on streams: high-level abstraction sobre Kafka Streams; SQL-like syntax para streaming queries. CREATE STREAM (event log) vs CREATE TABLE (compacted, latest-per-key).
CREATE STREAM orders_stream (
order_id VARCHAR KEY,
tenant_id VARCHAR,
status VARCHAR,
price_cents BIGINT,
created_at TIMESTAMP
) WITH (KAFKA_TOPIC='orders.events', VALUE_FORMAT='JSON');
CREATE TABLE tenant_revenue_per_hour AS
SELECT tenant_id,
WINDOWSTART AS hour,
SUM(price_cents) AS revenue_cents,
COUNT(*) AS order_count
FROM orders_stream
WINDOW TUMBLING (SIZE 1 HOUR, GRACE PERIOD 5 MINUTES)
WHERE status = 'delivered'
GROUP BY tenant_id
EMIT CHANGES;
EMIT CHANGES (push, contínuo) vs EMIT FINAL (apenas quando window fecha; menos data, mais latência). Pull queries (SELECT * FROM tenant_revenue_per_hour WHERE tenant_id='X') vs push queries (EMIT CHANGES): pull queries têm latency 50–200ms, evitar em hot path.
Exactly-once em Kafka Streams (EOS v2):
processing.guarantee=exactly_once_v2 no StreamsConfig.Logística applied stack:
orders.events, assignments.events, tracking.pings, couriers.state (compacted).tenant_revenue_per_minute (windowed agg).enriched_orders (stream-KTable join com courier profile).late_delivery_alerts (filter ETA exceeded → alert topic).Anti-patterns observados:
EMIT CHANGES + serve from materialized view.windowedBy(...).grace(Duration.ofMinutes(5)).num.standby.replicas=1.Cruza com 04-13 (streaming/batch, dbt + lakehouse alternative), 04-02 §2.18 (idempotent consumer para side effects externos), 03-13 (analytics DBs, ksqlDB pull queries vs ClickHouse), 04-09 (scaling, Kafka Streams horizontal via partitions), 04-04 (resilience, EOS v2 transactional guarantees).
O landscape de brokers mudou estruturalmente entre 2024 e 2026. Kafka 4.0 (Q1 2025) marcou KRaft GA e remoção definitiva do ZooKeeper (KIP-833), trouxe share groups (KIP-932) que adicionam consumo queue-like ao Kafka — competindo direto com RabbitMQ em cenários transacionais — e Tiered Storage GA (KIP-405) que separa hot data (SSD broker) de cold data (S3/GCS), reduzindo TCO 60–80% em tópicos com retenção >7d. NATS JetStream 2.10+ (Q1 2024, estável em 2.10.20+ Q3 2025) consolidou KV bucket e ObjectStore como primitivas first-class, viabilizando NATS como stack único pra core messaging + state. RabbitMQ 4.0 (Q3 2024) removeu mirrored queues (deprecated desde 3.8), tornou quorum queues (Raft-based) o default e introduziu Streams 4 super streams (partitioned streams) — RabbitMQ deixou de ser só broker AMQP pra competir em event log. Escolher broker em 2026 sem entender essas mudanças é decidir com mapa de 2020.
KRaft cluster (controller.quorum.voters) — sem ZooKeeper:
# server.properties (Kafka 4.0 — KRaft mode, combined controller+broker)
process.roles=broker,controller
node.id=1
controller.quorum.voters=1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
inter.broker.listener.name=PLAINTEXT
controller.listener.names=CONTROLLER
log.dirs=/var/kafka-logs
# Tiered Storage (KIP-405 GA Kafka 4.0)
remote.log.storage.system.enable=true
remote.log.storage.manager.class.name=org.apache.kafka.server.log.remote.storage.S3RemoteStorageManager
remote.log.storage.manager.impl.prefix=rsm.config.
rsm.config.s3.bucket.name=fathom-kafka-tier
rsm.config.s3.region=sa-east-1
# Per-topic: hot 24h local, cold S3 até 90d
# kafka-configs.sh --alter --topic order_events \
# --add-config remote.storage.enable=true,local.retention.ms=86400000,retention.ms=7776000000
KRaft cluster sobe ~5x mais rápido que ZooKeeper-backed (controller election em <500ms vs 2–5s). Mínimo 3 controllers pra quorum (tolera 1 falha); 5 pra tolerar 2.
Share groups (KIP-932) — queue-like consumption, per-message ack, sem partition affinity:
// Kafka 4.0 — Share consumer (queue semantics, alternativa ao RabbitMQ)
Properties props = new Properties();
props.put("bootstrap.servers", "kafka:9092");
props.put("group.id", "courier-assignment-share-group");
props.put("group.type", "share"); // KIP-932
props.put("share.acknowledgement.mode", "explicit");
try (KafkaShareConsumer<String, String> consumer = new KafkaShareConsumer<>(props)) {
consumer.subscribe(List.of("courier_assignment"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> r : records) {
try {
processAssignment(r.value());
consumer.acknowledge(r, AcknowledgeType.ACCEPT); // ack individual
} catch (TransientException e) {
consumer.acknowledge(r, AcknowledgeType.RELEASE); // requeue
} catch (PoisonException e) {
consumer.acknowledge(r, AcknowledgeType.REJECT); // DLQ
}
}
consumer.commitSync();
}
}
Diferença consumer group vs share group: consumer group = ordering per-partition, throughput limitado por #partitions, rebalance custoso; share group = round-robin per message, ack individual, scaling sem reparticionamento, MAS sem ordering global. Use share group pra task queues (assignment, billing, notifications); consumer group pra event sourcing + ordering per-aggregate.
Tiered Storage economics — exemplo Stack Logística: tópico order_events, 50k msg/s × 1KB = 50MB/s × 86400s × 90d = ~388TB. SSD broker a USD 0.10/GB/mês = USD 38.8k/mês. Tiered (24h local + 89d S3 Standard a USD 0.023/GB/mês): SSD ~432GB = USD 43 + S3 ~387TB = USD 8.9k. Total USD 8.95k vs USD 38.8k — 77% economia. ATENÇÃO: S3 GET/PUT custa em high-throughput re-reads (consumer lag, replay); calcule USD 0.0004/1k GET (S3 Standard) × volume de fetch antes de migrar.
// NATS JetStream 2.10 — stream + Pull consumer (Node.js)
import { connect, AckPolicy, DeliverPolicy } from 'nats';
const nc = await connect({ servers: 'nats://nats:4222' });
const jsm = await nc.jetstreamManager();
await jsm.streams.add({
name: 'ORDERS',
subjects: ['order.>'],
retention: 'limits',
max_age: 7 * 24 * 3600 * 1_000_000_000, // 7d em ns
storage: 'file',
num_replicas: 3,
});
await jsm.consumers.add('ORDERS', {
durable_name: 'fulfillment-worker',
ack_policy: AckPolicy.Explicit, // NUNCA None em produção
ack_wait: 30 * 1_000_000_000, // 30s redelivery
max_ack_pending: 1000,
filter_subject: 'order.created',
deliver_policy: DeliverPolicy.All,
});
const js = nc.jetstream();
const consumer = await js.consumers.get('ORDERS', 'fulfillment-worker');
const msgs = await consumer.consume({ max_messages: 100 });
for await (const m of msgs) {
try {
await processOrder(JSON.parse(m.string()));
m.ack();
} catch (e) {
m.nak(5_000); // retry em 5s
}
}
// KV bucket — Redis-like com replay e watch
const kvm = await js.views.kv('courier_presence', { history: 5, ttl: 60_000 });
await kvm.put('courier:42', JSON.stringify({ lat: -23.5, lng: -46.6, ts: Date.now() }));
const entry = await kvm.get('courier:42');
// ObjectStore — S3-compat embedded
const os = await js.views.os('order_attachments');
await os.put({ name: 'invoice-123.pdf' }, fileStream);
Pull vs Push consumers: Pull (consumer.consume) = back-pressure natural, recomendado >1k msg/s; Push = broker empurra, simples mas overflow risk em consumer lento. Em 2026 sempre Pull pra workloads sérios. AckPolicy.None = fire-and-forget (logs, metrics); All = ack acumulativo (batch processing); Explicit = ack individual (default pra business logic).
# Quorum queue (Raft-based, replaces mirrored queues)
rabbitmqadmin declare queue name=billing.tasks \
durable=true arguments='{"x-queue-type":"quorum","x-quorum-initial-group-size":3,"x-dead-letter-exchange":"dlx.billing"}'
# Super stream (partitioned stream — RabbitMQ Streams 4)
rabbitmq-streams add_super_stream order_events \
--partitions 6 \
--binding-keys "BR-SP,BR-RJ,BR-MG,BR-RS,BR-PR,BR-BA"
// RabbitMQ Streams 4 — super stream producer + Single Active Consumer
import { connect } from 'rabbitmq-stream-js-client';
const client = await connect({ hostname: 'rabbit', port: 5552, username: 'admin', password: '...' });
const producer = await client.declareSuperStreamPublisher(
{ superStream: 'order_events' },
(order) => order.region, // routing key → partition
);
await producer.send(Buffer.from(JSON.stringify({ id: 'o1', region: 'BR-SP', total: 99.9 })));
// Single Active Consumer — só 1 consumer ativo por partition (failover automático)
const consumer = await client.declareSuperStreamConsumer({
superStream: 'order_events',
consumerRef: 'fulfillment-sac',
singleActive: true,
offset: Offset.first(),
}, async (msg) => {
await processOrder(JSON.parse(msg.content.toString()));
});
Mirrored queues foram removidas em 4.0 — migration obrigatória pra quorum (rabbitmq-diagnostics check_if_any_deprecated_features_are_used). Quorum queues custam ~30% mais latência que classic mas dão durability real (Raft replication, no message loss em network partition).
| Workload | Broker | Por quê |
|---|---|---|
| Event sourcing + CDC + analytics pipeline | Kafka 4.0 KRaft | Tiered storage barato, EOS v2, Streams/ksqlDB ecosystem |
| Task queue (notifications, billing, jobs) | Kafka share groups OU RabbitMQ quorum | Share groups se já tem Kafka; RabbitMQ se precisa AMQP routing complexo |
| IoT / edge / low-latency RPC + KV | NATS JetStream 2.10 | <1ms latency, KV embedded, leaf nodes pra edge |
| Legacy AMQP 0.9.1 partner integrations | RabbitMQ | Único com AMQP 0.9 maduro, exchanges (direct/topic/headers/fanout) |
| Multi-tenant SaaS com isolation | Pulsar OU NATS accounts | Pulsar tenants/namespaces; NATS accounts isolam credentials |
order_events (90d retention, hot 24h SSD + cold S3), delivery_telemetry (30d), payment_events (7y compliance — tier 99% em S3 Glacier IR)courier_assignment_queue (round-robin assignment, ack individual, sem ordering)courier_presence (TTL 60s, watch pra dashboard real-time), cart_state (session)device_telemetry (IoT scanners, 1M msg/s peak, leaf node em CD)rabbitmqctl list_queues type).Cruza com 04-02 §2.2-§2.5 (broker intros), 04-02 §2.7 (Pulsar/Redpanda/NATS deep), 04-02 §2.13 (consumer scaling), 04-02 §2.15 (operação Kafka), 04-02 §2.18 (idempotent consumer), 04-02 §2.19 (Kafka Streams + ksqlDB), 04-13 §2.2 (streaming engines consume), 04-13 §2.20 (Iceberg sink), 03-05 (AWS MSK + S3 tiered), 04-09 §2.4 (sharding via partitions), 04-04 §2.30 (multi-region MirrorMaker).
Você precisa, sem consultar:
acks=all e min.insync.replicas impacto em durability.Adicionar camada de eventos ao Logística com Kafka (ou Redpanda) + outbox + CDC.
orders.events (compacted? ou retention timed): OrderCreated, OrderAssigned, OrderStatusChanged, OrderDelivered.couriers.events: CourierLocationUpdated, CourierStatusChanged.outbox_events em Postgres.POST /orders escreve orders + outbox_events na mesma transação.orders mudanças viram events.OrderStatusChanged → push pra cliente via SSE/WS.Threshold de Maestria
Acerte todas as 5 pra marcar o módulo como concluído. Sem pressa, sem timer. Tudo fica salvo no teu navegador.
Q1Qual é a relação entre partições Kafka e o paralelismo máximo de um consumer group?
Q2Por que o outbox pattern é necessário ao publicar eventos após escrita no DB?
Q3Qual a diferença essencial entre Kafka share groups (KIP-932) e consumer groups tradicionais?
Q4Quando RabbitMQ tende a vencer Kafka como escolha de broker?
Q5Por que ack ANTES do processamento é considerado anti-pattern em consumer idempotente?
Destrava
04-02 é prereq dos seguintes módulos: