홈으로 돌아가기

2025-05-16 안드로이드 장애 회고

박종하
2025-05-18
2025-05-16 안드로이드 장애 회고

안녕하세요, 커피팅의 Lead Developer이자 백엔드/인프라 엔지니어를 담당하고 있는 박종하입니다.

2025년 5월 16일 커피팅 서비스에는 모든 안드로이드 사용자를 대상으로 앱을 이용할 수 없는 큰 장애가 발생했습니다.
수 많은 고객분들이 불편을 겪었고 서비스 장애는 다행이 구글의 빠른 심사로 약 4시간만에 종료될 수 있었습니다.

이번 글에 왜 이런 장애가 발생했고 어떻게 파악을 하고 어떻게 해결을 했는지 정리해보도록 하겠습니다.

앞으로 이런 장애나 오류, 실수에 관련된 글들을 투명하게 공유하고 지속적으로 개선해나가는 서비스가 되도록 하겠습니다.

1. 장애 인식

매우 평화로운 금요일 오후였습니다, 흥미로운 프론트 개발자와 면접을 앞두고 있어 기쁜 마음에 개발을 하고 있었습니다.

그러다 갑자기 고객센터로 문의가 오기 시작합니다, '로그인이 안됩니다'

종종 로그인이 안되는 분들이 계셔서 카톡에 있는 템플릿으로 답장하면 되겠지 생각하는 순간 또 하나의 문의가 도착합니다,
'홈 화면에서 스피너가 무한으로 돌아가요'

상황이 심상치 않음을 느끼고 다급하게 테스트폰으로 프로덕션 앱에 접속해봅니다, 이런 접속이 안되네요?

그동안 수많은 디버깅 케이스로 생긴 두뇌속의 인덱스를 바탕으로 체크해봐야할 지점을 하나씩 들여다보기 시작합니다.

2. 디버깅 과정

상황의 판단

저는 디버깅을 할 때 최대한 상황을 좁혀놓은 다음 하나씩 스코프를 추가하며 디버깅하는 방식을 좋아합니다.

첫번째로 확인한 부분은 iOS는 정상적으로 작동되는가? 였습니다.
이것으로 어떤 단서를 얻을 수 있냐면 서버의 문제일 가능성이 많이 배제됩니다. (물론 완벽하게 배제하면 안됩니다)

위 단서로 안드로이드만 bug fix로 오늘 오전에 배포되었던 버전이 있다는점이 생각이 났습니다.
이렇게 되면 서버의 문제일 가능성은 더더욱 줄어듭니다, 그렇다면 범위가 안드로이드 클라이언트로 좁혀지게 됩니다.

안드로이드 클라이언트에서 어떤 버그가 일어나고 있는지 파악해야 했습니다, 아래와 같은 지점들을 생각했습니다.

  1. Server Sentry에는 어떤 오류도 보이지 않음
  2. 실제로 로그인 요청을 해도 Datadog애는 로그인 요청 로그가 보이지 않음
  3. Production 앱에선 디버깅 메세지를 볼 수 없지만 마치 동작이 network connection timeout 같았음

위 3가지 단서들을 조합했을때 API Endpoint 설정이 잘못되어 배포되었나? 라는 생각이 들었습니다.
하지만 이전에도 PG키를 수동으로 잘못 넣어 배포한 사례가 있었기에 자동화된 CI/CD 파이프라인을 구축했었습니다.

그래도 저의 직관과 위의 단서들이 한번쯤은 살펴봐도 좋을 지점이라고 말해주고 있었습니다, 그래서 살펴보기로 판단했습니다.

우연

이 상황에 첫번째 우연 이라는 악마가 찾아옵니다.
고객센터에 똑같이 '로그인이 안됩니다' 라는 제보가 들어왔습니다, 저는 물어봤습니다 '어떤 기종 사용하고 계시나요?'

'아이폰 16이요'

이럴수가, 저의 좁혀놨던 법위와 판단이 흔들리기 시작합니다. 전체 클라이언트 문제인가? 왜 내 핸드폰에서는 잘 작동하지?
안드로이드 사용자 전체는 장애이고 iOS는 일부만 장애인데 서버 문제도 아니다?
와중에 팀원도 '이거 같은 오류로 봐야하는거 아니에요?' 라고 한다.

하지만 다행이 나는 이 우연이라는 악마와 자주 마주한적이 있었다.
사람들이 자주 겪어보지 않은 상황에만 특별이 출연해서 디버깅 범위를 어지럽히고 개발자의 판단을 흐리게 한다.

