배경
필자가 활동하고 있는 교내 동아리 BCSD에서는 코인이라는 교내 커뮤니티 서비스를 개발하고 있다. 최근 재학생들을 대상으로 만족도 및 의견에 대한 설문을 진행했고, 저번주 쯤 결과를 받아보게 되었다.
PM을 통해 5개 정도의 핵심 인사이트를 도출했는데, 그 중 우리가 해결해야 할 문제가 있었다.
앱 성능·접속 문제
발생 빈도는 낮지만, 겪는 순간 다른 개선이 무의미해지는 유형의 문제
기획 / 디자인 등 다른 직군에 관련 없이, 전적으로 우리 안드로이드 개발자들의 책임이었다.
무엇이 문제인가
실제로 어떤 문제가 발생하는지, 어떤 상황에서 발생하는지를 먼저 파악해야 했다.
다행이도, 우리 팀에서는 Firebase Crashlytics로 크래시 로그를 수집하고 있었다.

최근 1주일 사이의 크래시 로그를 보면, 200건이 넘는 네트워크 오류가 잡히는 것을 볼 수 있다.
물론, 우리 팀에서는 모든 Exception을 기록하고 있기 때문에, 저 크래시 로그들이 모두 사용자가 인지할 수 있는 크래시로 이어졌다기 보단, API 호출이 조용히 실패했다고 보는 것이 합리적이다.
왜?
사실, 코드 자체의 문제보다는 외부 환경이 문제였다.
우리 학교의 교내 네트워크는 상당히 불안정하다.
네트워크 속도도 느리고, 자주 끊기고, 점검도 자주한다. (얼마 전에도 강의실 와이파이가 전부 고장나서 일주일 정도는 사용을 못했다.)
결국, 우리가 통제할 수 없는 외부 환경의 문제가 우리 앱의 VoC로 들어오고 있던 것이다.
해결 해야지 뭐...
하지만 사용자들은 그런걸 모른다. 우리 문제인 줄 안다.
그러므로, 우리가 해결해야 한다.
어떻게 해결해야 할까?
네트워크가 문제니, 그냥 네트워크 없이도 정상적으로 동작하도록 하면 되지 않을까?
Offline-first
이야기를 듣고 계시던 멘토님께서 'Offline-first'라는 키워드를 던져 주셨다.
안드로이드 공식 문서에 따르면, 네트워크가 없는 상황에서 앱의 전체, 혹은 핵심 기능이 동작하도록 하는 것이 Offline-first이다.
딱 우리 팀이 처한 상황에 적합한 해결책이다.
정책
Offline-first를 도입하기 앞서, 어느 화면에 어디까지 도입해야 할 지 고민이 필요했다.
우리 서비스는 굉장히 많은 기능을 가지고 있지만, 그 중에서도 자주 사용되는 기능은 다음과 같다.
- 식단
- 버스
- 시간표
이 화면들은 모두 다른 데이터 갱신 주기를 가지고 있다.
먼저 식단의 경우, 매주 1회 식단 데이터가 갱신되며, 매일 식사 시간 30분에서 1시간 전 쯤 영양사께서 사진을 올려주신다.
매일 갱신되는 데이터라고 볼 수 있다.
버스의 경우 조금 다르다.
매 학기 개강 전 학교 셔틀 데이터가 업데이트되고, 극히 드물게 학기 중 업데이트된다.
그 외 시내버스 및 시외버스의 시간표는 거의 업데이트 되지 않는다.
사실상 학기당 1회라고 생각할 수 있다.
마지막 시간표는 크게 두개의 데이터를 가지고 있다.
먼저 수업 데이터. 이는 매 학기 1회 업데이트된다.
다음으로 개인별 시간표 데이터는 불규칙한 주기를 가지고 있지만, 학생이 직접 시간표를 수정할 때 업데이트 된다.
이 중 가장 간단해보이는 식단 화면에 우선적으로 Offline-first를 도입하기로 했다.
식단을 언제 업데이트 할 것인가
처음에는 WorkManager를 통해 일정 주기마다 데이터를 업데이트 하는 것을 고려하였고, 실제로 그렇게 구현을 하였다.
하지만 테스트 과정에서 문제가 발생하였다.
영양사님이 식단 사진을 업로드하면, FCM을 통해 사용자에게 notification이 발송된다.
그런데, 이미 식단 데이터가 캐싱되었을 경우 식단의 imageUrl이 null인 상태이고, 즉시 갱신되지 않아 이미지가 보이지 않는 문제가 발생했다.
즉, 사용자의 입장에서는
식단 사진이 업로드 되었다고 했는데, 왜 사진이 보이지 않지?
라는 상황이 되는 것이다.
처음에는 "FCM 날라오면 WorkManager 한번 더 실행하자"라고 생각했다.
근데, 과연 식단 데이터에 WorkManager까지 쓸 필요가 있을까?
처음으로 돌아가서, Offline-first를 도입한 목표는 다음과 같았다.
네트워크가 없는 상황에서도 앱이 정상 작동하도록 하자
그렇다면, 그냥 네트워크가 없을때만 캐싱된 데이터를 보여주고, 없을때는 네트워크에서 가져오면 되는거 아닌가?
Single Source of Truth
이 부분에서 고민한게 Single Source of Truth, 단일 진실 공급원이다.
만약, 네트워크 상태에 따라 어느 경우에는 로컬 DB, 어느 경우에는 API로부터 데이터를 가져오게 되면 어떻게 될까?
동일한 데이터를 두 곳에서 가져오는 경우가 발생한다.
그렇다면, 만약 두 데이터가 모종의 사유로 달라진다면 어떤 일이 발생할까?
예를 들어, 로컬에 캐싱된 오늘의 메뉴는 김치찌개인데, API에서는 메뉴가 참치김치찌개라고 가정해보자. 김치찌개에는 참치가 진리다.
사용자는 동일한 날짜, 동일한 시간의 메뉴임에도 불구하고 네트워크 상태에 따라 다른 결과를 받게 된다.
사용자 입장에서는 무엇이 맞는 정보인지 알 수 없게 되고, 서비스에 대한 신뢰도가 떨어질 수 밖에 없다.
이래서 SSoT가 필요하다.
코인에서는 어떻게 할 것인가.
간단하다. 사용자에게 보여지는 모든 데이터는 로컬 DB에서 가져온다.
그리고, 네트워크가 연결되어 있다면, API를 호출하여 로컬 DB만을 갱신한다.
즉, API → DB → UI의 흐름이 되는 것이다.
그러기 위해, 기존의 Result<T>를 쓰던 UseCase를 전부 Flow로 변경하였다.
그래서 끝?
아니다. 여기까지 읽은 사람이라면
캐싱만 도입하고 무슨 Offline-first래
라고 생각할 수 있다.
사실, 코인 서비스의 대부분 기능들은 CRUD 중 Read 중심이다.
그리고 위에서 언급한 기능들도, 시간표를 제외하면 전부 Read만 존재하는 기능이다.
그렇기 때문에, 데이터를 캐싱하고 보여주는 것 위주로 구현이 될 수밖에 없었다.
물론, Read가 아닌 기능들도 존재는 만큼, 이 부분에 대해서도 추후 팀원들과 논의를 통해 도입을 할 예정이다.
그때가 되면 데이터를 언제 갱신 할 것인지, 충돌은 어떻게 해결 할 것인지에 대해서도 고민이 필요할 것이다.
'개발 > 안드로이드' 카테고리의 다른 글
| 난독화와 R8 Full mode 적용하기 (0) | 2026.09.13 |
|---|---|
| Kotlin DSL을 활용한 에러 핸들링 설계 (1) | 2026.03.23 |
| Compose contentDescription null과 ""의 차이 (0) | 2025.10.20 |
| Retrofit2 난독화 했을 때 NullPointException 해결 (0) | 2022.10.02 |