ERROR!!!!!!!

외부 시스템에 덜 의존하게 만들기

voider 2022. 7. 13. 22:45

이전 글 예외처리에 대한 잡설에 이어서

지난 몇 달 간 아파트 입주민이 사용하는 방문예약 시스템을 개발했다. 
입주민이 방문 차량을 예약하면 우리 시스템은 아파트 차단기를 관리하는 외부 시스템에 요청을 보내서, 예약된 차량이 간편하게 입차할 수 있게 하는 서비스다. 이 시스템의 아이덴티티는 예약하면 차단기가 열린다는 것이다. 그런데 우리는 차단기 회사가 아니고 차단기로 예약을 요청을 대신 보내주는 대리자 역할이다. 따라서 외부 시스템에 크게 의존적일 수밖에 없는 서비스다. 플로우는 복잡하지 않는데 간략하게 설명하면 세 단계로 볼 수 있다.

  1. 사용자가 앱에서 방문 차량을 예약한다.
  2. 메인 서버에서 방문 예약 서버로 요청을 위임한다.
  3. 방문 예약 서버는 외부 시스템인 차단기 관제 서버에 예약 내역을 전송한다.
  4. 사용자에게 예약이 완료되었다고 알림을 띄운다.

이 세 가지 동작이 모두 동기로 이루어지는데, 내부 시스템에서 외부 시스템으로 요청을 보내는 과정에서 문제가 자주 발생했다. 아파트 단지에 설치된 차단기 시스템은 네트워크가 불안정해서 서버가 자주 죽는다. 문제는 이 모든 동작이 ‘동기’로 동작한다는 사실이다. 비동기로 동작하지 않는 데에는 사용자에게 즉각적인 피드백을 주기 위해서다.

비동기로 동작할 경우, 서버가 죽거나 그외 잘못된 데이터가 들어오거나, 아무튼 수많은 이유로 실제 외부 시스템에 보낸 예약 요청이 실패할 수도 있다. 그럼 사용자는 앱에서는 분명히 예약 성공 알림을 받았는데, 실제로 예약 차량이 입차할 때 예약이 안 되어 있는 상황이 발생할 수도 있다. 물론 일단 비동기로 전송한 다음, 실패하면 다시 푸시 메시지를 보낼 수도 있지만 당장 사용자가 엄청나게 많은 서비스가 아니었기 때문에 동기로 만들고, 즉각적인 피드백을 주는 것으로 결정했다.

동기로 시스템을 구성한 뒤 배포하니 주말마다 문제가 터졌다. 외부 시스템이 자꾸 주기적으로 죽는 것이다. 외부 시스템만 죽으면 그나마 낫다. 동기로 동작하니 외부 시스템이 죽으면 계속해서 알 수 없는 이유로 예약에 실패했다는 알림을 사용자가 받게 됐다. 우리 시스템 문제도 아닌데 이걸로 몇 번이나 운영팀에게 문의가 왔다. 나도 실제로 원인을 파악하기 위해 직접 로그를 보고, POSTMAN을 열어서 요청해보고 SocketTimeOut이 발생하는 것을 눈으로 확인해야 했다. 이런 상황이 3~4번 이어지면서 자동화의 필요성을 느꼈다.

  1. 내부 시스템에서 외부 시스템으로 요청을 보낼 때 SocketTimeOutException 이 발생하면 한 번 retry하고, 다시 발생하면 외부 시스템이 죽었다고 판단한다.
    1. 이때 예약 내역에 외부 시스템 문제로 요청에 실패했다는 플래그를 단다.
    2. 방문 예약 메인 화면에 외부 시스템이 점검 중이어서 예약이 제대로 처리되지 않을 수도 있다는 문구를 띄운다.
    3. Slack으로 외부 시스템이 죽었다는 경보 메시지 전송
  2. 메인 서버는 방문 예약 서버에서 5초 안에 response가 돌아오지 않으면 사용자에게 일단 200 OK를 내보낸다.
  3. 방문 예약 시스템은 Scheduler가 요청에 실패한 예약을 10초마다 retry한다.
    1. 여전히 SocketTimeOut 이 발생하면 Pass.
    2. 외부 시스템과 통신에 성공하면 방문 예약 메인 페이지에 경고 문구 지운다.
    3. Slack으로 외부 시스템이 복구되었다는 메시지 전송

간단하게 이런 느낌..?

나름 머리를 쥐어짜내서 생각한 예외처리 플로우다. 이것보다 훨씬 더 좋은 방법이 당연히 있을 거고 내가 한 방법이 완전 멍청한 방법일 수도 있다. 혼자서 이 프로젝트를 맡고 있었기 때문에 의논할 사람도 많이 없었고 그냥 이렇게 처리하려고 한다고 상의만 했다. 어쨌든 이 정도 스펙이면 당장 내 눈 앞에 있는 웅덩이를 메꿀 수는 있었다.

실제로 이렇게 했더니 운영팀으로 들어오는 방문 예약 CS가 확연하게 줄었다. 사용자도 알 수 없는 이유로 예약에 실패하는 일이 없으니 (몰랐겠지만) 이전보다 훨씬 더 나은 사용자 경험을 만들 수 있었다고 생각한다.

이 문제를 해결하면서 예외처리가 그다지 간단한 문제가 아니라는 것을 깨달았다. 보통 요구사항을 개발하다보면 어쩔 수 없는 상황에 thorw 를 날리는 것이 전부였는데, 외부 시스템과 연동하면서 발생하는 예외 상황들을 고려하다보니 굉장히 복잡한 세계라는 것을 깨달았다. 골치아프지만 또 한편으로 재밌는 세상이다. 이 이야기를 처음 고민하면서 예외처리에 대한 잡설 이 글을 쓰기도 했다. 거의 비슷한 내용이다. 이유는 모르겠지만 직관적으로 중요한 경험인 것 같아서 좀더 자세히 남겨둘 필요성을 느꼈다.

실제 프로덕션 코드를 가져올 수는 없으니, 다음엔 간단한 예제 코드를 만들어서 재현해보겠다.