(번역) 자바스크립트를 이용한 재시도 로직 구현하기

TapK·2024년 5월 13일
47

원문: https://anu95.medium.com/implement-retry-logic-using-javascript-e502693e0b5c

소개

열정적인 자바스크립트 사용자인 우리들은 애플리케이션을 위한 데이터 페칭을 하거나 정보를 생성하고 수정하기 위해 REST API와 마이크로서비스에 의존합니다. 가끔 우리는 일시적인 오류와 불안정한 연결 또는 서비스 중단 시간과 같은 문제에 직면하게 됩니다. 이러한 상황에서 작업을 다시 시도할 효과적인 전략을 갖추고 있지 않을 경우 애플리케이션은 중단되므로 다소 실망스러운 사용자 경험을 제공할 수 있습니다. 🌐💡

이를 개선하고 애플리케이션이 매끄럽게 실행되도록 하려면 견고한 재시도 전략을 구현해야만 합니다. 이 접근 방식을 사용하면 애플리케이션 중단 현상을 우아하게 처리하여 안정성과 사용자 경험을 유지할 수 있습니다. 실패한 작업을 현명하게 재시도할 수 있도록 준비하여 일반적인 네트워크 문제에 대한 복원력이 향상됩니다. 🚀🛡️

재시도 전략 깃허브 예제: rerty-strategies-js

재시도 로직을 구현하기 위한 다양한 방법에 대해 살펴보겠습니다! 🤿

그렇다면 재시도 전략은 무엇입니까?

탄력적인 애플리케이션의 핵심으로 재시도 전략이라는 강력한 개념이 있습니다.

패배는 재시도를 거부하는 이들에게만 일어난다.

애플리케이션을 알 수 없는 장애물로 가득 찬 풍경을 헤쳐 나가는 결연한 여행자라고 상상해보세요. 여행자가 폐쇄된 도로나 나쁜 날씨를 만날 수 있듯이, 애플리케이션도 종종 갑작스러운 서비스 중단이나 네트워크 결함과 같은 디지털 영역에서 비슷한 장애물에 직면하게 됩니다. 재시도 전략은 이러한 시나리오에서 지도이자 나침반이며, 애플리케이션이 목적지에 성공적으로 도달하기 위해 언제, 어떻게 다시 시도해야 할지 알려줍니다. 🗺️⏳

문제가 일시적인 결함인지 아니면 더 심각한 문제인지 애플리케이션을 이해하는 데 도움이 됩니다. 이러한 이해를 바탕으로 애플리케이션은 조금 기다렸다가 다시 시도할지, 아니면 요청 시기나 방법을 조정함으로써 조금 다른 경로를 선택할지를 결정할 수 있습니다.

몇 가지 전략에 대한 요약

요청 실패 시뮬레이션을 가지고 예제 코드를 통해 다양한 재시도 전략을 탐구해 봅시다. 각각의 전략은 서로 다른 디지털 영역의 문제를 극복하고 애플리케이션을 올바른 길로 유도하는 독특한 방법을 제공합니다. 🛤️

📈 지수 백오프(Exponential Backoff)

이것은 웅덩이를 뛰어넘으려고 할 때 점점 보폭이 커지는 걸 생각하면 됩니다. 처음에는 작은 도약으로 시작하지만, 시도할 때마다 도약의 폭이 기하급수적으로 늘어납니다. 이 전략은 우리의 디지털 경로에 대한 부담을 줄여줌으로써, 우리의 노력이 다시 물에 빠질 가능성을 낮춰줍니다.

시스템 로드와 실패 가능성을 줄이기 위해 각 재시도 후 대기 시간을 두 배로 늘립니다.

const axios = require("axios");

let attemptCounter = 0; // 시도 횟수 추적

