상용 서비스 script, API 오류 처리 개선 작업 회고. 이렇게나 오류가 많았다고?

작성 : 2023-08-24수정 : 2023-09-19

목차 펼치기

머리말

오류 모니터링을 위해

데이터독

을 도입하고,

RUM(Real User Monitoring)

을 살펴봤을 때 깜짝 놀랐다.

심지어 setInterval에서 동작하는 스크립트에 오류가 나서 clear가 제대로 되지 않아 오류가 interval 시간마다 찍히는 부분도 있었다. API 오류 처리는 컴포넌트에서 제각각 처리하고 있어 관리가 되지 않는 상태였는데, 400대의 오류를 500 status로 보내는 API도 있었다.

오류 모니터링 없이 1년을 넘게 상용 서비스 중인 프로젝트의 상황은 처참했다.

Script Error + API Fallback + Server Error 의 총체적 난국 상황에서 데이터독 모니터링을 통해 오류를 수정하고 API 오류 처리를 개선한 경험에 대한 이야기다.



Fix Script Error

source map 설정이 가능하다면 이 부분의 수정은 쉽다. 어떤 코드에서 무슨 오류가 발생했는지 금방 파악할 수 있기 때문이다. 대개 수정은 방어 코드를 추가하거나 예외 처리를 추가하는 등으로 실제 작성할 코드의 양은 적은 편이다. 해당 오류가 야기되는 일반적인 상황을 정리해보면 다음과 같다.


  • 높은 결합도

    • 결합도

      가 낮을 수록 좋은 코드다.

      • 내용 결합도 : 하나의 모듈이 다른 모듈의 내부 동작을 수정하거나 내부 동작에 의존하는 상태

      • 공통 결합도 : 두 개의 모듈이 같은 글로벌 데이터를 공유하는 상태

    • 결합도가 높을 경우 오류가 발생하기 쉬울 뿐만 아니라 유지보수도 어렵다.

    • 심지어 로직 상에서 DOM의 스타일을 직접 접근하는 경우도 있었다.

  • DOM 조작 시 방어코드 누락

    • DOM을 참조하지 못해 오류가 발생하는 경우가 생길 수 있다.

    • DOM Element를 찾지 못했을 경우도 같이 고려되어야 한다.

  • 브라우저 호환 문제

    • Array.at

      함수 같은 경우에는 크롬 92버전 부터 적용이 가능하다.

      • 나도 내가 이걸 외우게 될 지 몰랐다.

    • 폴리필을 잘 구축하여 해결할 수 있다. 아니면 사용하지 않거나.

  • 컴포넌트 해제 시 로직 처리

    • 모달이나 setInterval과 같이 컴포넌트에서 전역적으로 참조한 컴포넌트나 로직에 대해서 적절한 clear 처리를 해주어야 한다.

  • 로직 상의 오류

    • 엣지 케이스의 경우 QA 단계를 거쳐서도 발견되지 않을 수 있다.

    • 특히 빠른 배포 주기를 가지고 코드 리뷰와 테스트 환경을 제대로 갖추기 힘든 스타트업에서 자주 생길 수 있다.


try~catch는 기본이고, 특히

옵셔널 체이닝

문법만 잘 적용해주어도 오류 발생을 많이 낮출 수 있는 코드들이 꽤 많았다.



API Fallback

서버와 API 통신을 하는 과정에서도 충분히 오류가 발생할 수 있다. 사용자의 네트워크 환경 때문일 수도, 서버의 로직 때문일 수도, 클라우드 서비스의 장애일 수도 있다. 해당 오류에 맞는 적절한

HTTP Response Status Code

를 사용하여 오류를 구분하여 처리할 수 있다.

주로 사용자 단에서 잘못된 요청으로 발생한 오류, 즉 서버에서 예측이 가능한 오류의 경우에는 400번대 상태를 사용하며 예기치 못한 오류의 경우 500번대 상태를 사용한다.

자주 사용되는 상태값으로는 다음과 같다.


  • 200 : OK

  • 400 : Bad Request

  • 401 : Unauthorized

    • 사용자 인증 정보 없음

  • 403 : Forbidden

    • 사용자 인증 정보는 없으나, 사용자에게 권한이 없음

  • 404 : Not Found

  • 418 : I’m a teapot

  • 500 : Internal Server Error

  • 502 : Bad Gateway

  • 503 : Service Unavailable

  • 504 : Gateway Timeout


