Logica di Riprova Fatta Bene: Implementazione del Backoff Esponenziale per Sistemi Affidabili

Nel sviluppo software, una logica di ripetizione affidabile è essenziale per gestire i fallimenti intermittenti, come problemi di rete o interruzioni temporanee. Di recente, mi sono imbattuto in un codice in cui uno sviluppatore ha utilizzato un ciclo for con un intervallo di tempo fisso per ripetere le operazioni fallite. Sebbene questo approccio possa sembrare semplice, manca della resilienza necessaria per le applicazioni del mondo reale. È qui che entra in gioco l’Exponential Backoff: una strategia progettata per rendere le ripetizioni più intelligenti ed efficienti.

In questo articolo, esamineremo come funziona l’Exponential Backoff, i suoi vantaggi rispetto a un ciclo di ripetizione di base e come puoi implementarlo per migliorare l’affidabilità del tuo sistema. Ti guiderò anche attraverso un esempio pratico utilizzando un modulo di invio email, mostrandoti come usare l’Exponential Backoff per garantire una gestione degli errori più resiliente.

L’Exponential Backoff è una strategia di ripetizione in cui il tempo di attesa tra i tentativi di ripetizione aumenta in modo esponenziale dopo ogni fallimento. Invece di ripetere a intervalli fissi, ogni tentativo successivo aspetta più a lungo rispetto a quello precedente, raddoppiando tipicamente il ritardo ogni volta. Ad esempio, se il ritardo iniziale è di 1 secondo, i tentativi successivi si verificheranno a 2, 4, 8 secondi e così via. Questo approccio aiuta a ridurre il carico sul sistema e minimizza il rischio di sovraccaricare i servizi esterni durante i periodi di alta richiesta.

Consentendo più tempo tra le ripetizioni, l’Exponential Backoff dà la possibilità ai problemi temporanei di risolversi, portando a una gestione degli errori più efficiente e a una maggiore stabilità dell’applicazione.

  • Carico di Sistema Ridotto: Spaziando i tentativi di ripetizione, l’Exponential Backoff minimizza la possibilità di sovraccaricare i server, particolarmente utile per gestire limiti di velocità o interruzioni transitorie.

  • Gestione Efficiente degli Errori: Il ritardo crescente consente ai problemi transitori più tempo per risolversi in modo naturale, migliorando la probabilità di un tentativo di ripetizione riuscito.

  • Stabilità Migliorata: Specialmente per sistemi ad alto traffico, previene un’inondazione di tentativi di ripetizione, mantenendo le applicazioni funzionanti senza un consumo eccessivo di risorse.

  • Aumento della Latenza: Con ogni tentativo di ripetizione che richiede progressivamente più tempo, l’Exponential Backoff può comportare ritardi, specialmente se sono necessari molti tentativi prima del successo.

L’Exponential Backoff è particolarmente utile in scenari in cui i sistemi interagiscono con servizi esterni o gestiscono grandi volumi di traffico. Ecco alcuni altri casi d’uso comuni:

  1. API con Limiti di Richiesta: Alcune API hanno limiti di richiesta, restringendo le richieste entro un certo periodo. Il backoff esponenziale aiuta a evitare ripetizioni immediate che potrebbero superare il limite, dando tempo affinché il limite si resetti.

  2. Instabilità della Rete: In caso di guasti temporanei della rete o timeout, il backoff esponenziale aiuta ad aspettare più a lungo tra i tentativi, permettendo alla rete di stabilizzarsi.

  3. Connessioni al Database: Quando ci si connette a database sotto carico pesante, il backoff esponenziale aiuta a prevenire un ulteriore sovraccarico ritardando i tentativi, dando al database il tempo di recuperare.

  4. Sistemi di Coda: Nei sistemi di coda dei messaggi, se un messaggio fallisce a causa di un errore, utilizzare il Backoff Esponenziale per i tentativi può prevenire una rapida riprocessazione e dare tempo per risolvere problemi temporanei.

Per dimostrare l’Exponential Backoff, costruiremo un semplice inviatore di email che riprova a inviare email se si verifica un errore. Questo esempio mostra come l’Exponential Backoff migliori il processo di tentativo rispetto a un semplice ciclo 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;
};

Implementare l’Exponential Backoff richiede di regolare alcuni parametri per garantire che la strategia di tentativo funzioni bene per le esigenze della tua applicazione. I seguenti parametri chiave influenzano il comportamento e le prestazioni dell’Exponential Backoff in un meccanismo di tentativo:

  1. Ritardo Iniziale
  • Scopo: Imposta il tempo di attesa prima del primo tentativo. Dovrebbe essere abbastanza lungo da prevenire tentativi immediati, ma abbastanza breve da evitare ritardi evidenti.

  • Impostazione Raccomandata: Inizia con un ritardo tra 500 ms e 1000 ms. Per sistemi critici, utilizza un ritardo più breve, mentre operazioni meno urgenti possono avere un ritardo più lungo.

  1. Moltiplicatore di Ritardo
  • Scopo: Controlla quanto rapidamente aumenta il ritardo dopo ogni tentativo di ripetizione. Un moltiplicatore di 2 raddoppia il ritardo (ad es. 1s, 2s, 4s).

  • Impostazione Raccomandata: Tipicamente, un moltiplicatore tra 1.5 e 2 bilancia reattività e stabilità. Moltiplicatori più alti (ad es. 3) possono essere adatti se il sistema può gestire ritardi più lunghi tra i tentativi di ripetizione.

  1. Massimo Tentativi di Ripetizione
  • Scopo: Limita i tentativi di ripetizione per prevenire tentativi eccessivi che potrebbero esaurire le risorse o aumentare il carico del sistema.

  • Impostazione Raccomandata: Un intervallo di 3 a 5 tentativi è solitamente sufficiente per la maggior parte delle applicazioni. Oltre questo, l’operazione potrebbe dover essere registrata come fallita o gestita diversamente, come notificare l’utente o attivare un allerta.

  1. Jitter (Randomizzazione)
  • Scopo: Aggiunge casualità a ogni ritardo per prevenire che i tentativi si accumulino e causino un effetto di mandria in tempesta.

  • Impostazione consigliata: Aggiungi un ritardo casuale tra 0 e 500 ms a ciascun intervallo di tentativo. Questo jitter aiuta a distribuire i tentativi di ripetizione in modo più uniforme nel tempo.

Utilizzando l’Exponential Backoff, aggiungi resilienza alla tua applicazione, preparandola a gestire problemi imprevisti. È un piccolo cambiamento con un grande impatto, specialmente man mano che la tua applicazione cresce.

E questo è tutto per ora, ragazzi. Sentitevi liberi di lasciare un commento e fare domande se ne avete. Un brindisi alla costruzione di app più affidabili e resilienti.

Buon coding! 👨‍💻❤️

Source:
https://timothy.hashnode.dev/implementing-exponential-backoff-for-reliable-systems