본문 바로가기
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 하나에 신뢰 모델 전체가 들어가. 커밋은 끝났고 배달만 기다리는 것들."

왜 localStorage 말고 IndexedDB 냐

폰에서 안 없어지게 캡처하려면 저장소 선택이 장식이 아냐. localStorage 는 동기고, 문자열만 담고, 용량도 작아. 메인 스레드를 막고 이미지 blob 은 아예 못 담고. IndexedDB 가 맞는 도구야. 비동기라 UI 를 절대 안 얼리고, 트랜잭션을 지원하고(지난 레슨의 통째 커밋이 여기 기대고 있어), 구조화된 객체랑 바이너리 Blob 을 그대로 저장해. 앱을 껐다 켜도 이미지 붙은 질문을 안전하게 쥐고 있는 게 할 일의 전부인 클라이언트한테, 이 조합은 있으면 좋은 정도가 아니라 바닥이야.

outbox 패턴

핵심 구조가 outbox 야. 캡처한 턴들이 sync 상태 딱지를 하나씩 달고 줄 서 있는, 안 없어지는 큐. 메시징 쪽에서 쓰는 outbox 패턴을 기기로 가져온 거지. 흐름은 어느 지점에서 끊겨도 안전한 루프야.

  • Enqueue: 제출된 턴이 blob 이랑 통째로 묶여서 pending 상태로 outbox 에 들어가.
  • Drain: 워커가 pending 턴을 집어서 배달을 시도해. sending 으로 갔다가 answeredfailed 로 넘어가고.
  • Resume: 앱을 켤 때 워커가 아직 answered 가 아닌 것들을 outbox 에서 다시 훑어서 멈춘 데부터 이어가.

outbox 가 안 없어지니까, 이 단계들 중 뭐도 앞 단계가 같은 세션에서 끝났다고 전제하지 않아. enqueue 랑 drain 사이에 앱을 닫아 버리면 drain 은 그냥 다음에 켤 때 일어나. 큐가 기억이야.

outbox 는 폰 쪽 정본

기기 입장에서 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

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

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