Tanstack-query Retry 커스텀 로직 도입기

zena·2024년 11월 6일
0

Front-end

목록 보기
11/12

서론

바로 이전 글인 dynamic import module 이슈와는 또 다른 api 응답 에러 상황을 어떻게 처리할 것인가에 대해서도 고민해보았습니다

네트워크 등 다양한 환경에 따른 모듈 로드 실패에 적정한 retry 기회를 제공해 사용자 경험을 개선할 수 있는 방안과 함께, 서버와의 api 통신에서도 조건에 따른 (에러 코드 등) retry를 고려해야 한다고 생각했습니다

Global QueryClient를 생성할 때, 개발자의 의도에 따라 retry를 단순 횟수로도 지정할 수 있고, 또 다음 코드처럼 failureCount (실패 횟수), error(발생한 에러 객체)를 활용한 커스텀 로직을 지정할 수 있습니다

첫 번째 시도

처음 생각했던 방안은 다음과 같습니다

1. 현재 네트워크 상태(온/오프라인)을 파악할 수 있는 onlineManager를 활용해 retry 기회를 부여한다
2. axios error type인 경우의 응답 코드를 활용한다
3. 400대의 클라이언트 에러는 api 요청 시도부터 문제가 있기에 더 이상의 retry를 제공하지 않는다
→ 온라인 상태인 경우에는 재시도 시 성공 가능성이 높기에 보다 많은 기회를 제공, 그렇지 않은 경우 재연결에 가능성을 염두하지만 적은 기회를 제공

const ERROR_CODES = [400, 401, 403, 404];

const isOnline = onlineManager.isOnline();
const limitCountOfRetry = isOnline ? 6 : 3;

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
      useErrorBoundary: true,
      retry(failureCount, error) {

        // 클라이언트 요청 상 오류 시 retry 해제
        const { response } = error as AxiosError;

        if (response && ERROR_CODES.includes(response.status ?? 0)) {
          return false;
        }
        // 재시도 기회 소진 후 retry 해제
        if (failureCount >= limitCountOfRetry) {
          return false;
        }

        return true;
      },
      networkMode: 'offlineFirst'
    },
    mutations: {
      networkMode: 'offlineFirst',
    }
  }
})

하지만…

이는 초기 온라인 상태에 따라 retry 값을 고정해 재시도 사이에 상태가 변경될 수 있음을 고려하지 않은 방식이었으며, 또한!!! 클라이언트 에러 상황 뿐만 아니라 서버에서의 응답 에러(저희 회사는 일반적인 서버 에러의 경우 500 코드를 던지는 상황)에도 retry를 제한하는 것이 좋을 것 같다는 선임분의 말씀을 추가한 로직을 작성하기로 하였습니다

500대 에러 코드를 모두 고려하지 않고 500만을 예외로 처리한 이유는 바로 회사의 에러 코드 처리 방식이 반영되었기 때문입니다 일반적인 서버 응답 에러에는 500 코드를 응답하고 있으며, 애초에 응답에 명백히 실패했다는 말이기 때문에 retry의 필요성을 느끼지 못한다는 사실과 별개로, 다음 예외 상황이 존재하였습니다

서버 2대를 돌리는 상황에서 하나가 일시적으로 죽은 경우(타겟 포트에 실제로 서버가 돌지 않는 경우) 인프라쪽에서 502 코드를 던지는데, 새로고침을 시도했을 때 성공하는 경우가 발생한 경험에 대해 선임분께서 말씀해주셨고, 이 때 재연결 시도가 없이 곧바로 에러를 발생시켰던 케이스를 고려하는 방향을 제안해주셨습니다

두 번째 시도

개선된 방안은 다음과 같습니다

1. 현재 네트워크 상태(온/오프라인)을 파악할 수 있는 onlineManager를 활용해 retry 기회마다 동적으로 retry 기회를 부여한다
→ 연결 상태가 계속 변경되면 limitCountOfRetry 또한 계속 변경되지 않을까? 라는 생각도 했지만, retry 함수 자체적으로 실패 횟수를 카운팅하기 때문에 최대 시도 횟수가 6으로 고정되기에 무한 재시도는 발생하지 않음

2. axios error type이 아닌 경우 재시도를 제한한다
→ 보다 엄격하게 에러 타입을 제한

3. 500 이하의 응답 코드는 retry를 제공하지 않는다

api 요청 응답 코드에 따라 retry 횟수를 동적으로 변경하며 함수를 실행시키는 로직은 다음과 같이 완성되었습니다!

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
      useErrorBoundary: true,
      retry(failureCount, error) {
        if (!(error instanceof AxiosError)) return false;

        const isOnline = onlineManager.isOnline();
        const limitCountOfRetry = isOnline ? 6 : 3;

        // 클라이언트 요청 상 오류 시 retry 해제
        const { response } = error;

        if (response && response.status <= 500) {
          return false;
        }
        // 재시도 기회 소진 후 retry 해제
        if (failureCount >= limitCountOfRetry) {
          return false;
        }

        return true;
      },
      networkMode: 'offlineFirst'
    },
    mutations: {
      networkMode: 'offlineFirst',
      retry(failureCount, error) {
        if (!(error instanceof AxiosError)) return false;

        const isOnline = onlineManager.isOnline();
        const limitCountOfRetry = isOnline ? 6 : 3;

        // 클라이언트 요청 상 오류 시 retry 해제
        const { response } = error;

        if (response && response.status <= 500) {
          return false;
        }
        // 재시도 기회 소진 후 retry 해제
        if (failureCount >= limitCountOfRetry) {
          return false;
        }

        return true;
      }
    }
  }
});

(mutation의 경우에도 default retry는 false 이기에 동일한 로직을 태울 수 있도록 작성해주었습니다)

여기서 또…

dynamic import module issue에서 처리했던 retry로직과 tanstack-query retry 로직을 모두 타는 상황에서 prefetch 화면(learning 페이지의 문항 데이터를 prefetch 하는 learning-status 페이지)에 두 retry 로직이 무작위로 중복 실행되는 문제가 발생합니다 ㅎ


흠.. 이 상황에 대해서는 어쩔 수 없다고 결론을 내리게 되었는데, 하필 화면을 렌더링함과 동시에 데이터 패칭도 이루어지기 때문에 두 retry 로직을 동시에 타는 것은 어찌보면 당연한 것이라는 판단을 하였습니다

일반적으로는 js 파일 다운 후 api 요청을 하기에 prefetch와 같은 예외 상황이 아닌 경우에는 순차적으로 실행될 수 있습니다 따라서, dynamic import module refetchqueryClient refetch 각각의 로직을 대응할 수 있도록 짜는 것이 사용자 경험을 개선 시키는 데 도움이 될 것 같다는 결론을 내리게 되었습니다

사실 500 대의 서버 에러 관련해서는 서버 개발자 분들과의 더 심도있는 대화가 필요해보이지만, 각 상황 별 에러 케이스에 따라 retry를 동적으로 실행시키면서 사용자 경험을 개선시킬 수 있는 코드를 고민해볼 수 있는 좋은 시간이었던 것 같습니다 :)

profile
🐤 FE developer 🎧

0개의 댓글