Introdução
No desenvolvimento de software, uma lógica de repetição confiável é essencial para lidar com falhas intermitentes, como problemas de rede ou interrupções temporárias. Recentemente, me deparei com um código onde um desenvolvedor usou um loop for
com um intervalo de tempo fixo para repetir operações falhas. Embora essa abordagem possa parecer direta, ela carece da resiliência necessária para aplicações do mundo real. É aí que entra o Exponential Backoff – uma estratégia projetada para tornar as repetições mais inteligentes e eficientes.
Neste artigo, vamos analisar como o Exponential Backoff funciona, suas vantagens sobre um loop de repetição básico e como você pode implementá-lo para aprimorar a confiabilidade do seu sistema. Também vou guiá-lo por um exemplo prático usando um módulo de envio de e-mails, mostrando como usar o Exponential Backoff para garantir um tratamento de erros mais resiliente.
O que é o Exponential Backoff?
O Exponential Backoff é uma estratégia de repetição onde o tempo de espera entre as tentativas de repetição aumenta exponencialmente após cada falha. Em vez de repetir em intervalos fixos, cada tentativa subsequente espera mais do que a anterior – geralmente dobrando o atraso a cada vez. Por exemplo, se o atraso inicial for de 1 segundo, as próximas repetições ocorrerão em 2, 4, 8 segundos, e assim por diante. Essa abordagem ajuda a reduzir a carga do sistema e minimiza o risco de sobrecarregar serviços externos durante períodos de alta demanda.
Ao permitir mais tempo entre as repetições, o Exponential Backoff dá uma chance para que problemas temporários se resolvam, levando a um tratamento de erros mais eficiente e a uma maior estabilidade da aplicação.
Prós e Contras do Exponential Backoff
Prós:
-
Redução da Carga do Sistema: Ao espaçar as tentativas, o Exponential Backoff minimiza a chance de sobrecarregar servidores, especialmente útil para lidar com limites de taxa ou falhas transitórias.
-
Manuseio Eficiente de Erros: O atraso crescente permite que problemas transitórios tenham mais tempo para se resolver naturalmente, melhorando a probabilidade de uma nova tentativa bem-sucedida.
-
Estabilidade Aprimorada: Especialmente para sistemas de alto tráfego, ele impede uma enxurrada de novas tentativas, mantendo as aplicações em funcionamento suave sem consumo excessivo de recursos.
Contras:
- Latência Aumentada: Com cada nova tentativa levando progressivamente mais tempo, o Exponential Backoff pode resultar em atrasos, especialmente se muitas tentativas forem necessárias antes do sucesso.
Casos de Uso Chave para o Exponential Backoff
O Exponential Backoff é particularmente útil em cenários nos quais os sistemas interagem com serviços externos ou gerenciam grandes volumes de tráfego. Aqui estão alguns outros casos de uso comuns:
-
APIs com Limitação de Taxa: Alguns APIs possuem limites de taxa, restringindo as requisições em um determinado período de tempo. O Exponential Backoff ajuda a evitar novas tentativas imediatas que poderiam exceder o limite, dando tempo para que o limite seja redefinido.
-
Instabilidade de Rede: Em casos de falhas temporárias de rede ou timeouts, o exponential backoff ajuda ao esperar mais tempo entre as tentativas, permitindo que a rede se estabilize.
-
Conexões de Banco de Dados: Ao se conectar a bancos de dados sobrecarregados, o exponential backoff ajuda a prevenir uma sobrecarga adicional ao atrasar as tentativas, permitindo que o banco de dados se recupere.
-
Sistemas de Fila: Em sistemas de filas de mensagens, se uma mensagem falhar devido a um erro, o uso do Exponential Backoff para novas tentativas pode prevenir um reprocessamento rápido e permitir tempo para que problemas temporários sejam resolvidos.
Construindo um Serviço Básico de Envio de E-mails com Exponential Backoff
Para demonstrar o Exponential Backoff, vamos construir um envio básico de e-mails que tenta reenviar os e-mails se ocorrer um erro. Este exemplo mostra como o Exponential Backoff melhora o processo de reenvio em comparação com um simples for-loop.
import nodemailer from "nodemailer";
import { config } from "../common/config";
import SMTPTransport from "nodemailer/lib/smtp-transport";
const emailSender = async (
subject: string,
recipient: string,
body: string
): Promise<boolean> => {
const transport = nodemailer.createTransport({
host: config.EMAIL_HOST,
port: config.EMAIL_PORT,
secure: true,
auth: { user: config.EMAIL_SENDER, pass: config.EMAIL_PASSWORD },
} as SMTPTransport.Options);
const mailOptions: any = {
from: config.EMAIL_SENDER,
to: recipient,
subject: subject,
};
const maxRetries = 5; // maximum number of retries before giving up
let retryCount = 0;
let delay = 1000; // initial delay of 1 second
while (retryCount < maxRetries) {
try {
// send email
await transport.sendMail(mailOptions);
return true;
} catch (error) {
// Exponential backoff strategy
retryCount++;
if (retryCount < maxRetries) {
const jitter = Math.random() * 1000; // random jitter(in seconds) to prevent thundering herd problem
const delayMultiplier = 2
const backOffDelay = delay * delayMultiplier ** retryCount + jitter;
await new Promise((resolve) => setTimeout(resolve, backOffDelay));
} else {
// Log error
console.log(error)
return false; // maximum number of retries reached
}
}
}
return false;
};
Ajustando Parâmetros do Exponential Backoff
Implementar o Exponential Backoff envolve ajustar certos parâmetros para garantir que a estratégia de reenvio funcione bem de acordo com as necessidades da sua aplicação. Os seguintes parâmetros-chave afetam o comportamento e o desempenho do Exponential Backoff em um mecanismo de reenvio:
- Delay Inicial
-
Propósito: Define o tempo de espera antes da primeira tentativa de reenvio. Deve ser longo o suficiente para evitar reenvios imediatos, mas curto o suficiente para evitar atrasos perceptíveis.
-
Configuração Recomendada: Comece com um atraso entre 500 ms a 1000 ms. Para sistemas críticos, use um atraso mais curto, enquanto operações menos urgentes podem ter um atraso mais longo.
- Multiplicador de Atraso
-
Propósito: Controla a rapidez com que o atraso aumenta após cada tentativa. Um multiplicador de 2 dobra o atraso (por exemplo, 1s, 2s, 4s).
-
Configuração Recomendada: Normalmente, um multiplicador entre 1.5 e 2 equilibra responsividade e estabilidade. Multiplicadores mais altos (por exemplo, 3) podem ser adequados se o sistema puder lidar com atrasos mais longos entre as tentativas.
- Tentativas Máximas
-
Propósito: Limita as tentativas de retentativa para evitar retentativas excessivas que poderiam drenar recursos ou aumentar a carga do sistema.
-
Configuração Recomendada: Uma faixa de 3 a 5 retentativas geralmente é suficiente para a maioria das aplicações. Além disso, a operação pode precisar ser registrada como falha ou gerenciada de forma diferente, como notificar o usuário ou disparar um alerta.
- Jitter (Randomização)
-
Propósito: Adiciona aleatoriedade a cada atraso para evitar que as tentativas se agrupem e causem um efeito de rebanho estrondoso.
-
Configuração Recomendada: Adicione um atraso aleatório entre 0 e 500 ms a cada intervalo de tentativa. Este jitter ajuda a espaçar as tentativas de forma mais uniforme ao longo do tempo.
Conclusão
Ao usar o Exponential Backoff, você adiciona resiliência à sua aplicação, preparando-a para lidar com problemas inesperados. É uma pequena mudança com um grande impacto, especialmente à medida que sua aplicação cresce.
E isso é tudo por enquanto, pessoal. Fiquem à vontade para deixar um comentário e fazer perguntas, se tiverem. Um brinde à construção de aplicativos mais confiáveis e resilientes.
Feliz codificação! 👨💻❤️
Source:
https://timothy.hashnode.dev/implementing-exponential-backoff-for-reliable-systems