기기 안에 저장하던 설정을 다른 환경에서도 이어 쓰기 위해 서버와 맞추는 작업을 봤다. 처음에는 로그인할 때 내려받고 설정을 바꿀 때 올리면 된다고 생각했다. 두 기기에서 값을 바꾸거나 오프라인 상태에서 수정하면 어느 쪽이 최신인지 바로 모호해졌다.
동기화는 파일 복사보다 두 상태의 차이를 합의하는 과정이었다.
설정마다 동기화 범위가 달랐다
언어, 테마, 차트 표시 옵션처럼 사용자 경험에 영향을 주는 값은 서버 동기화 대상이 될 수 있었다. 창 크기나 기기 성능에 따른 옵션은 로컬에 남기는 편이 자연스러웠다.
public class SyncSettingsDto
{
public string Language { get; init; }
public string Theme { get; init; }
public ChartOptions Chart { get; init; }
public long Revision { get; init; }
}
로컬 설정 객체 전체를 보내지 않고 계약에 포함된 필드만 DTO로 만들었다.
최신이라는 말에 기준이 필요했다
기기 시각은 틀릴 수 있어서 로컬 수정 시간만 비교하기 어려웠다. 서버가 부여한 revision을 사용하고, 업데이트 요청에 내가 읽었던 revision을 함께 보냈다. 그 사이 다른 변경이 있으면 덮어쓰지 않고 충돌로 응답받았다.
GET settings -> revision 12
PUT settings (baseRevision: 12)
-> success revision 13
-> conflict current revision 14
초기 단계에서는 복잡한 자동 병합보다 새 서버 값을 보여주고 다시 선택하게 하는 안전한 정책을 택했다.
실패해도 사용 흐름은 유지했다
설정 저장 API가 실패했다고 사용자의 화면 변경을 즉시 되돌리면 불편했다. 로컬에는 변경을 반영하고 동기화 대기 상태로 표시한 뒤 연결이 회복되면 제한적으로 재시도했다.
로그인 직후 서버 설정을 받기 전 잠깐 기본값이 보이지 않도록 초기 로딩 순서도 조정했다. 서버를 기다릴 수 없는 상황에는 마지막 로컬 설정을 사용했다.
동기화는 성공과 실패 사이가 길었다
API 호출 한 번이 성공하는지만 보던 시기에는 동기화도 비슷하게 생각했다. 실제로는 오프라인 수정, 다른 기기 변경, 중복 재시도, 충돌이라는 중간 상태가 있었다.
이 작업으로 설정도 작은 분산 데이터라는 걸 배웠다. 어느 값을 공유할지, 누가 버전을 정할지, 실패 중 사용자에게 무엇을 보여줄지를 정해야 동기화가 됐다.