EAT-SSU 앱을 운영하면서 이상한 버그를 자주 만났다. 로컬에서는 아무 문제 없이 잘 돌아가는데, 배포하고 나면 간헐적으로 이상한 현상이 발생했다.
더 이상한 건, 에뮬레이터에서 기능 자체는 정상적으로 작동한다는 점이었다. 서버에는 데이터가 잘 저장되는데 사용자 화면에는 에러가 뜨거나, 갑자기 로그아웃되는 식이었다.
원인을 찾아보니 전부 Race Condition 때문이었다.
이 글에서는 실제로 겪었던 두 가지 사례와 해결 과정을 공유한다.
두 문제 모두 비슷한 특징이 있었다.
결국 타이밍이 문제였고, 이건 동시성 문제라는 신호였다.
여러 작업이 동시에 실행될 때, 실행 순서에 따라 결과가 달라지는 현상을 말한다.
쉽게 비유하자면:
두 명이 동시에 같은 은행 계좌에서 돈을 인출하려고 할 때,
누가 먼저 처리되느냐에 따라 잔액이 달라지는 상황
각 코드는 문제없는데, 동시에 실행되면 사고가 나는 것이다.
모바일 앱은 기본적으로 비동기 환경이다.
이 과정에서 공유 자원이 생긴다:
UiState)이 자원들에 동시 접근 + 순서 보장 없음이 겹치면 Race Condition이 발생한다.
내가 겪은 두 문제도 정확히 이랬다.
EAT-SSU의 토큰 정책은 이렇다:
배포 후, 하루에 한 번씩 갑자기 로그아웃되는 현상이 간헐적으로 발생했다.
서버 문제도 아니고, 토큰 정책 오류도 아니었다. 추적해보니 토큰 재발급 로직의 동시성 문제였다.
Access Token이 만료된 시점에 여러 API 요청이 동시에 날아가면:
1️⃣ 여러 요청이 동시에 401 응답을 받음
2️⃣ 각 요청이 같은 Refresh Token으로 재발급 요청
3️⃣ 첫 번째 요청이 Refresh Token을 사용해서 성공
4️⃣ 두 번째 요청은 이미 사용된 Refresh Token으로 시도
5️⃣ 서버에서 401 반환 → 강제 로그아웃
핵심은 간단했다.
Refresh Token 재발급은 딱 한 번만 일어나야 한다
Mutex를 사용해서 해결했다:
동시성 해결과 함께 전체 인증 구조도 정리했다:
로그아웃 시 원인도 명확히 알 수 있게 MISSING_REFRESH_TOKEN, REFRESH_TOKEN_EXPIRED 등을 로깅하도록 개선했다.
| 항목 | As-Is | To-Be |
|---|---|---|
| 동시성 제어 | ❌ 없음 | ✅ Mutex 적용 |
| 재발급 요청 | 여러 요청이 동시에 시도 | 1회만 |
| 결과 | 일부 실패 → 로그아웃 | 모든 요청 성공 |
QA에서 이런 제보가 들어왔다:
"리뷰 작성이 성공했는데 네트워크 오류 다이얼로그가 떠요"
실제로 서버에는 리뷰가 잘 저장되어 있었다. 하지만 사용자는 에러를 보게 되니 서비스 신뢰도가 떨어지는 상황이었다.
리뷰 작성 플로우는 이렇게 작동했다:
postReview API 호출saveS3 이미지 업로드문제는 두 작업 모두에서 UiState.Success를 설정하고 있었다는 점이었다.
문제 발생 시나리오:
1️⃣ postReview 성공 → UiState.Success 설정
2️⃣ Activity가 성공으로 인식 → finish() 호출
3️⃣ ViewModelScope 종료
4️⃣ 아직 끝나지 않은 saveS3가 IOException 발생
5️⃣ 네트워크 오류로 오인 → 에러 다이얼로그 표시
기능은 성공했지만, 상태 관리 경쟁 때문에 사용자 경험이 망가진 것이다.
해결 전략은 명확했다:
UiState.Success를 단 한 곳에서만 관리finish() 호출 시점 보장즉, UI 상태는 전체 작업 흐름을 대표해야 한다는 원칙을 적용했다.
두 문제 모두 이런 공통점이 있었다:
Race Condition은 특정 기술의 문제가 아니라 설계의 문제였다.
이번 경험을 통해 확실히 배운 게 있다:
1. 실제 서비스에서는 동시성 문제가 반드시 발생한다
2. 해결의 핵심은 구조
3. "운 좋게 잘 되는 코드"는 언젠가 문제를 만든다
이번 리팩토링은 단순한 버그 수정이 아니라, EAT-SSU 전체를 더 견고하게 만드는 계기가 되었다.
Race Condition은 찾기도 어렵고 재현하기도 어려운 문제다. 하지만 한 번 겪고 나면 패턴이 보이고, 예방할 수 있게 된다.
"로컬에선 되는데..."라는 말을 하게 된다면, 한 번쯤 동시성 문제를 의심해보자.