Retry parece uma boa ideia porque passa a sensação de insistência controlada.

Se uma chamada falhou, tenta de novo. Se o consumer não conseguiu processar, tenta mais uma vez. Se a dependência estiver instável, talvez a próxima tentativa funcione.

O problema é que retry cego costuma piorar exatamente o tipo de incidente que ele deveria aliviar.

Em Kafka, isso aparece o tempo todo: um consumer falha, repete a mesma mensagem em loop, segura a mesma partição, aumenta o lag, pressiona um banco já degradado e ainda corre o risco de repetir efeito colateral.

Retry não é automaticamente resiliência.

Muitas vezes, é apenas amplificação de falha.


O erro mais comum

O erro clássico é tratar toda falha como temporária.

Na prática, nem toda falha merece nova tentativa imediata.

Alguns exemplos:

  • timeout em uma API externa pode ser transitório
  • 429 ou 503 pode pedir espera e redução de pressão
  • payload inválido não vai melhorar na quarta tentativa
  • erro de validação de negócio não se cura sozinho
  • constraint violation no banco pode indicar duplicidade ou modelagem errada, não falta de insistência

Esse é o ponto central: retry só faz sentido quando existe chance real de a próxima tentativa encontrar um contexto melhor que a anterior.

Se nada mudou, você não está recuperando.

Você só está repetindo custo.


Quando retry piora o Kafka

No ecossistema Kafka, retry mal desenhado costuma produzir três efeitos ruins ao mesmo tempo.

1. A partição fica presa

Se o consumer recebe um evento, falha e fica insistindo nele no mesmo loop, aquela partição para de progredir.

As mensagens seguintes ficam esperando atrás de um item que já se mostrou problemático.

O resultado aparece como lag crescendo.

E foi exatamente esse tipo de leitura enganosa que discutimos no post sobre lag alto não ser culpa automática do broker.

2. A dependência degradada recebe mais pressão

Se o problema está no banco, numa API externa ou num serviço interno lento, retry imediato vira ataque involuntário.

O sistema que já estava sofrendo passa a receber mais chamadas por unidade de tempo, vindas justamente do mecanismo que deveria ajudar na recuperação.

Em vez de absorver a falha, o consumer amplifica a sobrecarga.

3. O efeito colateral pode acontecer de novo

Se a falha acontece depois de parte do processamento ter sido executada, insistir sem idempotência pode duplicar efeito real de negócio.

Uma cobrança pode ser enviada duas vezes. Um e-mail pode ser disparado novamente. Um evento de saída pode ser republicado.

Foi por isso que o post anterior insistiu tanto em idempotência no consumer.

Retry sem idempotência é uma combinação perigosa.


Nem toda falha pede a mesma estratégia

O desenho maduro começa quando o time para de falar em "retry" como se fosse uma única coisa.

O que existe de verdade é classificação de falha.

Perguntas úteis:

  • isso pode funcionar se eu tentar de novo daqui a alguns segundos?
  • esse erro depende de carga, concorrência ou indisponibilidade temporária?
  • esse evento está malformado e nunca vai funcionar?
  • eu devo reprocessar depois ou desviar para análise?
  • repetir agora aumenta ou reduz a chance de recuperação?

Sem essa classificação, o sistema cai em duas armadilhas:

  • retry demais para erros permanentes
  • abandono precoce para erros realmente transitórios

Os dois lados são ruins.

Mas, em produção, o mais destrutivo costuma ser o primeiro.


O loop que parece seguro e destrava incidente

Um exemplo comum:

  1. o consumer lê o evento
  2. chama uma API de pagamento
  3. recebe timeout
  4. tenta de novo imediatamente
  5. recebe timeout de novo
  6. segue tentando várias vezes dentro do mesmo processamento

Na superfície, isso parece prudente.

No efeito sistêmico, não.

Enquanto o consumer insiste:

  • a partição não avança
  • o grupo pode acumular backlog
  • o tempo de processamento sobe
  • a janela para rebalance aumenta
  • a dependência externa continua pressionada

Se esse comportamento se espalha por várias instâncias, o retry deixa de ser local.

Ele vira padrão de carga do sistema inteiro.

