Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

03 - Event Streaming (Kafka)

#event-streaming#kafka#offsets#particoes#replay
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

Event Streaming (Kafka)

Event streaming trata mensagens como um log de eventos persistente e reprocessável, e não como envelopes descartados após o consumo. No modelo introduzido pelo Apache Kafka, o broker é um log append-only distribuído: o produtor adiciona eventos ao fim do log, os consumidores leem do log controlando sua própria posição, o offset, e o log permanece armazenado por uma janela de retenção configurável. A diferença conceitual para a fila tradicional é profunda: a fila remove a mensagem ao confirmar o consumo, enquanto o stream mantém o evento e permite que qualquer consumidor leia de novo, do início ou de qualquer ponto, quantas vezes precisar. Em produção, essa propriedade sustenta integração entre sistemas, pipelines de dados em tempo real, event sourcing, auditoria completa e reconstrução de estado. Compreender streaming é entender por que o Kafka virou infraestrutura padrão para eventos em larga escala e o que ele muda no desenho de consumidores, desde o commit de offset até o replay deliberado.

Conceito: log persistente em vez de fila descartável

A metáfora correta para o Kafka não é o correio, é o livro contábil. Cada evento é um registro datado e imutável anexado ao log, e ler um registro não o consome. Diversos sistemas podem folhear o mesmo livro ao mesmo tempo, cada um em sua página, cada um no seu ritmo, sem interferir uns nos outros.

Essa inversão muda a pergunta central do consumidor. Na fila, a pergunta é qual mensagem o broker vai me entregar agora; no stream, é em que offset eu parei da última vez. O consumidor é dono da própria posição, e avançar, retroceder ou recomeçar são operações explícitas e baratas.

A retenção é configurável por tópico, tipicamente de sete dias até anos, ou por tamanho, e independe do consumo. Os eventos existem porque aconteceram, e não porque alguém está esperando por eles. É essa desvinculação entre existência do dado e interesse no dado que habilita o reprocessamento como prática de engenharia rotineira, e não como recuperação de desastre.

No Kafka especificamente, o log é replicado entre brokers, com um líder por partição e réplicas em sincronia formando o ISR. Durabilidade é função do replication factor e do acks configurado no produtor, e disponibilidade sob falha de broker é automática, com eleição de novo líder. Desde a versão 4.0 o cluster opera em KRaft, sem ZooKeeper, e o protocolo de consenso interna passou a ser parte do próprio Kafka.

O problema que resolve

Filas tradicionais resolvem distribuição de trabalho, mas falham em três situações comuns. A primeira é o bug no consumidor: se a mensagem foi removida após o ack e o processamento estava errado, o dado foi perdido, e a única saída é recuperar da origem. No stream, o evento continua no log, e corrigir o código e consumir novamente do offset do erro resolve o problema em minutos.

A segunda situação é o novo consumidor de um fluxo histórico. Um serviço novo precisa dos últimos trinta dias de eventos para construir seu estado inicial; com fila, ele só vê o que aparecer depois de conectado. Com streaming, ele consome o log desde o início, à velocidade de leitura, e alcança o presente.

A terceira é a múltipla finalidades para o mesmo fluxo. Integração, analytics, auditoria e machine learning precisam dos mesmos eventos com garantias e ritmos diferentes. O log serve todas as leituras de uma única gravação, sem duplicar produtores nem criar filas paralelas por interesse, e é dessa economia que nasce o papel do Kafka como espinha dorsal de dados.

Partições e offsets: a anatomia do log

Um tópico no Kafka é dividido em partições, e cada partição é um log ordenado e imutável com seus próprios offsets sequenciais. O produtor escolhe a partição, em geral pelo hash de uma chave, e eventos com a mesma chave caem sempre na mesma partição. A ordenação existe dentro da partição e apenas dentro dela.

O offset é a posição do evento na partição, um número simples e crescente. O consumidor acompanha seu progresso commitando offsets, o que é barato e explícito. O commit é a declaração: até aqui eu processei; e é também o mecanismo que define as garantias de entrega em caso de falha.

O paralelismo do tópico é dado pelo número de partições. Mais partições permitem mais consumidores simultâneos e maior throughput, ao custo de mais arquivos abertos e mais replicação no broker. A regra prática de desenho é dimensionar partições para o pico esperado com folga, porque reduzir partições depois não é suportado.

Conceito Definição Papel no padrão
Tópico Log lógico nomeado Contrato do fluxo de eventos
Partição Log físico ordenado Unidade de ordenação e paralelismo
Offset Posição do evento na partição Progresso do consumidor
Chave Identificador de roteamento Co-localização de eventos da mesma entidade
Retenção Janela de vida do log Habilita replay e leitura histórica
# Desenho e inspeção de um tópico de eventos em produção
kafka-topics.sh --create --topic pagamentos.aprovados \
  --partitions 12 --replication-factor 3 \
  --config retention.ms=604800000

kafka-topics.sh --describe --topic pagamentos.aprovados
# Partition: 0  Leader: broker-1  Replicas: 1,2,3  Isr: 1,2,3

# Inspeção da posição de um consumer group (lag por partição)
kafka-consumer-groups.sh --bootstrap-server broker:9092 \
  --group faturamento --describe

# Replay deliberado: reposiciona o grupo para o offset desejado
kafka-consumer-groups.sh --bootstrap-server broker:9092 \
  --group faturamento --topic pagamentos.aprovados \
  --reset-offsets --to-offset 42000 --execute

Consumer groups: competição com memória

Um consumer group reúne consumidores que dividem o trabalho de um tópico. Cada partição é atribuída a exatamente um consumidor do grupo, e o rebalanceamento redistribui partições quando membros entram ou saem. É a competição de consumidores da fila, adaptada ao log: mesma semântica de trabalho dividido, com progresso registrável em offsets.

