Índice

  1. O que é event-driven architecture
  2. O que é o Apache Kafka
  3. Conceitos essenciais: tópicos, partitions, offsets
  4. Producers, consumers e consumer groups
  5. Pub/Sub vs. fila tradicional
  6. Garantias de entrega: pelo menos uma vez, exatamente uma vez
  7. Exemplo prático em Python
  8. Quando usar (e quando NÃO usar) Kafka
  9. Esquemas, partições e chaves na prática
  10. Erros comuns
  11. 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:

  1. É um log append-only: eventos são só acrescentados, nunca alterados nem apagados no fluxo normal (apagamento só por retenção por tempo/tamanho).
  2. Distribuído e replicado: roda em cluster, com partições espalhadas e réplicas para tolerância a falhas.
  3. 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.
  4. Altíssimo throughput: centenas de milhares de eventos por segundo numa máquina modesta, milhões por segundo num cluster bem ajustado.
CaracterísticaFila tradicional (RabbitMQ)Kafka
ModeloMensagem consumida e removidaLog persistente, múltiplas leituras
RetençãoAté leituraPor tempo/tamanho configurável
ReplayNão (consumida = sumiu)Sim, rebobinando o offset
ThroughputMilhares/sCentenas de milhares a milhões/s
OrdenaçãoPor filaPor partition
Caso típicoComando pontualStream 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:

  1. 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.
  2. Commit manual após processar: garante at-least-once. Se der crash entre processar e commitar, o evento é reprocessado (daí a idempotência).
  3. Idempotência com event_id: o INSERT ... ON CONFLICT DO NOTHING evita 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árioFerramenta recomendada
Worker queue simplesRabbitMQ, SQS, Redis
Pub/Sub de alta escalaKafka, Pulsar
RPC síncronoREST, gRPC
Streaming + processamentoKafka + Flink/Streams
Notificação simples entre serviçosRabbitMQ, SNS
Event sourcing / log de domínioKafka, 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, version em 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}

  1. Consumer sem idempotência: duplica reservas/pagamentos quando há reprocessamento. Sempre deduque por event_id.
  2. Auto-commit ligado: o offset avança antes do processamento terminar. Crash no meio = evento perdido. Desligue e faça commit manual.
  3. Poucas partições para o volume: consumers parados, lag crescente. Calcule partições pelo throughput e deixe folga.
  4. Chave errada / hot partition: usar country=BR como chave quando 90% dos seus clientes são brasileiros concentra tudo numa partition. Use chaves de alta cardinalidade.
  5. Mensagens gigantes: mandar PDF/imagem dentro do evento. Kafka sofre com mensagens grandes. Use object storage e passe a URL.
  6. Ignorar lag: não monitorar o consumer_lag é não saber que seu pipeline está atrasado. Alertas em lag crescente são obrigatórios.
  7. Tópico único para tudo event types: misturar pedido.criado, pagamento.aprovado, usuario.atualizado num tópico só obriga todos os consumers a filtrar. Separe por domínio/event type.
  8. Sem schema registry: consumidores quebram a cada mudança de campo. Versione e valide os eventos.
  9. 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.
  10. Cluster mal dimensionado: poucos brokers = risco de perda de dados se min.insync.replicas não for respeitado. No mínimo 3 brokers com replication.factor=3 e min.insync.replicas=2 para 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=all e 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.