/**
 * 시물레이션된 실패와 axios를 사용하여 주어진 URL로부터 데이터를 가져오기 위해 시도합니다.
 * 이 함수는 오류를 던져 처음 두 번의 시도에 대한 네트워크 실패를 시뮬레이션하여 재시도 메커니즘이 어떻게 일시적인 오류를 처리하는지 보여줍니다.
 * 매개변수:
 * - url: 데이터를 가져올 URL
 * - retries: 재시도 허용 횟수
 * - delay: 재시도 전 초기 지연 시간, 재시도 시 두 배
 *
 * 이 함수는 재시도 사이에서 발생한 지연을 위해 지수 백오프를 사용하고, 복구 시간을 제공하여 일시적인 네트워크 문제를 효과적으로 처리합니다.
 */
const fetchData = async (url, retries, delay) => {
  try {
    attemptCounter++;
    if (attemptCounter <= 2) {
      throw new Error("Simulated network failure");
    }

    const response = await axios.get(url);
    console.log(`Success: ${response.status}`);
    return response.data;
  } catch (error) {
    console.log(
      `Attempt ${attemptCounter} failed with error: ${error.message}. Waiting ${delay} ms before retrying.`
    );
    if (retries > 0) {
      await new Promise((resolve) => setTimeout(resolve, delay));
      return fetchData(url, retries - 1, delay * 2);
    } else {
      throw new Error("All retries failed");
    }
  }
};

const url = "https://jsonplaceholder.typicode.com/posts/1";
fetchData(url, 3, 1000).catch(console.error);

/**
 * 결과물:
 * 시도 1 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 1000 밀리초를 기다립니다.
 * 시도 2 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 2000 밀리초를 기다립니다.
 * 성공: 200
 */

➡️ ️선형 백오프

이건 일정한 간격으로 한 걸음씩 앞으로 나아가는 것처럼 올곧은 길을 걷는 것과 같습니다. 재시도할 때마다, 조금 더 기다리지만 대기 시간은 매번 같은 양만큼 늘어납니다. 꾸준하게 예측 가능하기 때문에 서두르거나 비틀거리지 않도록 합니다.

예측할 수 있는 지연 시간에 대해 매번 재시도 후 일정한 양만큼 대기 시간을 늘립니다.

const axios = require("axios");

let attemptCounter = 0; // 실패 시뮬레이션 시도 횟수 추적

/**
 * 이 함수는 HTTP 요청에 대해 axios를 사용하는 선형 백오프 전략을 보여줍니다.
 * 초기 시도에서 네트워크 실패를 시뮬레이션한 다음 성공하여, 애플리케이션이 적절한 재시도 로직을 기반으로 일시적인 문제로부터 어떻게 회복할 수 있는지 보여줍니다.
 *
 * 선형 백오프 전략은 재시도 간의 지연 시간을 고정적으로 증가시켜 재시도 간격을 관리하는 균형 잡힌 접근 방식을 제공하며, 다음 시도 전에 시스템이 복구할 수 있는 시간을 제공합니다.
 *
 * 매개변수:
 * - url: 데이터를 가져올 URL
 * - retries: 재시도 허용 횟수
 * - delay: 첫 번째 재시도 전 초기 지연 시간
 * - increment: 재시도 후 지연 시간 증가량
 *
 * 실패 시, 이 함수는 지정된 지연 시간을 기다린 후 선형 백오프 계산을 기반으로 증가한 지연 시간으로 요청을 재시도 합니다.
 */
const fetchDataWithLinearBackoff = async (url, retries, delay, increment) => {
  try {
    attemptCounter++;
    if (attemptCounter <= 3) {
      throw new Error("Simulated network failure");
    }

    const response = await axios.get(url);
    console.log(`Success: ${response.status}`);
    return response.data;
  } catch (error) {
    console.log(
      `Attempt ${attemptCounter} failed with error: ${error.message}. Waiting ${delay} ms before retrying.`
    );
    if (retries > 0) {
      await new Promise((resolve) => setTimeout(resolve, delay));
      return fetchDataWithLinearBackoff(
        url,
        retries - 1,
        delay + increment,
        increment
      );
    } else {
      throw new Error("All retries failed");
    }
  }
};

