Exactly-once é uma daquelas expressões que parecem resolver tudo no slide.
Ler uma vez. Processar uma vez. Gerar efeito uma vez.
Bonito no discurso. Bem mais estreito na prática.
No Kafka, exactly-once não é uma garantia mágica para qualquer arquitetura. Ele resolve um problema específico, dentro de um escopo específico, com custo real de operação e desempenho.
Se você usar essa ferramenta no lugar certo, ela evita duplicidade onde ela realmente dói.
Se usar no lugar errado, você só ganha complexidade e continua precisando de idempotência do mesmo jeito.
A confusão mais comum
Muita gente escuta exactly-once e traduz assim:
"meu sistema inteiro agora processa cada evento exatamente uma vez".
Não é isso.
No Kafka, a semântica de exactly-once vale principalmente para fluxos em que consumo, processamento e nova publicação acontecem dentro do próprio ecossistema Kafka, coordenados por transação.
Em outras palavras:
- o consumer lê do Kafka
- processa o evento
- publica resultado em outro tópico
- confirma offsets dentro da mesma transação
Se tudo der certo, os dados publicados e o avanço do consumo aparecem juntos. Se der errado, nenhum dos dois aparece para leitores configurados corretamente.
Isso é valioso.
Mas repare no limite:
essa garantia não cobre automaticamente banco, API externa, e-mail, gateway de pagamento ou qualquer efeito colateral fora do log do Kafka.
O que o Kafka resolve de verdade com exactly-once
Quando exactly-once está bem configurado, o objetivo é evitar que um fluxo consume-transform-produce publique duplicado por causa de retry, reinício ou falha entre processamento e commit.
Na prática, isso combina algumas peças:
- producer idempotente para não duplicar publicação em tentativas do próprio producer
- transações para agrupar escrita em tópicos e commit de offsets
- consumidores com
read_committedpara não enxergar resultado parcial ou abortado
O efeito prático é forte:
- ou a transformação inteira fica visível
- ou ela não fica visível para o restante da topologia
Esse modelo é especialmente útil quando o tópico de saída alimenta outros serviços, joins, agregações ou etapas posteriores que não deveriam receber duplicidade artificial.
Não confunda producer idempotente com exactly-once
Esse ponto merece separação clara.
enable.idempotence=true no producer costuma valer muito a pena.
Ele reduz o risco de duplicidade causada por retentativas do próprio producer ao publicar no mesmo tópico e partição. Em muitos cenários, isso deveria ser o padrão.
Mas isso ainda não é a mesma coisa que exactly-once.
Exactly-once com transações vai além:
- coordena publicação de saída
- coordena commit de offsets
- define o que consumidores downstream podem enxergar
Ou seja:
- producer idempotente resolve um pedaço do problema
exactly-oncetransacional resolve um fluxo maior
Misturar os dois conceitos costuma gerar arquitetura mal explicada e expectativa errada com o time.
Quando vale muito a pena
Exactly-once costuma fazer sentido quando a principal saída do processamento continua sendo Kafka.
Exemplos típicos:
- uma aplicação lê de um tópico bruto, enriquece os eventos e publica em um tópico canônico
- um pipeline faz deduplicação, normalização ou agregação e escreve o resultado em novos tópicos
- uma topologia do Kafka Streams materializa estado e republica derivados
- vários serviços dependem do tópico de saída e a duplicidade ali teria efeito em cascata
Nesses casos, o ganho é concreto porque o problema está exatamente no ponto que a transação do Kafka sabe proteger.
Se a pergunta é "quero evitar publicar duas vezes o resultado desta transformação Kafka para Kafka", a ferramenta é boa.
Se a pergunta é "quero garantir que meu banco e meu tópico evoluam como se fossem uma única transação distribuída", a resposta já é outra.
Quando normalmente não vale
Tem muito cenário em que exactly-once é tecnicamente possível, mas economicamente ruim.
Alguns exemplos:
- consumidor simples que grava estado idempotente em banco com
upsert - fluxo de telemetria, log ou métrica em que duplicidade pequena é aceitável
- processamento em que o custo operacional da transação é maior do que o impacto da repetição
- integrações em que o verdadeiro problema está no efeito externo, não na publicação em outro tópico
Também não costuma valer quando o time está tentando usar exactly-once como atalho para não desenhar idempotência.
Se o evento termina em banco, webhook, reserva, cobrança ou notificação, o gargalo real está na borda do negócio. Foi exatamente essa armadilha que apareceu no post sobre idempotência no consumer.
O ponto em que a promessa quebra
Imagine este fluxo:
- o consumer lê um evento do Kafka
- grava no banco
- chama uma API externa
- confirma o offset
Você pode até usar producer transacional em outra parte do fluxo.
Ainda assim, isso não transforma banco e HTTP em participantes nativos da transação do Kafka.
Se a aplicação:
- gravar no banco
- falhar antes de confirmar o offset
- reiniciar e ler o evento de novo
então a duplicidade continua sendo um problema real.
O mesmo vale se a chamada externa aconteceu e o processo caiu antes do resto.
Por isso, exactly-once no Kafka não elimina a necessidade de:
- idempotência
- chave de deduplicação
upsert- outbox quando a publicação depende de estado persistido fora do Kafka
- desenho cuidadoso de retry e DLT
Esse é o ponto que mais decepciona times que compraram o nome pela metade.
O marketing parece fim a fim. A garantia real é bem mais localizada.
O custo que vem junto
Não existe semântica mais forte sem custo.
Quando você liga transações e passa a depender de read_committed, entra um pacote de trade-offs:
- mais coordenação interna
- mais sensibilidade a configuração correta de
transactional.id - throughput menor em alguns cenários
- latência adicional
- troubleshooting mais difícil
- mais cuidado com fencing, abortos e observabilidade do fluxo
Nada disso significa que exactly-once é ruim.
Significa apenas que ele precisa pagar aluguel.
Se a duplicidade no tópico de saída é um risco caro, o aluguel compensa. Se o problema poderia ser resolvido com modelagem idempotente mais simples, talvez não.
O lugar onde ele brilha
O melhor terreno para exactly-once é este:
- entrada no Kafka
- processamento no Kafka
- saída no Kafka
Nesse formato, o Kafka consegue proteger justamente a fronteira que importa.
Exemplo mental:
- um tópico recebe eventos brutos de pagamento
- uma aplicação valida e enriquece
- publica em
payments-validated - outro fluxo agrega e publica em
payments-settled
Se uma falha acontecer no meio, o objetivo é impedir que um estágio intermediário publique saída duplicada ou parcialmente visível.
Aqui, exactly-once conversa bem com o problema.
Principalmente quando a duplicidade num tópico intermediário contaminaria o resto da topologia.
O lugar onde ele vira distração
Agora troque o cenário:
- entrada no Kafka
- processamento na aplicação
- gravação em banco relacional
- chamada para sistema externo
- eventual publicação de auditoria
Aqui, discutir exactly-once no Kafka antes de discutir consistência de borda costuma ser distração arquitetural.
Os problemas mais caros não estão no broker:
- estão na repetição de efeito externo
- na falta de idempotência no consumer
- no retry sem critério
- na falsa sensação de segurança ao empurrar falha para DLT
Se o estado real do negócio mora fora do Kafka, a pergunta principal não é "a transação do Kafka está ligada?".
A pergunta principal é:
se eu processar esse evento de novo, o estado final continua correto?
Uma regra prática que costuma funcionar
Se o resultado principal do seu processamento é outro tópico Kafka, comece avaliando exactly-once.
Se o resultado principal é um efeito externo, comece avaliando idempotência, outbox, deduplicação e estratégia de retry.
Essa ordem é importante porque ela organiza a arquitetura em torno do risco real, não em torno do nome mais sedutor.
Uma heurística útil:
enable.idempotence=truequase sempre merece consideração séria no producerexactly-oncetransacional merece consideração séria em pipelines Kafka para Kafka- idempotência no consumer continua obrigatória quando existe efeito externo
- retry e commit precisam ser pensados junto da semântica de processamento, não depois
O ponto que vale fixar
Exactly-once no Kafka não é mentira.
Mas também não é milagre.
Ele é uma ferramenta excelente quando o seu problema é evitar duplicidade e inconsistência em fluxos transacionais dentro do próprio Kafka.
Fora desse escopo, ele não substitui arquitetura de borda, idempotência nem modelagem cuidadosa de efeito colateral.
Quando o problema é Kafka para Kafka, ele pode valer muito a pena.
Quando o problema é mundo real para fora do broker, muitas vezes o nome impressiona mais do que resolve.