다시 판단을 하기 시작했다, 1명을 제외한 모든 고객분들이 안드로이드에서 장애를 겪고 있기 때문에, 과감하게 내 판단을 유지했다.
이전의 경험으로 우연이라는 악마를 퇴마하고 위의 디버깅 맥락을 묵묵하게 이어나가기 시작했다.

apk 리버싱으로 가설 검증하기

이미 프로덕션에 나간 앱을 디버깅하기란 참 까다롭습니다,
다양한 방법들을 찾던 중 play console에서 업로드한 앱을 다운받을 수 있다는 사실을 알게 되었습니다.

apk 파일을 다운받고 이제 이걸 어떻게 요리해볼까? 생각을 하던 도중 해킹을 한참 좋아하던 시절에 즐겨하던 리버싱이 떠올랐습니다.

고등부 대회에 나갔을때 안드로이드 apk를 해킹하는 문제를 푼적이 있었고 그때 사용했던 jadx라는 디컴파일러가 생각났습니다.

이 디컴파일러를 이용하면 충분히 제 가설을 검증할 정도의 정보들을 얻을 수 있을것이라는 판단이 들었습니다.

jadx를 이용하여 apk를 디컴파일하고 grep으로 엔드포인트 변수를 검색하여 BuildConfig.java 파일에 있는것을 발견할 수 있었습니다.
이런데 이런, 엔드포인트 변수에 회사 내부망 ip가 할당되어 있었습니다!

한편으로는 빠르게 지점을 찾아서 다행이라는 생각을 했지만 왜 이런일이 발생했는지 도저히 맥락이 잡히지 않았습니다.

그래서 저희는 gpt의 도움을 받기로 했습니다.

React Native의 컴파일 과정에 대해 이해하기

GPT에게 이 상황을 설명했습니다, 역시 gpt는 이 상황에 대한 답을 알고 있었고 gpt의 힌트를 토대로 원인을 찾아나가기 시작했습니다.

React Native에서는 metro라는 번들러는 사용하는데 JS 코드가 크게 바뀌지 않으면 캐시된 번들을 그대로 재사용합니다.

그런데 저희의 CI/CD 파이프라인에는 gradlew clean과 빌드 명령어밖에 없었기 때문에 dev env로 캐시된 JS번들이 그래도 적용되어 버린겁니다.

하지만 또 의문점이 들었습니다 지금까지 이런 방식으로 문제 없이 3달 넘게 배포하고 있었는데 왜 이제서야 이런 문제가 발생했는지 이해가 되지 않았습니다.

원인은 위에 말씀드렸던것처럼 JS코드가 크게 바뀌지 않으면 캐시된 번들을 그대로 재사용한다는것이였습니다.
반대로 말하면 크게 변경사항이 있는 경우에는 알아서 번들 캐시를 초기화하고 빌드한다는거죠.

평소엔 새로운 기능이 포함된 배포가 많았기 때문에,
JS 코드 전체에 걸쳐 여러 파일이 변경됐고 Metro는 자동으로 대부분의 캐시를 무효화했습니다.
덕분에 매번 새롭게 .env.prod가 반영된 JS 번들이 생성되어 문제 없이 배포되었죠.

하지만 이번엔 단 두 줄만 바뀐 버그 패치였고,
Metro는 변경된 파일만 새로 번들링하고 나머지는 dev 환경이 반영된 기존 캐시를 그대로 재사용해버렸습니다.

결국, 캐시 시스템이 동작은 제대로 했지만, 우리의 의도와는 다른 결과를 낳은 거죠.

마무리

이번 장애는 기술적으로는 아주 작고 단순한 실수에서 시작되었지만,
우연과 캐시, 설정의 허점이 겹치며 대규모 장애로 확산된 전형적인 사례였습니다.

우리는 이 사건을 통해 “지금까지 잘 되던 건 운일 수 있다” 는 걸 배웠습니다.
운에 의존하지 않고, 항상 동일한 결과를 보장하는 시스템을 만드는 것,
그것이 진짜 개발자의 일이자 우리가 추구하는 서비스의 방향입니다.

장애는 누구에게나 발생할 수 있지만, 똑같은 실수를 반복하지 않는 건 우리의 선택입니다.
커피팅은 이번 장애를 단순히 “운이 나빴던 일”로 넘기지 않고,
시스템, 프로세스, 팀 문화 전반을 돌아보는 계기로 삼았습니다.

앞으로도 문제가 생기면 숨기지 않고 공유하며,
개선의 기회로 삼는 문화, 그게 우리가 지향하는 개발팀의 모습입니다.


읽어주셔서 감사합니다.
혹시 글에 대한 피드백이나 궁금한 점이 있다면 언제든지 댓글이나 연락 주세요!

mail : archan@coffeeting.kr