const url = "https://jsonplaceholder.typicode.com/posts/1";
fetchDataWithLinearBackoff(url, 5, 1000, 2000).catch(console.error);

/**
 * 결과물:
 * 시도 1 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 1000 밀리초를 기다립니다.
 * 시도 2 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 3000 밀리초를 기다립니다.
 * 시도 3 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 5000 밀리초를 기다립니다.
 * 성공: 200
 */

🕛 고정 지연

얼마나 멀리 달렸는지에 관계없이 일정한 간격으로 숨을 쉬기 위해 멈추는 것을 상상해 보세요. 이 전략은 재시도 사이에 대기 시간을 동일하게 유지하여 애플리케이션이 너무 빨리 지치지 않도록 보장하고 일관된 리듬을 제공합니다.

시도 횟수와 상관없이 재시도 간 대기 시간을 일정하게 유지합니다.

const axios = require("axios");

let attemptCounter = 0; // 시나리오 시뮬레이션을 위한 시도 횟수 추적 방법

/**
 * axios를 사용하여 HTTP 요청에 대한 고정 지연 재시도 전략을 구현하는 방법을 시도합니다.
 * 초기 시도에 대한 실패를 시뮬레이션하여 고정 지연 재시도가 시도 횟수에 상관없이 재시도 전에 미리 계산된 시간을 대기함으로써 일시적인 오류를 효과적으로 관리할 방법을 설명합니다.
 *
//  * 고정 지연 방식은 재시도 간격을 일정하게 유지하여 시스템 복구 및 오류 해결을 다음 시도 전에 가능하게 하는 간단하고 예측 가능한 방법을 제공합니다. 이 전략은 복구 예상 시간이 일정한 상황에서 특히 유용합니다.
 *
 * 매개변수:
 * - url: HTTP GET 요청을 위한 엔드포인트 URL
 * - retries: 종료 전 재시도 횟수
 * - delay: 재시도 전 대기하는 고정 시간(밀리초)
 */
const fetchData = async (url, retries, delay) => {
  try {
    attemptCounter++;

    if (attemptCounter <= 3) {
      throw new Error("Simulated network failure");
    }

    const response = await axios.get(url);
    console.log(`Success: ${response.status}`);
    return response.data;
  } catch (error) {
    console.log(
      `Attempt ${attemptCounter} failed with error: ${error.message}. Waiting ${delay} ms before retrying.`
    );
    if (retries > 0) {
      await new Promise((resolve) => setTimeout(resolve, delay));
      return fetchData(url, retries - 1, delay);
    } else {
      throw new Error("All retries failed");
    }
  }
};

const url = "https://jsonplaceholder.typicode.com/posts/1";
fetchData(url, 4, 1000).catch(console.error);

/**
 * 결과물:
 * 시도 1 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 1000 밀리초를 기다립니다.
 * 시도 2 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 1000 밀리초를 기다립니다.
 * 시도 3 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 1000 밀리초를 기다립니다.
 * 성공: 200
 */

🌀 피보나치 백오프

이 접근법은 자연의 나선형에서 영감을 얻어 피보나치 수열을 따라 대기 시간을 증가시킵니다. 각 단계는 마지막 두 단계의 지혜를 결합하여, 서두르거나 너무 오래 기다리는 것 사이의 조화로운 균형을 찾아내며, 자연스러운 우아함으로 문제를 헤쳐 나가도록 안내합니다.

피보나치 수열을 사용하여 급격하거나 완화된 지연 사이의 균형을 유지하며 대기 시간을 결정합니다.

const axios = require("axios");

let attemptCounter = 0; // 시뮬레이션을 위한 현재 시도 추적

/**
 * 주어진 인덱스에 대한 피보나치 수를 계산합니다. 피보나치수열은 보통 0과 1의 시작으로 각각의 수가 앞의 두 수의 합이 되는 수열입니다.
 *
 * - index: 피보나치수열의 위치
 */
