Introdução
No desenvolvimento de software, a 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-fonte onde um desenvolvedor utilizou um loop for
com um intervalo de tempo fixo para repetir operações falhadas. Embora essa abordagem possa parecer direta, ela carece da resiliência necessária para aplicações do mundo real. É aí que entra o Backoff Exponencial – uma estratégia projetada para tornar as repetições mais inteligentes e eficientes.
Neste artigo, vamos explorar como o Backoff Exponencial funciona, suas vantagens sobre um loop de repetição básico e como você pode implementá-lo para melhorar a confiabilidade do seu sistema. Também vou guiar você por um exemplo prático usando um módulo de envio de e-mails, mostrando como usar o Backoff Exponencial para garantir um tratamento de erros mais resiliente.
O que é o Backoff Exponencial?
O Backoff Exponencial é 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 pressão sobre o 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 Backoff Exponencial dá às questões temporárias a oportunidade de se resolverem, levando a um tratamento de erros mais eficiente e a uma maior estabilidade da aplicação.
Prós e Contras do Backoff Exponencial
Prós:
-
Carga do Sistema Reduzida: Ao espaçar as tentativas, o Retorno Exponencial minimiza a chance de sobrecarregar os servidores, sendo especialmente útil para lidar com limites de taxa ou interrupções transitórias.
-
Tratamento Eficiente de Erros: O aumento do atraso permite que problemas transitórios tenham mais tempo para se resolverem naturalmente, melhorando a probabilidade de uma tentativa bem-sucedida.
-
Estabilidade Melhorada: Especialmente para sistemas de alto tráfego, isso previne uma inundação de tentativas de repetição, mantendo as aplicações funcionando sem problemas e sem consumo excessivo de recursos.
Contras:
- Latência Aumentada: Com cada tentativa levando progressivamente mais tempo, o Retorno Exponencial pode resultar em atrasos, especialmente se muitas tentativas forem necessárias antes do sucesso.
Casos de Uso Chave para o Retorno Exponencial
O Retorno Exponencial é particularmente útil em cenários onde 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 solicitações dentro de um determinado período de tempo. O Exponential Backoff ajuda a evitar tentativas imediatas que poderiam exceder o limite, dando tempo para que o limite seja redefinido.
-
Instabilidade de Rede: Em casos de falhas temporárias na 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 conectar-se a bancos de dados sob carga pesada, o exponential backoff ajuda a evitar uma sobrecarga adicional ao atrasar as tentativas, permitindo que o banco de dados se recupere.
-
Sistemas de Filas: Em sistemas de filas de mensagem, se uma mensagem falhar devido a um erro, o uso do Exponential Backoff para tentativas pode evitar um rápido reprocessamento e permitir tempo para que problemas temporários sejam resolvidos.
Construindo um Serviço Básico de Envio de Email com Exponencial Backoff
Para demonstrar o Exponencial Backoff, construiremos um serviço básico de envio de email que tenta reenviar emails se ocorrer um erro. Este exemplo mostra como o Exponencial Backoff melhora o processo de tentativa em comparação a um simples laço for.
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 os Parâmetros do Exponencial Backoff
Implementar o Exponencial Backoff envolve ajustar certos parâmetros para garantir que a estratégia de tentativa funcione bem para as necessidades do seu aplicativo. Os seguintes parâmetros-chave afetam o comportamento e o desempenho do Exponencial Backoff em um mecanismo de tentativa:
- Atraso Inicial
-
Objetivo: Define o tempo de espera antes da primeira tentativa. Deve ser longo o suficiente para evitar tentativas imediatas, 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 menor, enquanto operações menos urgentes podem ter um atraso maior.
- Multiplicador de Atraso
-
Propósito: Controla o aumento do atraso após cada tentativa. Um multiplicador de 2 dobra o atraso (por exemplo, 1s, 2s, 4s).
-
Configuração Recomendada: Geralmente, um multiplicador entre 1,5 e 2 equilibra a responsividade e a 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 repetição para evitar repetições excessivas que poderiam drenar recursos ou aumentar a carga do sistema.
-
Configuração Recomendada: Uma faixa de 3 a 5 tentativas 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 acionar um alerta.
- Jitter (Randomização)
-
Propósito: Adiciona aleatoriedade a cada atraso para evitar que as tentativas de repetição se agrupem e causem um efeito de manada de elefantes.
-
Configuração Recomendada: Adicionar um atraso aleatório entre 0 e 500 ms a cada intervalo de repetição. Essa aleatoriedade ajuda a espaçar de forma mais uniforme as tentativas de repetição ao longo do tempo.
Conclusão
Ao usar o Retrocesso Exponencial, 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 por agora pessoal. Fique à vontade para deixar um comentário e fazer perguntas se tiver alguma. Parabéns por construir aplicativos mais confiáveis e resilientes.
Feliz codificação! 👨💻❤️
Source:
https://timothy.hashnode.dev/implementing-exponential-backoff-for-reliable-systems