[트러블슈팅 기록] API 401 에러로 앱 비정상 종료 이슈

김하연·2023년 12월 27일
1

우당탕탕

목록 보기
6/11

이슈

  1. 유저가 페이스 인증 요청 받은 상태에서 접속 시 튕김 현상
  2. 유저가 계정 삭제 후 재로그인을 위해 인증번호 수신 후 입력 시 튕김 현상

에러 추적

  1. ios 시뮬레이터로 돌려봤을 때, xcode에 에러 로그가 찍혔고 최종 위치가 badgeProvider인 것으로 나타남.

    2023-12-18 19:59:40.868559+0900 [2059:272957] [javascript] ApiUnauthorizedError: [401] unauthorized_api_error
    
    This error is located at:
        in BadgeProvider (created by App)
    	... 
        ...
        ...
  1. Charles 상에서 찍히는 API를 확인해보니 401에러가 다수 발생하고 있었음

  1. bugsnag상에서도 해당 에러가 찍히고 있었고, 14일 이후 급증한것으로 확인

  1. 이 중, BadgeProvider와 연관되어있는 API 중에 14일 이후 변경점이 생긴 관련 코드들을 파악
    • BadgeProvider 에서 임포트하고 있는 API관련 훅은 아래와 같았다.
      1. GET 유저정보 조회

      2. GET 성향테스트 결과 조회

      3. GET 평가카드 조회

      4. GET AB테스트 조회

      5. GET feature flag 조회

        이 중, 3번의 경우 유저의 state 상태가 active / review / dormant 일 경우에만 패칭하도록 되어있어 Charles에도 찍히지 않고 있었고 가장 최근에 관련된 변경사항이 많은 API가 4, 5번과 관련된 hook이어서 해당 훅의 변경사항을 확인해보았다.

확인된 코드 변경사항

BEFORE

const query = useQuery(
  QUERY_KEY,
  async () => {
    try {
      const response = await request();

      return response;
    } catch {
      return null;
    }
  },
  {
    suspense: true,
  },
);

AFTER

const useConfigurationBaseQuery = ( _options ) => {
  return useQuery({
    queryKey: QUERY_KEY,
    queryFn: request,
    enabled: Boolean(hasToken),
    ..._options,
  });
};

const query = useConfigurationBaseQuery({
	suspense: true,
  	select: () => { ... }
});

  ...
  • 기존 코드의 경우 쿼리 function 내부에서 try catch를 실행하고 있고, catch절에서 에러를 throw하지 않음
  • 변경된 코드에서는 발생된 에러를 따로 처리하지 않고 있음

이슈 원인이 된 부분

변경 전, 후 모두 쿼리의 suspense가 true 인데, suspense가 true 일 경우 useErrorBoundary 옵션이 자동으로 true 로 세팅된다.

💡 When using suspense mode, status states and error objects are not needed and are then replaced by usage of the React.Suspense component (including the use of the fallback prop and React error boundaries for catching errors). Please read the Resetting Error Boundaries and look at the Suspense Example for more information on how to set up suspense mode.
In addition to queries behaving differently in suspense mode, mutations also behave a bit differently. By default, instead of supplying the error variable when a mutation fails, it will be thrown during the next render of the component it's used in and propagate to the nearest error boundary, similar to query errors. If you wish to disable this, you can set the useErrorBoundary option to false. If you wish that errors are not thrown at all, you can set the throwOnError option to false as well!

→ suspense 기능이 활성화 되어 있었고 이로 인해 useErrorBoundary 또한 활성화 된 상태였으나, errorBoundary를 설정하지 않아 상위 컴포넌트로 핸들링되지 않은 에러가 전파되어 운영 환경에서 앱이 종료되었음.


이전 코드에서는 문제되지 않았던 이유

  • suspense가 ture 였으나 error를 throw하지 않고 있었다.

Frontend Class에서 개선&논의할 내용

  • suspense가 왜 설정되었는지에 대한 히스토리를 알 수 없었다. 상위에 suspense 컴포넌트도, errorBoundary 컴포넌트도 찾을 수 없었다. → 당연히 코드 히스토리만으로는 모든 히스토리를 추적할 수 없기에 suspense가 추가된 이유가 있었을 것이다. 하지만 여기서 얻은 깨달음은 단 한 줄도 이해하지 못한 코드는 커밋해선 안된다는 말을 명심해야할 것을 다시 한 번 생각하게 되었다.
  • Bugsgag에 찍히고는 있었다. 해당 에러가 급증하고 있었는데 아무도 알지 못했다. 14일 이후로 연달아 배포가 많이 나갔는데, 배포 후 모니터링을 조금 더 철저히 해야할 필요가 있을 것 같다.
  • 사실 해당 단계에서 불리면 안되는 API들이 불리고 있었고 이로 인해 401 에러가 발생했다. 구조적인 문제도 있지만 앱 전체를 관통하는 컨텍스트 단계에서 작업을 할 때에는 테스트 단계에서 가능한 모든 유저 상태의 시나리오를 고려하면 좋을 것 같다.
  • 하지만 매번 에픽을 진행하면서 모든 시나리오를 놓치지 않고 고려한다는 것이 가능하면 좋겠지만 사실상 어디에나 블랙박스는 존재하기 때문에 최소한의 안전장치는 만들어야 할 것 같다. 그리고 suspense 사용이나 에러 throw 방식에 대해서도 논의해보면 좋을 것 같다.
    • 배포 후 모니터링 어떻게 진행되고 있고, 어떻게 개선하는 것이 좋을지?
    • suspense 기능 사용에 대해 → suspense, errorBoundary 설정해야 한다.
    • 에러 throw 하는 방식 → 일단 throw ? → 이것이 또 노이즈가 되진 않을지에 대한 걱정
    • bugsnag / sentry 노이즈 제거

0개의 댓글