신뢰할 수 있는 시스템을 위한 지수 백오프 구현: 올바른 재시도 로직

소프트웨어 개발에서 신뢰할 수 있는 재시도 로직은 네트워크 문제나 임시 중단과 같은 간헐적 실패를 처리하는 데 필수적입니다. 최근에 한 개발자가 고정 시간 간격으로 실패한 작업을 재시도하는 for 루프를 사용한 코드베이스를 보았습니다. 이 접근 방식은 간단해 보일 수 있지만 실제 애플리케이션에서 필요한 회복력이 부족합니다. 그곳에서 지수 백오프가 등장합니다. 이는 재시도를 더 스마트하고 효율적으로 만들기 위해 설계된 전략입니다.

이 기사에서는 지수 백오프가 어떻게 작동하는지, 기본 재시도 루프에 비해 어떤 장점이 있는지, 그리고 시스템의 신뢰성을 높이기 위해 이를 어떻게 구현할 수 있는지 살펴보겠습니다. 또한 이메일 발송 모듈을 사용하여 지수 백오프를 활용해 보다 탄력적인 오류 처리를 보장하는 방법을 실용적인 예제로 안내하겠습니다.

지수 백오프는 재시도 시도 간의 대기 시간이 각 실패 후 기하급수적으로 증가하는 재시도 전략입니다. 고정 간격으로 재시도하는 대신, 각 후속 시도는 이전 시도보다 더 오래 기다립니다. 일반적으로 매번 지연 시간을 두 배로 늘립니다. 예를 들어, 초기 지연 시간이 1초라면 다음 재시도는 2, 4, 8초 등으로 진행됩니다. 이 접근 방식은 시스템의 부담을 줄이고 높은 수요 기간 동안 외부 서비스를 압도할 위험을 최소화하는 데 도움이 됩니다.

재시도 간의 시간을 더 많이 허용함으로써 지수 백오프는 임시 문제를 해결할 기회를 제공하여 보다 효율적인 오류 처리와 향상된 애플리케이션 안정성으로 이어집니다.

  • 시스템 부하 감소: 재시도를 간격을 두고 수행함으로써, 지수 백오프는 서버가 압도당할 가능성을 최소화하며, 특히 속도 제한이나 일시적인 장애를 처리하는 데 유용합니다.

  • 효율적인 오류 처리: 증가하는 지연은 일시적인 문제에 자연스럽게 해결될 수 있는 시간을 더 제공하여, 성공적인 재시도의 가능성을 향상시킵니다.

  • 안정성 향상: 특히 높은 트래픽을 처리하는 시스템에서 재시도 시도가 쏟아지는 것을 방지하여, 과도한 자원 소비 없이 애플리케이션이 원활하게 실행되도록 합니다.

  • 지연 증가: 각 재시도가 점점 더 오래 걸림에 따라, 지수 백오프는 지연을 초래할 수 있으며, 특히 성공하기 전에 많은 재시도가 필요한 경우 더욱 그렇습니다.

지수 백오프는 시스템이 외부 서비스와 상호 작용하거나 대량의 트래픽을 관리하는 시나리오에서 특히 유용합니다. 다음은 다른 일반적인 사용 사례입니다:

  1. 요금 제한 API: 일부 API는 요금 제한이 있어 특정 시간 내에 요청을 제한합니다. 지수 백오프는 즉각적인 재시도를 피하는 데 도움을 주어 제한이 초기화될 시간을 제공합니다.

  2. 네트워크 불안정성: 일시적인 네트워크 장애나 타임아웃의 경우, 지수 백오프는 시도 간의 대기 시간을 늘려 네트워크가 안정화될 수 있도록 도와줍니다.

  3. 데이터베이스 연결: 부하가 많이 걸린 데이터베이스에 연결할 때, 지수 백오프는 재시도를 지연시켜 추가 과부하를 방지하고 데이터베이스가 회복할 시간을 제공합니다.

  4. 큐 시스템: 메시지 큐 시스템에서 메시지가 오류로 실패할 경우, 재시도에 지수 백오프를 사용하면 빠른 재처리를 방지하고 일시적인 문제를 해결할 시간을 줄 수 있습니다.

지수 백오프를 시연하기 위해, 오류가 발생할 경우 이메일 발송을 재시도하는 기본 이메일 발송기를 구축할 것입니다. 이 예제는 단순한 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;
};

지수 백오프를 구현하는 것은 재시도 전략이 애플리케이션의 요구에 잘 맞도록 특정 매개변수를 조정하는 것을 포함합니다. 다음의 주요 매개변수는 재시도 메커니즘에서 지수 백오프의 동작과 성능에 영향을 미칩니다:

  1. 초기 지연
  • 목적: 첫 번째 재시도 전에 대기 시간을 설정합니다. 즉각적인 재시도를 방지할 만큼 길어야 하지만 눈에 띄는 지연을 피할 만큼 짧아야 합니다.
  • 추천 설정: 500 ms에서 1000 ms 사이의 지연으로 시작합니다. 중요 시스템의 경우 더 짧은 지연을 사용하고, 덜 긴급한 작업은 더 긴 지연을 가질 수 있습니다.
  1. 지연 배수기
  • 목적: 각 재시도 후 지연이 얼마나 빨리 증가하는지를 제어합니다. 2의 배수기는 지연 시간을 두 배로 늘립니다(예: 1초, 2초, 4초).

  • 추천 설정: 일반적으로 1.52 사이의 배수기가 반응성과 안정성을 균형 있게 유지합니다. 시스템이 재시도 사이의 긴 지연을 처리할 수 있다면 더 높은 배수기(예: 3)가 적합할 수 있습니다.

  1. 최대 재시도 횟수
  • 목적: 자원을 소모하거나 시스템 부하를 증가시킬 수 있는 과도한 재시도를 방지하기 위해 재시도 횟수를 제한합니다.

  • 추천 설정: 대부분의 애플리케이션에는 3에서 5회의 재시도가 일반적으로 충분합니다. 이 이상으로 넘어가면 작동이 실패로 기록되거나 사용자에게 알림을 보내거나 경고를 발생시키는 등 다른 방식으로 관리되어야 할 수 있습니다.

  1. 지터 (무작위화)
  • 목적: 각 지연에 무작위성을 추가하여 재시도가 집중되는 것을 방지하고 천둥 무리 효과를 초래하지 않도록 합니다.
  • 추천 설정: 각 재시도 간격에 0에서 500ms 사이의 무작위 지연을 추가합니다. 이 지터는 재시도 시도를 시간이 지남에 따라 더 고르게 분산시키는 데 도움을 줍니다.

지수 백오프를 사용함으로써 애플리케이션에 회복력을 추가하여 예기치 않은 문제를 처리할 준비를 합니다. 이는 작은 변화이지만 큰 영향을 미치며, 특히 애플리케이션이 성장함에 따라 더욱 그렇습니다.

그럼 지금은 여기까지입니다, 여러분. 댓글을 남기고 질문이 있으면 언제든지 물어보세요. 더 신뢰할 수 있고 회복력 있는 앱을 만드는 것에 건배합니다.

행복한 코딩 되세요! 👨‍💻❤️

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