앱을 으로 다루세요: 모든 디바이스는 로컬 입니다. 학습 동작은 와 append-only (outbox)에 기록됩니다. 재연결 시 디바이스는 하고 마지막 동기화 이후의 합니다. 충돌은 하나의 전역 규칙이 각 데이터 필드의 으로 해결합니다.
멘탈 모델: git 처럼 생각하세요 — 각 디바이스는 오프라인 상태에서 로컬로 "커밋"하고, 동기화는 덮어쓰기가 아니라 머지(merge) 입니다.
Device (offline) Server = source of truth
┌──────────────────┐ on reconnect ┌───────────────────────────┐
│ Local DB (SQLite)│ push ops (delta) │ Sync API /sync │
│ + Op log (outbox)│ ─────────────────▶ │ • idempotent by op.id │
│ cursor = lastSeen│ ◀───────────────── │ • merge by semantics │
└──────────────────┘ pull changes>cursor└───────────────────────────┘
Progress DB (Postgres)
폰의 시계는 신뢰할 수 없고 두 디바이스가 오프라인에서 같은 레코드를 수정할 수 있으므로, "last write wins"(LWW)는 조용히 데이터를 잃습니다. 의미론으로 해결하세요:
// op에는 id가 있음 -> 멱등하게 적용(재시도해도 이중 집계 안 됨). clientTs는 신뢰하지 않음.
function mergeProgress(server, op) {
return {
// progress는 증가만 함 -> max: 늦게 도착한 오래된 op가 값을 뒤로 끌어내리지 않음
progress: Math.max(server.progress ?? 0, op.progress),
// studiedMs는 가산 카운터 -> 델타를 더함, 덮어쓰지 않음
studiedMs: (server.studiedMs ?? 0) + op.studiedMs,
completedAt: server.completedAt ?? (op.progress >= 1 ? op.clientTs : null),
};
}
여기서 순수 LWW를 쓰면, 늦게 동기화되는 오래된 디바이스가 progress: 0.3 으로 서버의 0.9 를 덮어써서 → 진도가 사라집니다. max 를 취하고 studiedMs 를 합산하면 동기화가 순서에 무관(order-independent) 해집니다 — 수백만 대의 디바이스가 뒤틀린 시계로 동기화해도 안전합니다.
면접관이 확인하려는 것은 여러분이 클라이언트 시계를 신뢰할 수 없음(그래서 순수 timestamp LWW가 아님)을 아는지, 동기화를 멱등하게(op id) 만드는지, 데이터 의미로 머지하는지입니다. 약한 답변: "timestamp를 쓰고, last write wins." 강한 답변: 레플리카 + op 로그 + 멱등성 + 필드별 머지 + 서버를 source of truth로 두기 — 그리고 LWW의 데이터 손실 실패를 명시적으로 짚는 것.
LWW는 저렴하지만 손실이 있고, 의미론적 머지는 필드별 고민이 필요하지만 아무것도 잃지 않습니다. 진짜 협업 편집(노트, 문서)이 필요할 때만 완전한 CRDT/OT 를 꺼내세요 — 비용이 크므로 모든 곳에 쓰지 마세요.
주니어부터 시니어까지 상세한 답변이 포함된 IT 면접 질문 라이브러리.
후원하기