C.W.K.
Stream
Lesson 03 of 04 · published

내구성 있는 outbox 로서의 IndexedDB

~13 min · indexeddb, outbox, storage, blobs

Level 0신호 없음
0 XP0/32 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"outbox 는 신뢰 모델 전체를 object store 하나에 담아: 커밋했고, 배달을 기다리는 것들."

왜 IndexedDB 지, localStorage 가 아니라

내구성 있는 폰 쪽 캡처엔, 저장소 선택이 장식이 아냐. localStorage 는 동기, 문자열 전용, 작아 — 메인 스레드를 막고 이미지 blob 을 못 담아. IndexedDB 가 맞는 도구야: 비동기(UI 를 절대 안 얼려), 트랜잭션 지원(지난 레슨의 원자적 aggregate 가 이거에 기대), 구조화된 객체랑 바이너리 Blob 을 직접 저장해. 앱 재시작을 넘어 이미지 붙은 질문을 안전하게 쥐는 게 일 전부인 클라이언트한테, 그 조합은 있으면 좋은 게 아냐 — 기반이야.

outbox 패턴

핵심 구조는 outbox 야: 캡처된 턴들의 내구성 있는 큐, 각각 sync 상태 태그가 붙은. 메시징 세계의 outbox 패턴을 기기에 적용한 거야. 흐름은 어느 지점에서 끊겨도 안전한 루프야:

  • Enqueue: 제출된 턴이 blob 이랑 원자적으로, pending 으로 outbox 에 들어가.
  • Drain: 워커가 pending 턴을 집어 배달을 시도하고, sending 으로, 그다음 answeredfailed 로 옮겨.
  • Resume: 앱 실행 때, 워커가 아직 answered 아닌 걸 outbox 에서 다시 훑어 멈춘 데서 이어가.

outbox 가 내구성 있으니까, 이 단계 중 뭐도 이전 게 같은 세션에서 끝났다고 가정 안 해. enqueue 랑 drain 사이에 앱을 닫으면, drain 은 그냥 다음 실행 때 일어나. 큐가 기억이야.

outbox 는 폰의 ground truth

기기에서 outbox 는 서버가 아는 걸 담은 캐시가 아냐 — 서버가 아직 들었든 말든 상관없이, 사용자가 뭘 물었는지의 권위 있는 기록이야. 그래서 몇 시간 오프라인을 살아남을 수 있어: 로컬 상태의 진실 출처로 서버를 필요로 하지 않으니까. (백엔드는 canonical 대화의 진실 출처로 남아 — outbox 는 폰의 내구성 캡처 층, 다른 일이야. 트랙 5 가 그 선을 정확히 그어.)

Code

outbox 비우기, 재개 안전·typescript
// 제출, 재연결, 앱 실행 때 실행. 설계상 멱등.
async function drainOutbox() {
  const pending = await db.getAllFromIndex("turns", "by-status", "pending");
  for (const turn of pending) {
    await setStatus(turn.deviceTurnId, "sending");
    try {
      const answer = await deliver(turn); // 저장소에서 blob 을 읽음
      await saveAnswer(turn.deviceTurnId, answer, "answered");
    } catch {
      await setStatus(turn.deviceTurnId, "failed"); // outbox 에 남음, 재시도 가능
    }
  }
}

// 루프 도중 앱을 죽여도 생존자는 여전히 내구 저장소 안에서
// 'pending' 이나 'sending' — 다음 drainOutbox() 가 바로 다시 집어.

External links

Exercise

Pippa Go outbox 의 object store 를 설계해봐: 턴의 key path 는 뭐고, '모든 pending' 을 쿼리하게 해주는 index 는 뭐고, 이미지 blob 을 턴이랑 같은 트랜잭션에서 커밋되게 어떻게 저장할까? 스키마를 스케치하고, 턴 하나를 enqueue 부터 answered 까지 추적하며 각 단계 상태를 이름 붙여.
Hint
deviceTurnId 로 키한 'turns' 스토어, drain 쿼리용 syncStatus index, 같은 트랜잭션에 쓰는 'blobs' 스토어. 상태 걸음은 pending → sending → answered (또는 failed), 그리고 화살표 하나하나가 내구성 있는 쓰기야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.