Consumer groups distintos leem o tópico de forma independente. Faturamento e analytics podem estar em offsets completamente diferentes do mesmo log, sem qualquer interferência, e cada um comanda seu próprio replay. A entrega independente do pub-sub e a competição da fila convivem nesse desenho, e é por isso que o Kafka é descrito frequentemente como fila distribuída e pub-sub ao mesmo tempo.

O rebalanceamento é o momento delicado: enquanto partições mudam de dono, o consumo pausa, e offsets não commitados podem ser reprocessados. Grupos com sessões instáveis, membros longos no processamento sem heartbeat adequado e sticky sessions mal configuradas geram rebalanceamentos frequentes que aparecem em produção como picos de lag sem causa aparente.

# Consumidor com controle explícito de offset
consumidor = KafkaConsumer(
    "pagamentos.aprovados",
    group_id="faturamento",
    enable_auto_commit=False,      # commit só após processar
)

for evento in consumidor:
    processar(evento)              # efeito colateral: persistir fatura
    if evento.partition == 0:      # commit granular, por partição
        consumidor.commit(
            offsets={OffsetAndPartition(evento.partition, evento.offset + 1)}
        )
# Falha antes do commit: o grupo volta ao último offset válido
# e o evento é reprocessado, logo idempotência é obrigatória

Reprocessamento e replay

O replay é a operação que define o padrão. Repor eventos existentes para reconstruir estado, retroalimentar um modelo, corrigir uma projeção ou auditar decisões passadas são variações da mesma operação: reposicionar o offset e consumir.

O reprocessamento exige dois cuidados técnicos. O primeiro é idempotência no consumidor, pois eventos já processados retornarão, e o efeito colateral precisa ser tolerante a repetição. O segundo é a compatibilidade do schema entre a versão que escreveu o evento e a versão que vai lê-lo novamente, pois o log guarda a forma original, e o código novo precisa saber ler o histórico inteiro dentro da janela de retenção.

Replay massivo tem custo real: satura rede, brokers e sistemas de destino. Em produção, replay sério é operação controlada, com throttling, janela acordada e monitoramento do downstream, e não comando disparado em sexta-feira à tarde.

Uma variação útil é o replay seletivo por chave ou por intervalo de tempo, comum em correção de dados: em vez de reprocessar o log inteiro, o time reposiciona o grupo para o offset correspondente ao início da janela afetada e interrompe no offset fim. Ferramentas de pipeline como Kafka Streams e Flink expõem essa capacidade nativamente, e em consumidores próprios ela é apenas aritmética de offsets.

Há ainda o caso do compacted topic, com retenção por chave em vez de por tempo: o log mantém o último valor de cada chave, e o formato é o suporte natural de changelog, tabelas derivadas e estado compactado para stores de agregação. É o mesmo mecanismo de log servindo a um contrato diferente.

Comparação com fila tradicional

Aspecto Fila tradicional Event Streaming (Kafka)
Após o consumo Mensagem removida Evento mantido no log
Posição do consumidor Gerenciada pelo broker Offset de cada consumer group
Releitura Não suportada Replay a qualquer offset
Novo consumidor do histórico Só vê o futuro Lê desde o início, dentro da retenção
Ordenação Por fila única ou não garantida Por partição, com chave
Modelos de consumo Competição entre workers Grupos independentes + competição dentro do grupo
Caso central Distribuição de tarefas Movimentação e histórico de eventos

A fronteira prática entre os dois modelos: se a mensagem é um comando que um executor precisa fazer exatamente uma vez, fila; se é um fato que múltiplos sistemas precisam observar, derivar e rever, streaming. Muitas arquiteturas usam os dois, com o stream como fonte da verdade e filas derivadas para entrega a consumidores com semântica de trabalho.

No desenho com ambos, o padrão recorrente é o stream interno ao domínio e a fila na borda de entrega. O log central preserva o histórico e alimenta projeções, e filas derivadas fazem a distribuição final para workers com DLQ e visibilidade própria. O erro oposto, usar fila para tudo, aparece como perda de histórico, impossibilidade de replay e duplicação de produtores para cada novo interesse.

Erros comuns

  • Chaves mal escolhidas: chave com baixa cardinalidade ou com picos, como loja em Black Friday, cria partições quentes e desequilibra o cluster.
  • Commit automático com processamento longo: offsets avançam antes do processamento terminar, e uma falha a partir dali é perda silenciosa de eventos.
  • Partições insuficientes: paralelismo máximo já definido na criação do tópico, e escalar o grupo além do número de partições apenas adiciona consumidores ociosos.
  • Ignorar o rebalanceamento: processamento longo sem max poll configurado derruba o membro do grupo e desencadeia ciclos de rebalance e lag crescente.
  • Tratar o log como banco de dados consultável: busca por conteúdo não é caso de uso do log, e essa demanda pertence a um índice derivado dos eventos.

Boas práticas

  • Escolha chaves de partição com alta cardinalidade e distribuição estável, e valide a uniformidade com métricas por partição, não só agregadas.
  • Defina retenção pensando em cenários de replay e recuperação, porque a janela disponível delimita o quanto você pode voltar no tempo.
  • Torne o processamento idempotente e versione o schema com registro central e política de compatibilidade, pré-requisitos de qualquer replay seguro.
  • Monitore lag por consumer group e por partição, com alerta em tendência de crescimento, pois lag é o termômetro universal do streaming.
  • Trate replay como procedimento operacional documentado, com runbook, limites de taxa e critério de sucesso, e não como improvisação pós-incidente.