Retry Pattern
O Retry Pattern é uma abordagem para lidar com falhas temporárias, repetindo automaticamente uma operação que falhou na expectativa de que a próxima tentativa funcione. Em sistemas modernos, especialmente em arquiteturas distribuídas como microsserviços, esse tipo de falha é inevitável.
Instabilidades momentâneas de rede, lentidão em serviços externos, timeouts intermitentes e limitações de recursos são exemplos de falhas que não indicam, necessariamente, um problema permanente. Por isso, saber quando tentar de novo é uma habilidade central da engenharia de resiliência.
Neste artigo, você vai entender o conceito, os cenários de uso, as estratégias de espera e as armadilhas mais comuns. Além disso, vai ver um exemplo simples em C# para fixar a ideia.
O que é o Retry Pattern
O Retry Pattern, ou padrão de tentativas, consiste na repetição automática de uma operação que falhou. A premissa é que a falha foi transitória e que uma nova tentativa pode resolver o problema.
Em vez de falhar imediatamente ao detectar um erro, o sistema aguarda e tenta novamente. Esse ciclo se repete por um número predefinido de vezes, geralmente com intervalos entre as tentativas.
O guia oficial do Retry Pattern no Azure Architecture Center descreve o objetivo de forma direta. Segundo a Microsoft, o padrão permite que a aplicação lide com falhas transitórias ao repetir, de forma transparente, uma operação que falhou.
Quando todas as tentativas se esgotam, a aplicação precisa tratar a falha como uma exceção real. Em outras palavras, o retry não esconde o erro para sempre, ele apenas dá uma segunda chance antes de desistir.
Se você está começando a estudar padrões de projeto, o artigo sobre o que é um design pattern ajuda a contextualizar esse tipo de solução.
O que são falhas transitórias
Falhas transitórias são erros temporários que se corrigem sozinhos depois de um curto período. A perda momentânea de conexão, a indisponibilidade breve de um serviço e os timeouts de um servidor ocupado são exemplos clássicos.
A documentação da Microsoft cita um caso bem ilustrativo. Um banco de dados sobrecarregado pode aplicar uma estratégia de throttling e rejeitar novas requisições até a carga diminuir, então a mesma chamada que falhou agora pode funcionar alguns segundos depois.
Na nuvem, esse cenário é ainda mais frequente. Por isso, a recomendação é projetar a aplicação já esperando falhas transitórias, e não tratá-las como exceção rara.
Por outro lado, nem todo erro é transitório. Uma senha errada, um recurso inexistente ou uma regra de negócio violada vão falhar de novo, não importa quantas vezes a operação se repita.
Quando usar o Retry Pattern
O Retry Pattern é especialmente útil em quatro situações. Em todas elas, a chance de sucesso em uma nova tentativa é real.
Primeiro, quando a operação falha de forma intermitente, como em chamadas HTTP para serviços instáveis. Nesse caso, a mesma requisição costuma funcionar logo em seguida.
Segundo, quando o erro é transitório por natureza, como TimeoutException e HttpRequestException, entre outros. Essas exceções indicam problemas de comunicação, e não de lógica.
Terceiro, quando o custo de uma nova tentativa é aceitável, tanto em desempenho quanto em impacto no negócio. Repetir uma consulta de leitura é barato, enquanto repetir um pagamento exige muito mais cuidado.
Por fim, quando a aplicação interage com recursos externos, como APIs de terceiros, bancos de dados ou serviços em nuvem. São justamente esses pontos que concentram as falhas de rede.
Quando o Retry Pattern não ajuda
O guia da Microsoft também lista cenários em que o padrão atrapalha. O primeiro é a falha duradoura, porque a aplicação desperdiça tempo e recursos repetindo algo que vai continuar falhando.
Também não faz sentido aplicar o padrão a falhas que não têm origem transitória, como exceções internas causadas por erros na lógica de negócio. Nesse caso, o correto é corrigir o código.
Por fim, o Retry Pattern não substitui a escalabilidade. Se um serviço vive ocupado, o problema provavelmente é capacidade, e o caminho é escalar o recurso.
Benefícios do Retry Pattern
Quando bem aplicado, o Retry Pattern traz três ganhos principais para a aplicação. Todos eles se relacionam com a forma como o sistema reage a instabilidades.
O primeiro é a resiliência. O retry reduz as falhas aparentes causadas por erros temporários e, com isso, aumenta a confiabilidade percebida do sistema.
Além disso, o padrão melhora a experiência do usuário. Interrupções pontuais deixam de chegar à tela, porque a aplicação resolve o problema antes de exibir qualquer mensagem de erro.
Por último, vem o isolamento de falhas transitórias. O padrão evita que falhas momentâneas se propaguem e derrubem o sistema como um todo.
Armadilhas comuns do Retry Pattern
Apesar de simples, a implementação do Retry Pattern exige cuidado. Um retry mal configurado pode transformar um problema pequeno em uma indisponibilidade completa.
Por isso, conhecer as armadilhas é tão importante quanto conhecer o próprio Retry Pattern. A seguir estão as cinco mais comuns.
Retry storm
Em sistemas altamente concorrentes, várias instâncias repetindo operações ao mesmo tempo podem sobrecarregar o serviço de destino. Esse fenômeno recebe o nome de retry storm, ou tempestade de requisições, e é o efeito colateral mais conhecido do Retry Pattern.
A Microsoft descreve o efeito como um círculo vicioso. Quanto mais requisições chegam ao serviço sobrecarregado, menor a chance de ele se recuperar, e mais requisições falham.
Retentativas desnecessárias
Tentar novamente uma operação que falhou por motivo permanente é inútil e pode até prejudicar o sistema. Um erro de autenticação, por exemplo, vai se repetir em todas as tentativas.
Além de gastar recursos, essas tentativas atrasam a resposta ao usuário. Ou seja, o retry piora a experiência que deveria melhorar.
Falta de limites
Repetir indefinidamente, sem um número máximo de tentativas, pode levar a loops infinitos e ao esgotamento de recursos. Threads, conexões e memória ficam presos esperando algo que talvez nunca aconteça.
A documentação da Microsoft sugere ir além do limite por requisição. Um retry budget limita o total de tentativas de um processo inteiro contra uma dependência, por exemplo, no máximo 60 tentativas por minuto.
Bloqueios e latência
Esperas excessivas entre as tentativas podem causar atrasos desnecessários. Isso acontece principalmente quando não existe controle sobre o tempo total da operação.
Na prática, um usuário que espera trinta segundos por uma resposta já desistiu, mesmo que o Retry Pattern ainda esteja tentando. Por isso, o tempo total de todas as tentativas precisa caber no requisito de latência da funcionalidade.
Operações que não são idempotentes
Existe ainda uma armadilha mais sutil. O serviço pode receber a requisição, processá-la com sucesso e falhar apenas ao enviar a resposta.
Nesse caso, o retry reenvia uma operação que já aconteceu. Se ela não for idempotente, o resultado pode ser um pedido duplicado ou uma cobrança em dobro, e o artigo sobre código idempotente aprofunda esse risco.
Estratégias de retentativa do Retry Pattern
Existem várias estratégias que podem ser aplicadas ao Retry Pattern, cada uma com características específicas. A escolha certa depende do tipo de operação e do volume de clientes concorrentes.
Retry fixo
No retry fixo, ou fixed interval, a operação se repete após um intervalo de tempo constante, por exemplo, a cada três segundos. Essa é a forma mais simples de implementar o Retry Pattern.
Contudo, ela não é ideal em cenários com muita concorrência. Se muitos clientes falham juntos, todos voltam juntos, exatamente no mesmo intervalo.
Retry exponencial
No exponential backoff, o tempo entre as tentativas aumenta exponencialmente, como dois, quatro e oito segundos. Dessa forma, o serviço de destino ganha cada vez mais tempo para se recuperar.
Essa estratégia reduz o risco de sobrecarga e é ideal em situações de contenção. Segundo a Microsoft, ela é a mais indicada para operações em segundo plano.
Exponential backoff com jitter
O jitter adiciona um grau de aleatoriedade ao tempo de espera. Assim, as tentativas de clientes diferentes deixam de se alinhar no mesmo instante, o que reduz colisões.
A documentação do Azure recomenda justamente essa combinação. O objetivo é impedir que várias instâncias sincronizem suas tentativas e criem picos de carga no serviço que está se recuperando.
Retry com timeout total
Nessa estratégia, você define um tempo máximo para todas as tentativas somadas. Com isso, o sistema nunca espera indefinidamente, mesmo que ainda restem tentativas.
O timeout total é especialmente importante em operações interativas. Ele garante que o usuário receba uma resposta, mesmo que seja uma mensagem de erro, dentro de um prazo razoável.
Retry imediato
O guia de boas práticas do Azure cita também o retry imediato, útil para falhas muito breves, como uma colisão de pacotes na rede. Porém, a recomendação é nunca fazer mais de uma tentativa imediata.
Se a tentativa imediata falhar, o próximo passo deve ser outra estratégia, como o exponential backoff. Caso contrário, o cliente bombardeia o serviço sem dar tempo para ele se recuperar.
Exemplo de Retry Pattern em C#
Para fixar o conceito, o exemplo a seguir implementa um Retry Pattern simples com exponential backoff e jitter. Ele trata apenas exceções transitórias e respeita um número máximo de tentativas.
public static class RetryHelper
{
public static async Task<T> ExecuteAsync<T>(
Func<CancellationToken, Task<T>> operation,
int maxAttempts = 4,
CancellationToken cancellationToken = default)
{
var baseDelay = TimeSpan.FromSeconds(1);
for (var attempt = 1; ; attempt++)
{
try
{
return await operation(cancellationToken);
}
catch (Exception ex) when (IsTransient(ex) && attempt < maxAttempts)
{
// Backoff exponencial: 1s, 2s, 4s...
var exponential = baseDelay * Math.Pow(2, attempt - 1);
// Jitter: até 500 ms aleatórios para dessincronizar os clientes
var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 500));
await Task.Delay(exponential + jitter, cancellationToken);
}
}
}
// Só repete erros de comunicação, nunca erros de negócio
private static bool IsTransient(Exception ex)
=> ex is HttpRequestException or TimeoutException;
}
O filtro when é o coração do exemplo. Ele só captura exceções transitórias e só enquanto ainda restam tentativas, então qualquer outro erro sobe imediatamente para o chamador.
O CancellationToken cumpre o papel de timeout total. Quem chama o método pode cancelar a operação inteira, inclusive no meio de uma espera.
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var product = await RetryHelper.ExecuteAsync(
token => httpClient.GetFromJsonAsync<Product>("api/products/42", token),
maxAttempts: 4,
cancellationToken: cts.Token);
Em produção, porém, prefira uma biblioteca madura em vez de escrever a sua própria lógica. O artigo sobre Polly no .NET mostra como configurar retry, circuit breaker e timeout com poucas linhas.
Vale lembrar também que muitas bibliotecas já trazem o Retry Pattern embutido. O próprio Entity Framework Core, por exemplo, oferece mecanismos para repetir operações de banco que falharam.
Antes de criar a sua própria implementação, portanto, verifique se o SDK que você usa já faz esse trabalho. Segundo a Microsoft, as estratégias padrão costumam ser genéricas, então ajuste os valores com testes.
Cuidados e boas práticas com o Retry Pattern
Algumas práticas fazem a diferença entre um retry que protege o sistema e um retry que o derruba. Todas elas aparecem nas recomendações oficiais de resiliência.
Primeiro, identifique os erros transitórios. Nem todo erro deve ser repetido, então separe as falhas temporárias dos erros críticos e use o código de erro para decidir.
Por exemplo, uma resposta HTTP 503 com o cabeçalho Retry-After informa quanto tempo esperar. Já um 401 indica que nenhuma tentativa vai resolver.
Em seguida, use logging com moderação. Evite registrar cada tentativa como erro e prefira logar apenas a última falha, ou registros resumidos.
A Microsoft recomenda registrar as falhas transitórias como aviso, e não como erro. Assim, o monitoramento não dispara alertas falsos para tentativas que acabaram dando certo.
Também combine o Retry Pattern com o Circuit Breaker, o padrão que interrompe chamadas para serviços que falham repetidamente. Enquanto o retry insiste em falhas curtas, o circuit breaker interrompe as chamadas quando o serviço está claramente fora do ar, o que evita colapsos em cascata.
Além disso, evite camadas de retry empilhadas. Se uma tarefa com retry chama outra que também repete, os atrasos se multiplicam, então deixe a camada inferior falhar rápido.
Por fim, avalie o impacto no negócio antes de aplicar o Retry Pattern a uma operação. Nem sempre vale a pena tentar novamente, e em alguns casos é melhor falhar rápido e informar o usuário.
Conclusão
O Retry Pattern é um pilar fundamental da engenharia de resiliência. Quando bem aplicado, ele transforma falhas temporárias em sucesso e promove estabilidade em ambientes instáveis.
No entanto, a aplicação do Retry Pattern exige cautela e planejamento. Um retry mal implementado pode ser mais prejudicial do que a própria falha que ele tenta resolver.
Como toda boa prática de arquitetura, o segredo está no equilíbrio e na adaptação ao contexto da aplicação. Limite as tentativas, use backoff com jitter, respeite a idempotência e combine o retry com outros padrões de resiliência.
FAQ
O que é o Retry Pattern?
É um padrão de resiliência que repete automaticamente uma operação que falhou, partindo da premissa de que a falha foi temporária.
Quais erros devem ser repetidos?
Apenas erros transitórios, como timeouts, perda momentânea de conexão e respostas de serviço ocupado. Erros de autenticação ou de regra de negócio não devem ser repetidos.
O que é jitter?
É um atraso aleatório somado ao intervalo entre as tentativas. Ele evita que vários clientes repitam a operação no mesmo instante.
Qual a diferença entre Retry e Circuit Breaker?
O retry repete operações que falharam por pouco tempo. O circuit breaker bloqueia novas chamadas quando o serviço falha repetidamente, dando tempo para ele se recuperar.
Quantas tentativas devo configurar?
Depende da operação. Operações interativas pedem poucas tentativas com esperas curtas, enquanto processos em segundo plano toleram mais tentativas com backoff exponencial.