기기 교체 후 수신 메시지 복구를 위한 서버 동기화 구현기
목차
왜 만들었나
- 기기 교체하면 이전 수신 내역이 통째로 날아갔음. 사용자 문의가 꾸준히 들어옴
- 로컬에만 쌓아둔 데이터라 복구 경로가 아예 없었음
- 서버에 보존돼 있는 원본을 끌어와 로컬과 병합하는 흐름이 필요했음
구조 잡기
| 레이어 | 역할 |
|---|---|
| 진입 화면 | 최초 진입 시 동기화 트리거 |
| 설정 화면 | 수동 재동기화 + 마지막 시각 노출 |
| 로컬 DB | 메시지 캐시 + sync 메타데이터 |
| 동기화 모델 | 서버 응답을 로컬 스키마로 매핑 |
서버 호출 시그니처는 단순하게 잡았음. since 만 넘기고 증분으로 받음.
suspend fun sync(since: Long): Result<List<Message>>
진입 화면은 트리거만, 설정 화면은 상태 노출과 수동 버튼만. 동기화 자체 로직은 한 군데에 모아서 두 화면이 같은 함수만 호출하도록 했음. 처음엔 진입 화면 안에 코드를 다 박았다가 설정 화면에서 또 복붙하는 자신을 발견하고 바로 분리.
삽질 모음
- 처음엔 진입 시 풀싱크를 돌렸음. 첫 화면이 5초 가까이 멈춰서 바로 컴플레인 들어옴. 백그라운드로 빼고 증분 동기화로 변경
- 같은 메시지가 중복 인서트되면서 PK 충돌. upsert 로 바꿔서 정리
- DB 마이그레이션 누락으로 기존 사용자가 앱 켜자마자 크래시. 컬럼 추가는 항상 디폴트값 + 폴백 같이 작성하는 습관 들임
- 마지막 동기화 시각을 안 박아둬서 "왜 갱신 안 됐는지" 디버깅이 지옥이었음. 메타필드는 처음부터 박아두는 게 정답
배운 점
- 첫 진입 UX 가 데이터 정합성보다 우선. 무거운 작업은 무조건 백그라운드로
- 동기화 한 번 잘못 짜면 PK 충돌, 누락, 중복까지 한 세트로 옴. upsert + lastSyncedAt 조합이 표준이라 생각하기로 함
- 사용자가 체감하는 동기화 = 즉시성. 프로그레스 표시 빠지면 "안 됨" 으로 인식함. 인디케이터는 기능이 아니라 필수
- 기능 하나 추가했을 뿐인데 진입 화면, 설정 화면, 로컬 DB, 모델 4군데가 같이 흔들림. 다음에는 동기화 책임을 한 모듈로 미리 떼어놓고 시작할 예정
다음
댓글 0
첫 댓글 달아줘.