Skip to content

진행중 세션 소실 — 감지 시스템·임시 보관·복원 팝업·서버 체크포인트 (이슈 #887)

Phase 현황

Phase내용상태
Phase 0감지 시스템(계측 8종·IDB 타임아웃·리포터 생존화) + 로컬 임시 보관함 + 복원 수리✅ #898
Phase 1무조건 로컬 저장(hidden/pagehide 즉시 영속)✅ #906
Phase 2부팅 확인 팝업(이어서/삭제/닫기) + designContract 마커✅ #906
Phase 3서버 임시 체크포인트(D5 — 20260829140000·스로틀 업로드·복원 폴백)✅ #906 + db push·Production 프로브
Phase 4게이트·검증(신규 26케이스 + pgTAP 13, 풀 레인 1861/1861)

1. 배경

08-28 저녁 오너의 진행중 운동이 두 차례 통째로 사라졌고(21:00 콜드 재부팅·~21:35), 재실행에도 돌아오지 않아 4분 만에 재입력했다(이슈 #887, BUG-037). 진행중 드래프트는 편집마다 IndexedDB 봉투로 자동저장되고 부팅 시 조용히 복원되는 구조였다.

2. 문제 제기

원인 조사(코드 6레인 전수 + Production 코드 그대로 재현 19케이스 + 반박 검증)의 구조적 결론: 소실 전 경로가 무계측(검증 실패·만료의 무통보 삭제, 복원 경합의 침묵 포기, 발행 게이트 드롭, IDB 행)이고 유저당 봉투 1슬롯이라 다음 운동 시작 ~0.95초에 이전 봉투가 영구 덮여 증거까지 인멸된다. 후보 3계열(S1 부팅 직후 선탭 봉쇄 / S2 실값 오염 검증 전멸 / S3 WKWebView 저장소 계층)이 모두 관측과 정합해 단일 확정이 불가능했다 — 판별 불능 자체가 07-30부터의 잠복 설계.

3. 해결 방안

  • 원칙(오너 결정, 08-29): ① "경로 전부 무계측이 제일 문제 — 감지 시스템 먼저"(D 채택) ② "진행중 세션 임시 보관 시스템 구축" ③ D5(서버 체크포인트) 포함 Phase 0~4 중단 없이 ④ BRID(#879) 대기하지 않음(드래프트는 세션 전 산물).
  • 접근: 원인 3계열을 모두 봉인하는 4층 — 파기 대신 격리(+계측) / 강제 종료 직전 즉시 저장 / 부팅 확인 팝업 / 서버 체크포인트.

4. 적용한 내용

Phase 0 (#898) — 봉투를 지우거나 덮어쓰는 전 지점(검증 실패·만료·덮어쓰기·저장/삭제/빈 초안 정리·낡은 편집 폐기)이 archive:user:<uid>:<ts> 보관함 이동으로 교체(최근 5세대·7일 TTL). 계측: draft_archive_invalid(첫 실패 지점 사유 토큰)·expired·overwritten·draft_restore_race_aborted·draft_restore_foreclosed(S1 직접 감지)·draft_publish_dropped_*(4게이트, 세션당 1회·펜스는 60초 지속 시)·draft_storage_wipe_detected(IDB/localStorage 쌍마커, lean 하드컷 준수 위해 workoutDraftStorageMarker.ts 분리). 복원 수리: requestId 증가를 실제 시작 시로 이동(침묵 포기 홀 제거) + 커밋 직전 선점 재확인(비행 중 새 시작 클로버 방지 — 조사에서 발견된 별개 잠복 결함) + settled 래치. IDB 전 연산 8초 타임아웃(행 → IdbTimeoutError). 리포터 큐 sessionStorage→localStorage + hidden/pagehide flush.

Phase 1~3 (#906) — hidden/pagehide에서 최신 스냅샷 즉시 영속(꼬리 유실 창 ~950ms 제거). 모바일 복원이 확인 팝업 발행(이어서 하기 / 삭제=재확인 1회·보관함 소프트 삭제 / 닫기=이어하기 바 유지). 서버 체크포인트: 유저당 1행 opaque jsonb(256KB)·펜스·TTL 3일 lazy 정리·RPC 3종(본인 한정), 클라 스로틀 업로드(60초·운동 교체 즉시)·봉투 정리 동펜스 서버 정리·복원 폴백 실체화(로컬 검증기·보관함 규율 그대로 통과).

작업 중 드러난 것 — ① Node 22의 tsx가 소스 내부 임포트와 테스트 임포트를 별개 리포터 인스턴스로 로드(로컬 Node 24는 단일) → 싱글턴 관찰 단언은 CI에서만 실패, 소스-앵커 관용구로 교체. ② 마이그레이션 번호 20260829100000이 같은 날 병행 트랙 3개(#879 100000·110000, #888 120000, #880 130000)에 차례로 선점 → 140000까지 두 번 재번호(번호 사다리 — 커밋 직전과 머지 직전 각각 재확인 필요). ③ 새 data-lg 마커는 designContract 등록 필수. ④ schema.sql append는 \n\n 구분자까지가 계약.

5. 적용 결과

항목
소실 경로 계측0종 (7일 이벤트 0건)계측 10종+ (파기 사유 토큰 포함)
봉투 파기 시 데이터즉시 소멸보관함 5세대·7일 보존
강제 종료 직전 입력최대 ~950ms 유실hidden 즉시 영속
재부팅 시 미저장 세션 인지시작 탭 한정 바(구별 불능)전면 확인 팝업 100%
기기 저장소 소실(S3)복구 불가서버 체크포인트 복구(TTL 3일)
재발 시 원인 판별불능1회 발생으로 S1/S2/S3 특정
게이트신규 26케이스 + pgTAP 13 (풀 레인 1861/1861)

Production 실측: 마이그레이션 장부 최신 20260829140000, 테이블·RLS·RPC 3종·직접 읽기 차단·authenticated 실행 가능 프로브 전부 확인. Vercel Production success + 청크 실측(mobileRoot-CEIdDgIn.js=부팅 팝업, HostFrames-PKe1vKp6.js=보관함·계측·체크포인트 마커).

미검증: 오너 실기기의 부팅 팝업·이어하기·서버 복구 실동작(확인 대기). 실기기 e2e는 하네스 제약(인증 부팅+IDB 시딩)으로 스토어 행동+SSR 마크업+배선 앵커 3층 대체.

6. 이번 개선으로 향상된 것

  • 진행중 운동이 사라질 수 있는 원인 3계열이 각각 독립 층으로 봉인됐고, 소실이 다시 일어나면 텔레메트리 1회로 특정된다.
  • "파기 대신 격리"가 드래프트 캐시의 기본 의미론이 됐다(사용자 삭제도 소프트 삭제).
  • 기기 교체·재설치에도 진행중 운동이 따라오는 서버 층이 생겼다.

남은 것

  • 오너 실기기 확인(부팅 팝업 3갈래·서버 복구) → BUG-037 상태 갱신.
  • 임시 보관함 목록 UI(현재 복구 진입은 팝업 경유) — 별도 결정.
  • 검증기 완화(규약 밖 값 1개 → 봉투 전멸 대신 엔트리 격리) — 별도 결정.