서비스를 운영하면서 예상치 못한 네트워크 에러로 인해 데이터가 업로드되지 않는 상황이 발생하였다. 사용자가 서비스를 이용하는 도중에 발생하는 에러는 사용자 경험에 직접적인 영향을 미치며, 심지어 서비스의 신뢰성에도 영향을 미칠 수 있다. 그렇기 때문에 이번 사건을 발판으로 에러 처리에 중요성을 다시금 느끼며 공부한 내용을 기록하기로 마음먹었다.
잠깐! 그럼 재시도 요청은 왜 필요할까?
웹 서비스에서는 다양한 이유로 인해 HTTP 요청이 실패할 수 있다. 재시도를 통해 얻을 수 있는 몇 가지 장점은 다음과 같다.
- 향상된 안정성: 실패한 요청을 자동으로 재시도함으로써 성공적인 데이터 응답의 가능성이 높아지고 애플리케이션의 전반적인 안정성이 향상된다.
- 향상된 사용자 경험: 백그라운드에서 자동으로 요청을 재시도하면 오류 메시지나 중단 없이 원활한 사용자 경험을 보장할 수 있다.
- 수동 개입 감소: 자동화된 재시도 메커니즘이 없으면 요청을 수동으로 재시도를 해야한다. 이는 효율적이지도 않고 확장성도 없다.
방법 1. 무작정 코드 짜기
처음엔 무작정 재 요청을 하는 코드를 짰다. retries변수로 에러 시 retries를 증가시켰다. 그리고 1초 후 재 요청을 보냈다. 하지만 보기만 해도 코드는 복잡하고 지저분했다.
.catch((error) => {
if (
error.message === "Network Error" ||
error.code === "ECONNABORTED" ||
error.response === undefined
) {
let retries = 0;
const retryRequest = () => {
retries++;
if (retries <= 3) {
console.log(`Network error occurred. Retrying (${ 3 - retries + 1} attempts left)...`);
setTimeout(() => {
axios
.post(
"https://" + process.env.REACT_APP_SERVER + "/upload", data)
.then((response) => {...})
.catch(retryRequest);
}, 1000);
} else {
console.error(
"Maximum retries reached. Unable to complete the request."
);
}
};
retryRequest();
}
})
방법 2. axios-retry
다른 방법을 찾아보다가 axios-retry라는 라이브러리를 발견하였다.
axios-retry
는 Axios 라이브러리와 함께 사용되는 HTTP 요청 재시도를 구현하기 위한 라이브러리로, 네트워크 오류 또는 일시적인 서버 문제와 같은 상황에서 자동으로 HTTP 요청을 다시 시도할 수 있도록 도와준다.
설치하기
npm install axios-retry
일단 따라해보자
공식 홈페이지에 나와있는 방법을 일단 따라해봤다.
import axiosRetry from "axios-retry";
const client = axios.create({ baseURL: "http://example.com" });
axiosRetry(client, { retries: 3 });
client.get("/test").then((result) => {
result.data;
});
이슈발생 🚨 retry 되지 않음
내가 실수한게 있나 검색을 해보니 실패한 모든 요청에 대해서 재시도 조건을 다음과 같이 지정해야했다.
axiosRetry(axios, {
retries: 3,
retryCondition: () => true,
});
By default, it retries if it is a network error or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE).
axios-retry는 기본적으로 5xx 오류에 대해 재시도하고 다른 오류의 경우 'retryCondition' 옵션을 사용해야 한다.
axios-retry가 적용된 코드
코드는 훨씬 보기 좋고 깔끔해졌다.
import axiosRetry from 'axios-retry';
const retryInterval = 3000;
const accessToken = sessionStorage.getItem("token");
const client = axios.create({
baseURL: process.env.REACT_APP_API_SERVER,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
axiosRetry(client, {
retries: 3,
retryCondition: () => true,
retryDelay: () => retryInterval,
});
await client
.post("/upload", data)
.then((response) => {...})
.catch((error) => {...});
axios-retry
를 통해 최대 재시도 횟수, 재시도 간 딜레이, 재시도 조건 등을 쉽게 커스텀 할 수 있었다.
에러와 예외
- 에러 : 시스템 단계에서 발생. 시스템의 비정상적인 상황이므로 예외처리가 아닌 시스템 환경을 개선해야 한다.
- 예외 : 프로그램 로직에서 발생. 프로그래머가 작성한 로직에서 예외를 예상하여 구분하고 처리해야 한다.
예측가능한 예외 vs 예측 불가능한 예외
-
예측 가능한 예외
- 프로그램에서 당연히 발생 할 수 밖에 없는 상황. ex) 로그인 실패, 데이터 조회 실패 .. 등
-
예측 불가능한 예외
-
에러와 같은 수준의 레벨. ex) 버그, 시스템의 메모리 문제 .. 등
-
시스템 환경에서 개선해야 한다.
-
예외에 대한 생각 정리
프로그램 설계는 참 중요하다. 무작정 개발만 하는 것이 아닌 수많은 예외 상황을 고려하며 설계해야한다. 하지만 예외 상황을 다 예측하기에는 너무 힘든게 사실이다. 회사를 다니면서 기획의 중요성을 참 많이 느꼈다. 기획이 계속 틀어지면서 처음 설계한 프로그램이 꼬이기 시작하고, 그로 인해 없었던 사이드 이펙트 까지 생겨나게 되버린것이다. 하지만 그런 기획까지 예측하며, 잘게 쪼개고 나누는 것이 개발자의 몫인건가.🤔 참 어려운 것 같다. (이런 문제가 많아서 아토믹이 생겨난 것 같기도 하다)
예외 상황은 빈번하게 발생한다. 하지만 예외 상황이 발생한 것 보다 이를 보완하며 고쳐나가는 자세가 더 중요한 것 같다. 분명 작은 예외를 무시할 경우 더 큰 예외 상황이 될 것이다.
예외에 대해 생각하고 또 생각하자.