기존의 핸들링 방식은 API를 호출한 컴포넌트 단에서 포괄하여 처리하거나 필요 시 status를 확인한 후 처리를 하는데 모두 제각각이었다. 이를 개선하기 위한 구조를 고민해봤다. API 오류를 catch할 수 있는 곳은 크게 세 곳이었다.


  1. API를 호출한 Component

  2. API 호출 로직을 구현한 Service

  3. API를 호출하는 Fetch


이 세 곳에서 어떻게 오류 처리를 어떻게 분담할 지 고민하며 개발팀 내 의견도 요청한 결과 다음과 같이 정리되었다.


  1. Fetch : API를 요청한 후 status 200이 아닐 경우, status에 맞는 별도의 fallback 함수를 실행한 후 오류를 전파한다.

    1. 400 : 서버에서 전달해준 오류 메시지로 사용자 alert을 발생시킨다.

    2. 401 : 서비스를 로그아웃 처리한 후 로그인 페이지로 이동시킨다.

    3. 403 : 별도로 구현한 403 페이지로 이동시킨다.

    4. 404 : 잘못된 요청 경로임을 안내할 수 있는 문구로 사용자 alert을 발생시킨다.

    5. 500 : 서버 처리 중 오류가 발생했다는 문구로 사용자 alert을 발생시킨다.

    6. 502 : 서버 처리 중 오류가 발생했다는 문구로 사용자 confirm을 발생시키며, 사용자는 홈 화면으로 이동할 수도 있다.

    7. 503 : 서버 상태에 오류가 발생했다는 문구로 사용자 alert을 발생시킨다.

    8. 504 : 서버 상태에 오류가 발생했다는 문구로 사용자 alert을 발생시킨다.

  2. Service : API를 호출하며 응답받은 데이터 또는 오류를 Component로 전파한다.

  3. Component : 데이터독 로깅 라이브러리를 사용하여 발생한 오류에 따라

    warning

    ,

    error

    로 구분하여 로깅한다. 필요 시 status에 맞는 추가 로직을 구현한다.


데이터독 로깅 라이브러리를 사용할 경우 별도의 로그 서비스를 통해 보다 편하게 모니터링 할 수 있다. 400대와 같이 예상 가능하고, 대비가 되어 있는 오류들에 대해서는

warning

으로 처리했다.


status를 일반적으로 처리할 경우, API 기능에 따라 다른 메시지를 보여주는 등의 처리는 불가능하지만 개발팀과의 논의 결과 그렇게까지 처리하기보다는 각 모듈 별 기능을 온전히 위임하는 게 더 나은 방향인 것으로 이야기 되었다.



Server Error

기존 서버 로직에서 오류가 발생할 경우 status 처리가 명확하지 않고 작업자에 따라 다르게 처리되고 있었다. 이에 대해 프론트에서 오류를 핸들링하는 구조를 설명드리며 오류 처리에 대한 얼라인을 함께 맞췄다.

레거시로 기존 400대 오류를 오류 문구와 함께 500으로 반환하는 API가 몇 개 확인되는데, 우선 이에 대응하기 위해 500 fallback 함수에서 서버에서 전달해준 문구가 있을 경우 이를 최우선으로 노출하도록 처리하였으며 서버 개발자 분들에게 수정 요청을 드렸다.

프론트에서 오류 처리를 개선할수록 서버 오류도 발견하기가 쉬워지는 것이다.



꼬리말

이게 최선이고 좋은 방향인가? 에 대한 질문에 아직 확신을 담아 대답할 수는 없다. 다만 확실한 것은 이 과정을 통해 나는 오류 전파와 핸들링에 대한 고민을 많이 하게 됐으며, 기존의 오류들을 많이 해결했고, 오류 처리를 동일하게 관리하는 걸 적용하고, 백엔드 개발자 분들과 오류 처리에 대한 논의를 나누었다는 점이다. VoC로 서비스 중 발생하는 오류로 인해 서비스의 신뢰가 떨어진다는 내용이 발생하고 있는데 계속 더 나아져서 예기치 못한 오류에도 사용자 경험을 크게 해치지 않는 서비스가 되어가면 좋겠다.


Wanna get in touch?

All Icons byiconiFy