클라이언트에 갇혀있던 실패: 토큰 갱신 로직을 어디에 둘 것인가

아동의 스마트폰 사용 패턴을 연구하는 앱이었다. 실험군은 웨어러블 걸음 수에 비례해 앱 사용 시간이 적립됐고, 대조군은 잔여 시간을 Progressbar로 바로 확인할 수 있었다. 설계는 단순했다. 문제는 실행이었다.

웨어러블 벤더 API를 호출하려면 access token을 주기적으로 갱신해야 했는데, 이 갱신 로직이 클라이언트(앱)에 있었다. 앱이 활성 상태일 때만 갱신이 돌았다는 뜻이다. 그런데 아동 단말은 저전력 정책 때문에 백그라운드 앱을 쉽게 종료시켰고, 실제 사용 패턴에서는 장시간 미사용이나 간헐적 네트워크 연결이 흔했다. 토큰 갱신은 조용히 누락됐고, 그 결과 건강 데이터 수집 성공률은 사실상 0%였다.

처음엔 클라이언트 코드를 고치면 될 줄 알았다. 하지만 진짜 문제는 “토큰 갱신이라는 기능을 어느 계층에 배치했는가”였다. 앱이 활성 상태여야만 동작하는 로직을, 단말 상태와 무관하게 항상 돌아야 하는 작업으로 옮겨야 했다. On-premise 백엔드 API 서버에서 Crontab으로 DB에 저장된 사용자별 토큰을 정기적으로 유효성 검사·갱신하도록 재배치했다.

토큰 저장소 자체를 별도 캐시로 재설계하거나 사용자 단위로 분산 저장하는 방법도 고려했지만, 기존 RDB 구조를 유지하는 쪽을 택했다. 사용자별 토큰 상태의 정합성을 보장하고 서버 스케줄러가 단일 소스에서 조회·갱신하도록 하기 위해서였다. 갱신 “처리”만 서버로 옮기고 “저장소”는 그대로 둬서, 마이그레이션 비용 없이 이관할 수 있었다.

결과는 데이터 수집 성공률 0%→90%. 토큰 조회 실패 이슈는 최대 30회에서 3회 미만으로 줄었다. 그런데 이 프로젝트에서 정말 남은 건 숫자가 아니라 하나의 질문이었다. 레거시를 마주쳤을 때 “왜 안 되지”가 아니라 “왜 여기 있지”를 먼저 물어야 한다는 것.