const calculateFibonacciNumber = (index) => {
  if (index <= 1) return index;
  let previous = 0,
    current = 1,
    temp;
  for (let i = 2; i <= index; i++) {
    temp = previous + current;
    previous = current;
    current = temp;
  }
  return current;
};

/**
 * axios를 사용하여 HTTP GET 요청에 대한 피보나치 백오프 전략을 기반한 재시도를 수행합니다.
 * 처음 몇 번의 시도에서 네트워크 실패를 시뮬레이션하여, 피보나치수열을 기반한 지연 시간이 증가하는 재시도 전략을 사용함으로써 애플리케이션이 어떻게 다시 정상 작동하게 되는지 보여줍니다.
 * - url: 요청 보낼 URL
 * - retries: 실패 전 허용된 재시도 횟수
 * - baseDelay: 피보나치 백오프 계산을 위한 기본 지연 시간(밀리초)
 */
const fetchData = async (url, retries, baseDelay) => {
  try {
    attemptCounter++;

    if (attemptCounter <= 2) {
      throw new Error("Simulated network failure");
    }

    const response = await axios.get(url);
    console.log(`Success: ${response.status}`);
    return response.data;
  } catch (error) {
    console.log(
      `Attempt ${attemptCounter} failed with error: ${error.message}. Waiting for the next attempt.`
    );
    if (retries > 0) {
      const delay = calculateFibonacciNumber(5 - retries + 1) * baseDelay;
      console.log(`Waiting ${delay} ms before retrying.`);
      await new Promise((resolve) => setTimeout(resolve, delay));
      return fetchData(url, retries - 1, baseDelay);
    } else {
      throw new Error(
        "All retries failed after " + attemptCounter + " attempts"
      );
    }
  }
};

const url = "https://jsonplaceholder.typicode.com/posts/1";
fetchData(url, 5, 100).catch(console.error);

/**
 * 결과물:
 * 시도 1 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 100 밀리초를 기다립니다.
 * 시도 2 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 100 밀리초를 기다립니다.
 * 성공: 200
 */

🎲 무작위 재시도

주사위를 굴려 대기 시간을 결정하는 것처럼, 이 전략은 재시도 타이밍에 우연한 요소를 도입합니다. 이런 무작위성은 우리의 시도를 보다 골고루 분산시켜, 모두가 한꺼번에 통과하려는 부담을 줄이면서 시스템에 대한 부담을 감소시킵니다.

시도를 분산하고 시스템 부하를 줄이기 위해 재시도 전임의 대기 시간을 선택합니다.

const axios = require("axios");

let attemptCounter = 0; // 시도 횟수 추적 및 네트워크 오류 시뮬레이션.

/**
 * 무작위 지연을 포함한 재시도와 함께 axios를 사용하여 HTTP GET 요청을 수행합니다.
 * 네트워크 오류 시뮬레이션 전략은 특정 최소 범위와 최대 범위 사이의 임의 지연을 재시도하여 복구할 방법을 설명합니다.
 * 재시도 간격의 랜덤화는 부하 분산과 시스템 피크 압력 감소에 도움이 됩니다.
 *
 * - url: HTTP GET 요청을 위한 엔드포인트 URL
 * - retries: 종료 전 재시도 횟수
 * - minDelay: 재시도 전 최소 지연 시간(밀리초)
 * - maxDelay: 재시도 전 최대 지연 시간(밀리초)
 */
const fetchData = async (url, retries, minDelay, maxDelay) => {
  try {
    attemptCounter++;

    if (attemptCounter <= 2) {
      throw new Error("Simulated network failure");
    }

    const response = await axios.get(url);
    console.log(`Success: ${response.status}`);
    return response.data;
  } catch (error) {
    console.log(
      `Attempt ${attemptCounter} failed with error: ${error.message}.`
    );
    if (retries > 0) {
      const randomDelay = Math.random() * (maxDelay - minDelay) + minDelay; // 임의 지연 시간 계산
      console.log(`Waiting ${Math.round(randomDelay)} ms before retrying.`);
      await new Promise((resolve) =>
        setTimeout(resolve, Math.round(randomDelay))
      );
      return fetchData(url, retries - 1, minDelay, maxDelay);
    } else {
      throw new Error(`All retries failed after ${attemptCounter} attempts`);
    }
  }
};

