Índice
- O que é event-driven architecture
- O que é o Apache Kafka
- Conceitos essenciais: tópicos, partitions, offsets
- Producers, consumers e consumer groups
- Pub/Sub vs. fila tradicional
- Garantias de entrega: pelo menos uma vez, exatamente uma vez
- Exemplo prático em Python
- Quando usar (e quando NÃO usar) Kafka
- Esquemas, partições e chaves na prática
- Erros comuns
- Checklist de adoção
O que é event-driven architecture {#o-que-e}
Antes de entender event-driven architecture (arquitetura orientada a eventos), vale explicar a palavra-chave: evento. Um evento é "algo que aconteceu no passado e foi registrado" — um pedido foi criado, um pagamento foi confirmado, um sensor detectou temperatura acima do limite. O evento é um fato imutável: você não "des-faz" um evento, no máximo emite outro que o compensa.
Na arquitetura tradicional (síncrona, baseada em requests), o serviço A chama o serviço B e fica esperando a resposta. Pense numa ligação telefônica: você liga para o colega, ele atende, você fala, ele responde, e ninguém desliga enquanto a conversa não termina. Se ele não atende, você fica preso.
Na arquitetura orientada a eventos, o serviço A simplesmente anuncia que algo aconteceu e segue sua vida. Pense num canal de rádio: o locutor fala "o pedido 1234 foi pago" e desliga o microfone. Quem estiver ouvindo e se importar reage; quem não estiver ouvindo, perdeu. Ninguém fica esperando ninguém.
Isso traz três propriedades poderosas:
- Desacoplamento temporal: o produtor não precisa que o consumidor esteja online no momento do evento.
- Desacoplamento espacial: o produtor não precisa saber quem consome — novos consumidores podem surgir sem o produtor mudar uma linha.
- Elasticidade: dá para adicionar mais consumidores se o volume crescer, sem mexer em quem produz os eventos.
O que é o Apache Kafka {#kafka}
Apache Kafka é uma plataforma de streaming de eventos distribuída, criada no LinkedIn em 2011 e hoje mantida pela Confluent e pela comunidade. Em termos simples, é um "correio de alta velocidade" para eventos.
A analogia mais útil: Kafka é como um correio que distribui cartas para todos
os destinatários interessados, sem que o remetente precise saber quem são.
Você entrega a carta no correio dizendo "esta é sobre o assunto
pedidos-criados", e o correio garante que todo assinante daquele assunto
receba uma cópia. O remetente não conhece os destinatários, os destinatários
não se conhecem entre si, e o correio guarda as cartas por vários dias caso
alguém esteja de folga.
O que torna o Kafka diferente de um banco de dados ou de uma fila comum:
- É um log append-only: eventos são só acrescentados, nunca alterados nem apagados no fluxo normal (apagamento só por retenção por tempo/tamanho).
- Distribuído e replicado: roda em cluster, com partições espalhadas e réplicas para tolerância a falhas.
- Replay: consumidores podem "rebobinar" e ler eventos antigos, como um DVR de TV. Isso é brutalmente útil para reprocessar dados, treinar modelos de ML, ou reconstruir views materializadas.
- Altíssimo throughput: centenas de milhares de eventos por segundo numa máquina modesta, milhões por segundo num cluster bem ajustado.
| Característica | Fila tradicional (RabbitMQ) | Kafka |
|---|---|---|
| Modelo | Mensagem consumida e removida | Log persistente, múltiplas leituras |
| Retenção | Até leitura | Por tempo/tamanho configurável |
| Replay | Não (consumida = sumiu) | Sim, rebobinando o offset |
| Throughput | Milhares/s | Centenas de milhares a milhões/s |
| Ordenação | Por fila | Por partition |
| Caso típico | Comando pontual | Stream de eventos, integração de sistemas |
Conceitos essenciais: tópicos, partitions, offsets {#conceitos}
Esses três conceitos são a base de tudo no Kafka. Vamos devagar.
Tópico (topic)
Um tópico é o "assunto" da carta. É um canal nomeado para um tipo de evento.
Exemplos: pedidos-criados, pagamentos-confirmados, sensor-temperatura.
Produtores publicam em tópicos; consumidores assinam tópicos.
Partition (partição)
Cada tópico é dividido em partitions — pense em várias esteiras paralelas do mesmo correio. A partição é o que permite escala horizontal: múltiplas partições significam múltiplos consumidores em paralelo.
- Cada evento vai para uma partition, decidida por uma chave (key).
- Eventos com a mesma chave sempre vão para a mesma partition → garante ordem por chave.
- Eventos em partitions diferentes não têm ordem garantida entre si.
Exemplo: num tópico pedidos-criados com 6 partições, o evento com
key=cliente-123 sempre cai na partition 2 (por hash). Assim, todos os pedidos
do cliente-123 são processados em ordem pelo consumidor que pegou a partition 2.
Offset
Cada evento dentro de uma partition recebe um número sequencial chamado offset, começando em 0. É como a numeração das páginas de um livro. O consumidor registra "li até a página 47" e, se reiniciar, volta da página 48. Isso permite parar e retomar sem perder nem duplicar (se o commit for feito correto).
Tópico: pedidos-criados (3 partições)
Partition 0: [evt0] [evt3] [evt6] [evt9] ← offsets 0,1,2,3
Partition 1: [evt1] [evt4] [evt7] [evt10] ← offsets 0,1,2,3
Partition 2: [evt2] [evt5] [evt8] [evt11] ← offsets 0,1,2,3
Nota importante: o offset é por partition, não global. O evento offset 3 da partition 0 não é "o quarto evento do tópico todo".
Producers, consumers e consumer groups {#producers-consumers}
Producer (produtor)
Quem publica eventos no tópico. Pode publicar:
- Com chave: Kafka decide a partition via hash da chave → ordem garantida por chave.
- Sem chave: Kafka escolhe a partition (round-robin ou sticky) → sem ordem.
Consumer (consumidor)
Quem lê eventos do tópico. Um consumer lê de uma ou mais partitions.
Consumer group (grupo de consumidores)
Aqui está a mágica da escala. Um consumer group é um conjunto de consumers que dividem as partitions de um tópico entre si: cada partition é lida por exatamente um consumer dentro do grupo.
- Tópico com 6 partitions e consumer group com 6 consumers → cada consumer pega uma partition, paralelismo máximo.
- Consumer group com 3 consumers → cada um pega 2 partitions.
- Consumer group com 10 consumers → 4 ficam parados (não há partition para eles). Mais consumers que partitions = desperdício.
Diferentes consumer groups não competem: cada grupo recebe uma cópia
completa dos eventos. É o que permite que o serviço de estoque, o de
notificação e o de analytics leiam o mesmo pedidos-criados independentemente.
Tópico: pedidos-criados (6 partições)
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Group A Group B Group C
(estoque) (notifica) (analytics)
3 consumers 6 consumers 1 consumer
2 parts cada 1 part cada 6 parts
Pub/Sub vs. fila tradicional {#pubsub-vs-fila}
O modelo pub/sub (publish/subscribe) é o coração do Kafka. Diferente de uma fila comum onde a mensagem é consumida e some, no Kafka:
- Cada consumer group mantém seu próprio offset — o Kafka não remove o evento quando alguém lê.
- Múltiplos grupos independentes leem o mesmo stream.
- A retenção é por tempo (ex.: 7 dias) ou tamanho (ex.: 10GB por partition), independente de leitura.
Isso explica por que Kafka é usado para integração entre sistemas: o ERP, o CRM e o data warehouse podem ler o mesmo evento de "cliente cadastrado" sem que um atrapalhe o outro. Cada um segue no seu ritmo.
Garantias de entrega: pelo menos uma vez, exatamente uma vez {#garantias}
Esse é o tópico que mais confunde iniciantes. Vamos com calma.
At-most-once (no máximo uma vez)
O produtor envia e esquece. Pode perder eventos. Raramente é o que você quer em negócio (perder um pagamento é grave).
At-least-once (pelo menos uma vez)
O produtor reenvia enquanto não recebe confirmação. Pode haver duplicação: o evento chegou, mas a confirmação se perdeu, então ele é reenviado. É o padrão mais comum no Kafka.
Para lidar com duplicação, seus consumers precisam ser idempotentes —
processar o mesmo evento duas vezes deve dar o mesmo resultado que processar
uma vez. Exemplo: usar o event_id como chave de deduplicação no banco.
Exactly-once (exatamente uma vez)
Kafka suporta exactly-once com transactions API (produtor transacional +
read_process_write), mas é mais complexo e tem custo de throughput. Em
muitos casos, at-least-once + idempotência no consumidor é mais simples e
igualmente correto.
Regra prática: prefira at-least-once + consumidor idempotente. Reserve exactly-once transacional para fluxos financeiros sensíveis onde a duplicação é cara de deduplicar.
Exemplo prático em Python {#exemplo-python}
Vamos usar confluent-kafka (cliente Python performático, baseado em librdkafka).
Produtor
from confluent_kafka import Producer
import json
# Configuração do produtor
conf = {
"bootstrap.servers": "kafka1:9092,kafka2:9092", # brokers do cluster
"client.id": "pedido-service",
"acks": "all", # espera confirmação de todas as réplicas → seguro
"retries": 5, # reenvia em caso de falha transitória
"enable.idempotence": True, # produtor idempotente → sem duplicação no broker
}
producer = Producer(conf)
def delivery_report(err, msg):
"""Callback chamado quando o broker confirma (ou falha) a entrega."""
if err is not None:
print(f"Falha ao entregar evento: {err}")
else:
print(f"Entregue em {msg.topic()}[{msg.partition()}]@{msg.offset()}")
# Publicando um evento com chave = customer_id (ordem garantida por cliente)
evento = {
"event_id": "a1b2c3",
"order_id": 4567,
"customer_id": "cliente-123",
"total": 199.90,
"timestamp": "2026-08-26T10:15:00Z",
}
producer.produce(
topic="pedidos-criados",
key=evento["customer_id"].encode("utf-8"), # chave → partição estável
value=json.dumps(evento).encode("utf-8"),
callback=delivery_report,
)
# Garante que todas as mensagens pendentes sejam enviadas antes de sair
producer.flush()
Pontos-chave do código acima:
acks=all: o evento só é confirmado quando todas as réplicas in-sync gravam — máxima durabilidade.enable.idempotence=True: mesmo se o produtor reenviar, o broker descarta duplicatas baseado em PID (Producer ID) e sequência.key=customer_id: garante que todos os eventos do mesmo cliente caiam na mesma partition, preservando ordem por cliente.
Consumidor
from confluent_kafka import Consumer
import json
conf = {
"bootstrap.servers": "kafka1:9092,kafka2:9092",
"group.id": "estoque-service", # identidade do consumer group
"auto.offset.reset": "earliest", # começa do início se não houver offset salvo
"enable.auto.commit": False, # commit manual → controle de "processado"
}
consumer = Consumer(conf)
consumer.subscribe(["pedidos-criados"])
try:
while True:
msg = consumer.poll(1.0) # espera até 1s por uma mensagem
if msg is None:
continue # sem mensagem neste instante
if msg.error():
print(f"Erro: {msg.error()}")
continue
evento = json.loads(msg.value())
# IDEMPOTÊNCIA: usa event_id para não duplicar o processamento
# Em produção: INSERT ... ON CONFLICT (event_id) DO NOTHING
reservar_estoque(evento["order_id"], evento["customer_id"])
# Só confirma o offset DEPOIS de processar com sucesso
# Se o processo cair antes disso, o evento será reprocessado ao reiniciar
consumer.commit(msg)
finally:
consumer.close()
Repare em três decisões críticas desse consumidor:
enable.auto.commit=False: o offset só avança quando você decide. Com auto-commit, o Kafka pode marcar como lido antes do processamento terminar — se o processo cair, você perde o evento.- Commit manual após processar: garante at-least-once. Se der crash entre processar e commitar, o evento é reprocessado (daí a idempotência).
- Idempotência com
event_id: oINSERT ... ON CONFLICT DO NOTHINGevita duplicar a reserva de estoque caso o evento seja reprocessado.
Quando usar (e quando NÃO usar) Kafka {#quando-usar}
Use Kafka quando…
- Vários sistemas precisam reagir ao mesmo fato: pedido criado dispara estoque, notificação, analytics, faturamento. Pub/Sub brilha aqui.
- Volume muito alto: milhões de eventos/dia, em que uma fila comum sente.
- Replay é necessário: reconstruir uma base, treinar modelo, reprocessar após bug.
- Integração de dados: alimentar data lake, warehouse e caches a partir de um único stream (CDC — change data capture).
- ** buffering entre produtores e consumidores**: produtores em picos, consumidores constantes — Kafka absorve o pico no log.
NÃO use Kafka quando…
- Simples fila de tarefas: worker pegar job, processar, pronto. RabbitMQ, SQS ou Redis Streams são mais simples e baratos.
- Solicitação-resposta síncrona: o cliente precisa da resposta agora. Kafka não foi feito para RPC. Use REST/gRPC.
- Time pequeno, volume baixo: a complexidade operacional do Kafka (cluster, ZooKeeper/KRaft, monitoramento de lag) não se paga. Use um broker simples.
- Mensagens grandes: Kafka é otimizado para mensagens pequenas (KBs). Arquivos grandes devem ir para object storage e o evento só carrega a URL.
- Fila com prioridade: Kafka não tem prioridade nativa entre mensagens do mesmo tópico. Para isso, use RabbitMQ com filas prioritárias.
| Cenário | Ferramenta recomendada |
|---|---|
| Worker queue simples | RabbitMQ, SQS, Redis |
| Pub/Sub de alta escala | Kafka, Pulsar |
| RPC síncrono | REST, gRPC |
| Streaming + processamento | Kafka + Flink/Streams |
| Notificação simples entre serviços | RabbitMQ, SNS |
| Event sourcing / log de domínio | Kafka, EventStoreDB |
Esquemas, partições e chaves na prática {#particoes-chaves}
Esquema (schema) dos eventos
Eventos sem contrato quebram consumidores a cada mudança. Use um schema registry (Confluent Schema Registry, Apicurio) com Avro, Protobuf ou JSON Schema. O registro valida que cada evento respeita o contrato e permite evolução compatível (backward/forward compatibility).
Boas práticas de schema:
- Inclua
event_id,event_type,timestamp,versionem todo evento. - Nunca remova campos — marque como deprecated e mantenha nullable.
- Adicione campos novos como opcionais (default) para não quebrar consumidores antigos.
Quantas partições?
- Calcule pelo throughput desejado dividido pela capacidade de cada consumidor. Se 1 consumidor processa 1.000 eventos/s e você precisa de 6.000/s, precisa de no mínimo 6 partições (e 6 consumers).
- Arredonde para cima com folga: partições são baratas, mas não dá para diminuir depois de criadas (precisa recriar o tópico).
- Considere paralelismo futuro: mais partições hoje = headroom para escalar consumers amanhã.
Escolha da chave
A chave decide a partition e, portanto, a ordem garantida. Pergunte:
"para qual entidade a ordem dos eventos importa?". Se for por cliente, use
customer_id como chave. Se for por pedido, use order_id. Cuidado com
chaves de baixa cardinalidade (ex.: 10 regiões) que causam hot partitions:
uma partition recebe quase todo o tráfego e vira gargalo.
Retenção
- Por tempo:
retention.ms=604800000(7 dias). Padrão comum. - Por tamanho:
retention.bytes=10737418240(10GB por partition). - Log compaction: para tópicos de "estado atual" (ex.: saldos de conta), Kafka mantém só o último valor por chave. Útil para materializar views.
Erros comuns {#erros}
- Consumer sem idempotência: duplica reservas/pagamentos quando há
reprocessamento. Sempre deduque por
event_id. - Auto-commit ligado: o offset avança antes do processamento terminar. Crash no meio = evento perdido. Desligue e faça commit manual.
- Poucas partições para o volume: consumers parados, lag crescente. Calcule partições pelo throughput e deixe folga.
- Chave errada / hot partition: usar
country=BRcomo chave quando 90% dos seus clientes são brasileiros concentra tudo numa partition. Use chaves de alta cardinalidade. - Mensagens gigantes: mandar PDF/imagem dentro do evento. Kafka sofre com mensagens grandes. Use object storage e passe a URL.
- Ignorar lag: não monitorar o
consumer_lagé não saber que seu pipeline está atrasado. Alertas em lag crescente são obrigatórios. - Tópico único para tudo event types: misturar
pedido.criado,pagamento.aprovado,usuario.atualizadonum tópico só obriga todos os consumers a filtrar. Separe por domínio/event type. - Sem schema registry: consumidores quebram a cada mudança de campo. Versione e valide os eventos.
- Tratar Kafka como banco de dados: consultar o mesmo evento de forma aleatória é lento e caro. Kafka é log sequencial, não é store de lookup.
- Cluster mal dimensionado: poucos brokers = risco de perda de dados se
min.insync.replicasnão for respeitado. No mínimo 3 brokers comreplication.factor=3emin.insync.replicas=2para produção.
Checklist de adoção {#checklist}
- Definiu se o problema é pub/sub de escala (Kafka) ou fila simples (RabbitMQ)
- Esquema de eventos versionado em schema registry
- Particionamento calculado pelo throughput + folga
- Chave de partição escolhida para ordem por entidade correta
- Produtores com
acks=alle idempotência - Consumidores com commit manual e idempotência
- Monitoramento de consumer lag com alertas
- Retenção configurada (tempo e/ou tamanho) por tópico
- Cluster com no mínimo 3 brokers,
replication.factor=3 - Tópicos separados por domínio/event type
- Estratégia de DLQ (dead letter queue) para eventos que falham
- Testes de caos: matar broker, reiniciar consumer, simular reprocessamento
Construindo integrações orientadas a eventos? A Inicialize Tec projeta e implementa pipelines com Kafka — da modelagem de tópicos e esquemas aos consumidores idempotentes e monitoramento de lag. Conversamos sobre seu caso.