E incidentes pequenos começam a escalar muito mais rápido.


Retry imediato é diferente de retry controlado

Vale separar bem duas coisas que muita gente mistura:

  • repetir imediatamente no mesmo fluxo
  • reencaminhar para uma nova tentativa com atraso, limite e observabilidade

O primeiro costuma ser o mais perigoso.

Ele bloqueia progresso, prende partição e reage à falha com mais pressa.

O segundo pode fazer sentido, desde que exista critério.

Alguns elementos saudáveis:

  • limite máximo de tentativas
  • backoff exponencial
  • jitter para evitar sincronismo entre instâncias
  • classificação entre erro transitório e erro permanente
  • desvio para DLQ quando insistir deixa de ser racional
  • métricas para enxergar volume de retries, taxa de sucesso após retry e impacto no lag

Retry que não tem limite nem observabilidade não é estratégia.

É reflexo.


Quando tirar a mensagem do caminho é a melhor decisão

Tem eventos que não deveriam continuar bloqueando a partição.

Se o payload está inválido, se há erro estrutural, se a regra de negócio não permite processamento ou se a mensagem exige intervenção humana, insistir ali só mascara o problema.

Nesses casos, a estratégia madura costuma ser:

  • registrar o motivo da falha
  • mover a mensagem para uma trilha de erro ou DLQ
  • liberar a partição para o restante do fluxo
  • tratar o evento problemático fora do caminho crítico

Isso não significa desistir da mensagem.

Significa parar de sacrificar o fluxo inteiro por um item que não vai melhorar sozinho.

Em muitos cenários, proteger a vazão e a estabilidade do consumo vale mais do que insistir cegamente na mesma unidade de trabalho.


Retry também precisa respeitar a semântica do negócio

Esse ponto costuma ser subestimado.

Mesmo quando a falha parece transitória, a repetição pode não ser neutra.

Se a operação envolve:

  • cobrança
  • reserva
  • envio de notificação
  • escrita em serviço externo
  • publicação de evento derivado

então retry precisa vir acompanhado de garantias sobre duplicidade e consistência.

Não basta pensar "vai uma vez ou duas".

Você precisa pensar:

  • se isso repetir, o estado final continua correto?
  • existe chave de idempotência?
  • existe outbox ou algum mecanismo para não perder nem duplicar publicação?
  • a dependência externa aceita operação repetida com segurança?

Sem essas respostas, retry vira aposta.


O que costuma funcionar melhor na prática

Em arquiteturas Kafka mais maduras, o padrão costuma ser bem menos impulsivo.

Algo nessa linha:

  • falhas claramente permanentes vão para DLQ ou trilha de tratamento
  • falhas transitórias recebem retry com atraso e limite
  • chamadas externas são protegidas com timeout, circuit breaker e backoff
  • consumers são idempotentes para tolerar repetição
  • reprocessamento fica desacoplado do caminho crítico quando necessário

Perceba a lógica.

O objetivo não é "tentar sempre".

O objetivo é tentar de um jeito que não destrua o restante do sistema.

Essa diferença parece pequena no discurso.

Em produção, ela separa recuperação de amplificação.


Retry mal configurado costuma gerar diagnóstico errado

Outro efeito ruim é observabilidade enganosa.

O time olha o painel e conclui:

  • o broker está lento
  • Kafka não está dando conta
  • precisamos de mais partições
  • precisamos escalar consumers

Só que, muitas vezes, o gargalo real é o próprio comportamento de retry.

Foi essa linha de raciocínio que já apareceu quando falamos de hot partition, rebalance e commit automático.

Kafka frequentemente recebe a culpa por sintomas que nasceram no desenho do consumer.

Retry cego é uma das causas mais comuns desse tipo de confusão.


O ponto que vale fixar

Retry não é virtude por si só.

Insistir sem critério pode aumentar lag, prolongar incidente, derrubar dependências e repetir efeitos que o negócio não aceita duplicar.

Em sistemas orientados a eventos, a pergunta correta não é "tem retry?".

É:

quando falha, o sistema sabe diferenciar o que vale tentar de novo daquilo que precisa sair do caminho?

Se não souber, o retry não está protegendo o fluxo.

Está piorando o problema.