const url = "https://jsonplaceholder.typicode.com/posts/1";
fetchData(url, 3, 500, 1500).catch(console.error);

/**
 * 결과물:
 * 시도 1 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 1487 밀리초를 기다립니다.
 * 시도 2 실패 오류: 시뮬레이션한 네트워크 실패. 재시도 전 777 밀리초를 기다립니다.
 * 성공: 200
 */

⚡ 즉시 재시도

때로는 가장 좋은 접근 방식은 즉시 다시 시도하는 것입니다. 특히 빠르게 문제가 해결될 것 같을 때 더욱 좋습니다. 이 전략은 기회를 잡는 것으로, 문제의 첫 징후가 나타나는 즉시 다시 시작할 준비를 하는 것입니다.

지체 없이 즉시 재시도하므로, 신속하게 해결할 수 있는 문제에 이상적입니다.

const axios = require("axios");

let attemptCounter = 0; // 네트워크 실패를 시뮬레이션하기 위해 현재 시도 횟수 추적.

/**
 * 실패 시 즉각적인 재시도 전략을 사용하여 axios를 사용한 HTTP GET 요청을 실행합니다.
 * 이 접근법은 초반에 몇 번의 시도를 통해 네트워크 실패를 시뮬레이션하여, 빠르게 해결할 수 있는 일시적인 문제를 어떻게 애플리케이션이 지연 없이 즉시 재시도하여 적용되는지 설명합니다.
 *
 * 즉각적인 재시도는 오류가 일시적일 가능성이 높고 지연을 적용하지 않고 해결될 수 있는 상황에서 사용됩니다. 이는 네트워크 안정성이 변동하는 환경에서 성공적인 요청 가능성에서 효과적입니다.
 *
 *
 * - url: HTTP GET 요청을 위한 엔드포인트 URL
 * - retries: 종료 전 재시도 횟수
 */
const fetchData = async (url, retries) => {
  try {
    attemptCounter++;

    if (attemptCounter <= 3) {
      throw new Error("Simulated network failure");
    }

    const response = await axios.get(url);
    console.log(`Success: ${response.status}`);
    return response.data;
  } catch (error) {
    console.log(
      `Attempt ${attemptCounter} failed with error: ${error.message}.`
    );
    if (retries > 0) {
      console.log("Retrying immediately.");
      return fetchData(url, retries - 1);
    } else {
      throw new Error(`All retries failed after ${attemptCounter} attempts`);
    }
  }
};

const url = "https://jsonplaceholder.typicode.com/posts/1";
fetchData(url, 5).catch(console.error);

/**
 * 결과물:
 * 시도 1 실패 오류: 시뮬레이션한 네트워크 실패. 즉시 재시도.
 * 시도 2 실패 오류: 시뮬레이션한 네트워크 실패. 즉시 재시도.
 * 시도 3 실패 오류: 시뮬레이션한 네트워크 실패. 즉시 재시도.
 * 성공: 200
 */

재시도 로직을 위한 npm 패키지 활용

axios-retry

axios-retry는 axios 요청에 재시도 기능을 추가하는 간단한 방법을 제공합니다. 이 라이브러리는 네트워크 오류 또는 특정 HTTP 응답 코드와 같은 특정 조건에서 실패한 요청을 자동으로 재시도할 수 있고, 지수 백오프를 포함한 구성 가능한 재시도 전략을 지원합니다.

먼저 axios-retry를 사용하려면 axios와 axios-retry를 설치해야 합니다.

npm install axios axios-retry

또는

yarn add axios axios-retry

그리고서 다음과 같이 axios-retry를 구성할 수 있습니다.

import axios from "axios";
import axiosRetry from "axios-retry";

// 자동 재시도 요청을 위한 axios-retry 구성
axiosRetry(axios, {
  retries: 3, // 재시도 횟수
  retryDelay: axiosRetry.exponentialDelay, // 재시도 사이에 지수 백오프 지연 사용
});

// 실패 시 재시도되는 axios 요청
axios
  .get("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => console.log(response.data))
  .catch((error) => console.error(error));

프런트엔드에서 axios-retry를 사용하면 애플리케이션에서 HTTP 요청에 대한 재시도 처리를 크게 간소화하여 최소한의 코드 추가로 견고하고 믿을 수 있는 웹 앱을 만들 수 있습니다.

npm - axios-retry

retry

retry 패키지는 코드에 재시도 기능을 추가한 유연한 방법을 제공하고, 실패 시 여러 번 시도하려는 비동기 연산이나 로직 처리에 적합합니다. 이제 사용 방법에 대한 기본 가이드를 보시죠.

먼저, npm 또는 yarn 사용해서 패키지를 설치하세요.

npm install retry

또는

yarn add retry

아래 예제는 실패할 수 있는 작업을 수행하기 위해 retry를 사용하는 간단한 예시입니다.

const retry = require("retry");
const axios = require("axios"); // axios가 HTTP 요청에 사용된다고 가정해봅시다.

async function fetchData(url) {
  const operation = retry.operation({
    retries: 3, // 최대 재시도 횟수
    factor: 2, // 지연에 대한 지수 계수
    minTimeout: 1000, // 첫 번째 재시도를 시작하기 전의 시간(밀리초)
    maxTimeout: 2000, // 두 번째 재시도 사이의 최대 시간(밀리초)
  });

  operation.attempt(async (currentAttempt) => {
    try {
      const response = await axios.get(url);
      console.log("Data:", response.data);
    } catch (error) {
      console.log(`Attempt ${currentAttempt} failed: ${error.message}`);
      if (operation.retry(error)) {
        console.log(`Retrying...`);
        return;
      }
      console.error("Request failed after retries:", error.message);
    }
  });
}

fetchData("https://jsonplaceholder.typicode.com/posts/1");

retry 패키지는 강력하고 유연합니다. 단지 HTTP 요청을 넘어서 넓은 범위의 재시도 상황을 만드는 데 적합합니다. 데이터베이스 작업, 파일 시스템 접근 또는 실패 시 재시도가 필요한 다른 작업과 같은 다양한 비동기 작업에 알맞은 재시도 로직을 적용할 수 있습니다.

npm - retry

재시도 로직 구현을 위한 모범 사례

  • 재시도 시기 결정: 모든 오류에 대해 재시도가 발생해선 안 됩니다. 오류가 일시적인 것인지, 후속 시도를 통해 해결될 가능성이 있는지 고려해야 합니다.
  • 재시도 제한: 무한 루프를 방지하기 위해 최대 재시도 횟수를 설정합니다.
  • 사용자 경험 주시: 특히 클라이언트 애플리케이션에서 재시도 로직이 사용자 환경을 저하하지 않도록 해야 합니다.

결론 🚀

재시도 로직을 자바스크립트 애플리케이션에 결합하는 것은 안정성과 복원력을 보장하기 위한 중요한 단계입니다. 이런 전략을 직접 구현할 것인지 아니면 기존 npm 패키지를 활용할 것인지에 상관없이 일시적인 오류를 우아하게 처리할 수 있는 기능은 애플리케이션의 견고함과 사용자 만족도를 크게 향상할 것입니다.

행복한 코딩하세요! 💻 ❤️

Instagram: @code_with_anu

LinkedIn: https://www.linkedin.com/in/anu95/

profile
누구나 읽기 편한 글을 위해

0개의 댓글