ELI5 · GUEST RESOURCE CACHE · 2026.09.14
대기 시간에 자료를 받고,
입장할 때 남은 것만 확인해요.
마지막 업데이트 2026-09-14
수업 시작 전에 파일을 챙겨 두면, 입장 준비 때 다시 받을 자료를 줄일 수 있습니다.
PPI-1299 · 최신 브랜치 구현 확인 · 2026.09.14
기다리는 동안 준비물을 챙겨요
필요한 파일을 하나씩 저장
이미 시작한 저장은 끝까지 정리
최신 자료 중 없는 것만 준비
5초 안에 정리가 끝난다는 보장은 없어요. 시작 시각이 지나도 정리 중이면 “입장 준비 중...”으로 기다립니다. 실제 대기 감소량은 아직 실측하지 않았습니다.
입장할 때 할 일을 미리 나눠 해요
대기실에서는 자료를 미리 받지 않았습니다.
같은 버전으로 저장된 파일은 입장 준비 때 다시 받지 않습니다.
받다가 중단한 파일의 조각은 넘기지 않습니다. 저장하지 못한 파일은 입장 준비 때 다시 받을 수 있어요.
누구의 어떤 자료를 준비하나요?
인증 세션의 아동 본인 수업만 조회합니다. 비레거시·PENDING 수업이 SLIGHTLY_EARLY(시작 전 10분 이내)이고, 클라이언트 기준 시작까지 5초보다 많이 남았을 때 준비합니다. 다른 아동 ID를 요청에 넣어 자료를 고르는 API가 아닙니다.
활동·아바타에 필요한 파일명을 모아 중복을 제거합니다. 주소와 버전을 확인한 뒤 Cache API에 저장합니다. 대기실 완료 표시로 최종 resourcesReady 검사를 생략하거나 수업 타이머·AI 연결·미디어 발행을 앞당기지 않습니다.
준비물 목록은 가끔 다시 확인해요
목록 다시 확인
서버 확인 횟수 줄이기
입장 준비로 넘기기
일정 변경을 즉시 알아차리는 방식은 아닙니다. 입장할 때 수업과 자료를 다시 확인합니다.
목록 변경·실패·탭 이동은 어떻게 처리하나요?
- 120초는 다운로드 작업이 종료된 뒤 적용됩니다. 성공뿐 아니라 실패로 종료된 경우도 포함합니다. 고정 시각마다 호출하는 방식이 아니라 응답·작업 종료 후 다음 타이머를 잡습니다.
- 회차와 파일명 집합이 같으면 같은 준비 작업을 반복하지 않습니다. 목록이 바뀌면 이전 다운로드를 취소하고 정리가 끝난 뒤 새 목록을 준비합니다. 같은 이름 파일의 버전 변경은 이 목록 비교만으로 탐지하지 않으므로 입장 시 버전을 재확인합니다.
- 시작 시각이 바뀌거나 대상 수업이 아니게 되면 정리 후 입장 검증을 다시 요청합니다. 검증 결과에 따라 새 대기실 또는 입장 흐름으로 이어집니다.
- 탭이 숨겨지면 일시 중단하고, 다시 보일 때 조건을 확인합니다. 마감으로 닫힌 동일 사용자·시작 시각의 작업은 다시 시작하지 않습니다.
- 다운로드·저장 실패는 “입장할 때 자료를 다시 확인할게요”로 표시합니다. 이미 완료된 뒤 목록 조회만 실패하면 완료 상태를 유지합니다. 최종 입장 준비의 필수 다운로드 실패 정책은 유지됩니다.
“준비 완료”와 “재생 성공”은 달라요
미리 보관해도 실제 재생과 화면 표시는 별도로 확인해야 합니다.
중단과 입장 사이의 안전한 순서
cutoffAt = 예정 시작 시각 − 5,000ms. 타이머 외에 응답 적용·다음 파일·재시도 전에도 마감을 검사하고 취소를 전파합니다. await stopAndDrain()은 요청과 취소할 수 없는 캐시 작업의 종료까지 기다립니다. 그 뒤에만 입장 검증과 V2 이동을 진행합니다.
자동 입장 요청은 진행 중 Promise를 공유합니다. 대기실 카운트다운 완료 콜백의 1.5초 지연은 다운로드 가능 시간에 더하지 않습니다. 수업 중 단계별 다운로드와 부분 파일의 페이지 간 인계는 이 구현 범위에 없습니다.
확인한 것과 남은 것
관련 자동 테스트: 9개 파일 · 48개 통과.
저속망·실기기에서 줄어든 대기 시간과 통화 품질은 이번에 측정하지 않았습니다.
코드 기준과 변경 경로
구현 커밋: 756e7f8c8a745eb4b3ae6b4b6202bd33fc572ebf. 최신 확인 HEAD: a5ca395097ed13f9d70b2ed1faeeaf0eb0ad6741 (develop 병합 포함). 앱 작업 트리는 깨끗한 상태에서 확인했습니다. 운영 배포 여부는 확인하지 않았습니다.
별도 GUEST_WAITING_PREFETCH_ENABLED 환경변수는 현재 실행 경로에서 사용하지 않습니다. 대기실 상태와 인증 사용자, 화면 표시 여부 및 서버의 수업 조건으로 동작합니다.
apps/web/app/api/guest-resource-prefetch/route.tsapps/web/lib/api/services/guest-resource-prefetch.service.tsapps/web/lib/api/lesson-entry-selection.tsapps/web/lib/resources/waiting-resource-prefetch.tsapps/web/hooks/use-waiting-resource-prefetch.tsapps/web/components/pages/guest-entry.tsxapps/web/components/ui/waiting-room.tsxapps/web/components/ui/waiting-room.stories.tsxapps/web/hooks/use-resource-cache.tsapps/web/lib/fetch-resumable.tsapps/web/lib/resource-abort.ts
이번에 실행한 검증과 재현 방법
2026-09-14 · apps/web에서 다음 명령을 실행해 9개 파일, 48개 테스트 통과를 확인했습니다.
pnpm exec vitest run
lib/resources/waiting-resource-prefetch.test.ts
hooks/use-waiting-resource-prefetch.test.tsx
hooks/use-resource-cache.test.ts
lib/fetch-resumable.test.ts
lib/resource-abort.test.ts
app/api/guest-resource-prefetch/route.test.ts
app/api/validate-lesson/route.test.ts
components/pages/guest-entry.test.tsx
components/ui/waiting-room.test.tsx위 줄들을 공백으로 이어 한 명령으로 실행합니다. 마감 경계, 취소·정리 순서, 캐시 재사용, API 대상 제한, 재조회 간격과 UI 콜백을 검증합니다.
pnpm --dir apps/web storybook의 Guest/WaitingRoom에서 자료 확인·진행·완료·재확인·입장 정리 상태를 fixture로 볼 수 있습니다. 이번 문서 갱신에서는 앱 타입 검사·ESLint·Storybook 빌드를 다시 실행하지 않았습니다. 이전 문서의 통과 기록을 이번 실행 결과로 간주하지 않습니다.
추가 확인: 실제 브라우저 HAR에서 중단 후 새 요청 유무, 시작 이후 정리 대기 시간, 재다운로드 바이트, 실기기 통화 품질을 관측해야 합니다. 로그의 attemptId로 Guest:WaitingPrefetchStopped와 Guest:WaitingPrefetchEntryValidationStarted를 연결해 정리 완료와 입장 검증 시작 순서를 확인할 수 있습니다.
더 알아보기 · 기존 캐시 동작과 로그 분석
큰 그림: 배송 → 보관 → 사용
입장 전에는 ①·②를 준비합니다. 실제로 보이고 움직이는지는 ③에서 결정됩니다.
입장 전 · 미리 준비하기
없는 파일만 하나씩 받기
필수 파일 다운로드가 최종 실패하면 입장을 막습니다. 저장만 실패하면 진행할 수 있습니다.
목록·진행률·예외 자세히
현재 V2 client-guest 흐름 기준. 아바타 리소스, embedded 스텝 리소스, 선택 이미지·영상이 수집 대상입니다. 알림 이미지는 별도 비차단 프리캐시이며 실패해도 입장을 막지 않습니다.
진행률은 바이트 비율이 아니라 처리한 파일 수입니다. 캐시 적중과 다운로드 후 저장 시도 완료 모두 증가합니다. 메타데이터 누락은 다운로드를 시작하기 전에 중단하고, 다운로드 최종 실패는 뒤의 파일을 더 받지 않습니다.
Cache Storage 열기 실패나 URL API 실패는 예외를 전달합니다. V2 호출부는 실패 목록·예외를 입장 차단으로 처리합니다. 레거시 guest-approved2 경로는 실패해도 입장을 이어가므로 같은 정책으로 해석하면 안 됩니다.
수업 중 · 필요할 때 꺼내기
가까운 곳부터 찾습니다
blob:은 브라우저 안 데이터의 주소입니다. 재생 성공 표시가 아닙니다.
메모리·버전·저장 실패 자세히
저장 캐시는 dubu-resources-v1, 키는 https://dubu-cache/{fileName}?v={updatedAt}입니다. 가상 도메인은 조회용 라벨이며 그곳으로 다운로드하지 않습니다.
메모리 두 Map의 키는 파일명만 사용합니다. 메모리에 있으면 API·버전 확인을 건너뜁니다. 따라서 updatedAt 변경만으로 이미 메모리에 있는 URL까지 즉시 갱신되지는 않습니다. 저장 캐시 키에도 화질 구분이 없습니다.
lazy 경로는 다운로드 후 캐시 쓰기가 실패해도 생성한 blob URL을 반환합니다. 반면 preload에서 저장에 실패한 Blob은 메모리 Map에 보관하지 않아 나중에 다시 다운로드할 수 있습니다. lazy에서 전체 오류는 null로 반환합니다.
invalidateMemoryCache는 해당 blob URL을 해제하고 메모리 항목을 지웁니다. clearCache는 전체 blob URL·메모리·저장 캐시를 지웁니다. 버전 키 변경 자체가 옛 항목을 삭제하지는 않습니다.
다운로드가 끊기면
받은 조각 뒤부터 다시 요청합니다
최대 4번 시도 = 첫 요청 1번 + 재시도 3번. HTTP 상태 오류는 즉시 중단합니다.
재시도·화질 선택·한계
재시도 대기는 기본 0.5초 → 1초 → 2초입니다. Range 요청에 206이 아닌 응답이 오면 누적 데이터를 비우고 다시 시작합니다. 최종 크기를 아는 스트림 경로는 수신 바이트 수가 같은지 확인합니다. 이것은 영상 디코딩 검사가 아닙니다.
저사양 기기이며 480p 처리본이 있는 영상은 480p를 선택합니다. 그 외에는 processed 또는 raw를 사용합니다. 파일 다운로드는 S3 presigned URL로 직접 요청하며 앱 API는 주소를 발급합니다.
이 문서의 캐시는 훅이 Cache API를 직접 호출하는 경로입니다. Service Worker가 리소스를 대신 받는 흐름으로 설명하지 않습니다. 별도 service-worker-clean-up 컴포넌트에는 등록 해제 처리가 있습니다.
오류를 읽는 법
어디서 실패했는지 먼저 구분하세요
“리소스 준비 완료” 로그만으로 모든 이미지·아바타·영상이 정상 표시됐다고 판정할 수 없습니다.
로그 이름과 확인할 증거
- 주소 API:
Guest:ResourceUrlFetchFailed— API 요청 실패는 네트워크·HTTP 응답 등도 포함하며 서버 원인으로 단정하지 않습니다. - 다운로드:
Guest:ResourceFetchFailed— preload/lazy, HTTP 상태, 수신량, 시도별 오류를 확인합니다. - 보관함:
Guest:ResourceCacheOpenFailed, preload 쓰기 실패의Guest:ResourceCacheWriteFailed. - 입장 차단:
Guest:ResourceCachingBlocked. 이 이벤트들은 trackEvent 경로이므로 모든 이름이 JSONL에 반드시 포함된다고 가정하지 않습니다. RESOURCE_ERROR수집기는 비-ErrorEvent의 src/href만 기록합니다. 요소 종류·미디어 오류 코드·복구 결과가 없으므로 같은 URL의 로드·재생 기록이나 녹화가 필요합니다.
실제 로그 예시 · 2026.09.13 / 익명화된 11회기
수업 계속 진행 ≠ 오류 리소스 복구 확인
ghostwhite.mp4 1건 + 서로 다른 blob URL 2건. 당시 split:image 대화 단계.
재생 시각 증가, muted:false, readyState:4. 화면 이미지 복구의 증거는 아님.
ended:true 확인. 앞서 오류 난 blob 두 개와는 다른 리소스.
미확정: 오류 blob 두 개는 원본 파일명·후속 성공 기록과 연결되지 않습니다. 화면 영향과 복구를 확정하려면 해당 시각 녹화가 필요합니다.
분석 범위·검증·변경 사항
제공된 확장자 .jsonl.gz 파일은 실제로 일반 JSONL이며, 18,419개 레코드를 누락 없이 파싱했습니다. 전부 guest 로그입니다. 오류 URL의 전체 등장 횟수와 후속 스텝·미디어 이벤트를 대조했습니다. 아동 이름·전체 세션 식별자는 문서에 포함하지 않았습니다.
이 작업은 기존 동작 분석과 문서 갱신입니다. 앱 코드 변경·동작 전후 변경·배포는 없습니다. 근거는 2026-09-14 현재 코드 리비전과 제공 로그이며 당시 배포 리비전의 일치 여부는 검증하지 않았습니다. 다운로드 장애 실기기 재현이나 캐시 자동 복구를 검증한 것은 아닙니다.
1차 구현 반영 · 단계별 다운로드는 제안 / 미구현
기다리는 동안 받고,
시작에 필요한 것부터 준비하면?
1차 범위인 대기 중 선다운로드와 시작 5초 전 중단은 브랜치에 구현했습니다. 입장 후 단계별 다운로드는 후속 검토입니다.
다운로드 속도가 빨라지는 것은 아닙니다. 원래 기다리는 시간과 다운로드를 겹쳐 체감 대기를 줄이는 전략입니다. 개선 폭은 실측 전입니다.
우선 추천 · 입장 전
대기 중 선다운로드
수업 시작 전 대기 시간을 활용합니다. 기존 ‘전체 필수 자료 준비 후 입장’ 조건을 유지할 수 있습니다.
늦게 접속하면 효과가 작습니다. 다운로드 화면 이름만 대기실로 바꾸는 것은 시간 단축이 아닙니다.
조건부 추천 · 입장 후
시작 자료 → 다음 자료
현재 활동 자료를 확보하고 시작한 뒤, 다음 활동 자료를 미리 받습니다.
통화와 회선을 나눠 쓰므로 수업 중 멈춤을 늘리지 않는 제어가 필요합니다. 빈 상태로 먼저 입장시키는 방식은 권장하지 않습니다.
현재 코드와 실제로 달라지는 점
기존 고정 리비전의 WaitingRoom은 카운트다운을 표시하고 종료 시 입장 검증을 다시 시도합니다. PPI-1299 워킹트리는 상위 GuestEntryPage에 선다운로드 훅을 연결했으며 WaitingRoom 자체는 표시를 담당합니다. V2 useGuestPageSession은 전체 필수 자료 준비 후 resourcesReady를 켜고 입장 요청을 진행합니다. 이미 다운로드가 끝난 뒤의 승인 대기에 작업을 붙이면 효과가 작습니다.
PPI-1299는 인증된 사용자의 예정 수업과 리소스 목록을 입장 전에 조회하는 경로를 추가했습니다. 기존 조기 입장 제한을 우회하지 않고, 시작할 때 수업 상태·자료 버전을 재확인해야 합니다. 취소·수업 변경 시 이전 다운로드를 중단하거나 새 목록으로 바꿉니다.
준비는 다운로드·캐시 처리로 한정하고 수업 타이머·AI 세션·미디어 발행을 앞당기지 않습니다. 여기서 백그라운드는 대기 화면 또는 수업 화면과 함께 비동기로 받는 뜻이며, 앱 종료나 운영체제의 백그라운드 상태에서도 계속 받는 기능을 의미하지 않습니다.
가정으로 보는 효과: 60MB / 2Mbps
실제 파일 전송에 쓸 수 있는 속도가 일정하게 2Mbps라고 가정하면 60MB × 8 ÷ 2Mbps = 약 240초입니다. 5분 일찍 접속해 이 시간에 받으면 시작 이후의 추가 대기를 상당 부분 줄일 수 있습니다. PPI 실측값이 아니며 API 지연·재시도·통신 오버헤드는 제외한 예시입니다.
입장 후 방식은 ‘다음 자료의 남은 다운로드 시간’이 ‘그 자료를 사용할 때까지의 시간’보다 짧아야 유효합니다. 통화 중 남는 다운로드 여유를 기준으로 판단해야 하며, 지속적으로 수신 속도가 부족하면 대기 시간을 수업 중으로 옮기게 됩니다.
조건 · 검증 · 적용 순서
빠른 입장보다 중요한 것은
수업 도중 멈추지 않는 것
입장 후 다운로드에 필요한 설계 조건
- 현재·재개 활동 우선: 해당 영상·이미지와 아바타 상태별 파일을 확보합니다. 중간 재입장, 진행자 점프, 조기 종료·엔딩 경로도 준비 대상에 반영해야 합니다.
- 회선 사용 조절: 후순위 작업은 제한된 동시성으로 처리하고, 통화 품질 저하 시 새 작업을 보류하거나 진행 중 작업을 중단·재개할 수 있어야 합니다. PPI-1299는 외부 취소를 추가했지만, 부분 데이터를 보존하는 외부 스케줄러의 일시중지·재개는 구현하지 않았습니다.
- 중복 요청 통합: 백그라운드 요청과 화면의 즉시 요청이 같은 파일을 받지 않도록 진행 중 Promise를 공유하고 우선순위를 올립니다. 현재 Map은 완료된 Response·blob URL 중심이며 진행 중 요청 공유가 없습니다.
- 활동 전환 전 준비 확인: 아직 없는 리소스로 화면을 넘기지 않고 준비 상태를 표시합니다. 다운로드 대기를 이유로 수업이 자동 진행되거나 타이머가 부당하게 소모되지 않도록 정책도 정해야 합니다.
- 실제로 사용할 수 있는 준비 상태: preload의 cache.put 실패는 현재 완료로 취급될 수 있습니다. 단계별 입장 조건은 필요한 자료의 저장 또는 메모리 보유 여부를 구분해야 합니다. 이는 실제 디코딩·재생 성공 검증과도 별개입니다.
- 단순 priority 지정만으로 해결하지 않기: 낮은 요청 우선순위는 브라우저 힌트이며, AI 통화 대역폭을 예약하는 기능이 아닙니다.
느린 네트워크에서 여전히 남는 한계
현재 480p 선택은 기기 성능 기준이며 회선 속도 기준이 아닙니다. 데이터가 조금씩 계속 오면 전체 다운로드는 오래 지속될 수 있습니다. 영상은 전체 Blob 수신 후 반환하므로 일부만 받고 즉시 재생하는 스트리밍 방식은 아닙니다.
대기 시간이 없거나 회선이 수업에 필요한 전송량을 계속 감당하지 못하면 선다운로드만으로 해결되지 않습니다. 회선에 따른 작은 자료 선택·용량 절감은 별도 검토 대상입니다.
검증할 지표와 수용 기준
- 비교: 현재 전체 프리캐시 / 대기 중 선다운로드 / 시작 자료 후 단계별 다운로드를 같은 수업·기기·망 조건으로 비교합니다.
- 조건: 캐시 없음·있음, 저대역폭, 지연·패킷 손실, 일시 단절, 시작 직전 접속, 중간 재입장, 활동 점프, 캐시 저장 실패를 포함합니다.
- 효과: 로그인부터 시작 가능까지와 예정 시작 시각 이후 추가 대기를 따로 측정합니다. ‘입장 화면 전환’만 지표로 사용하지 않습니다.
- 안정성: 수업 중 리소스 대기 횟수·시간, 재생 실패, AI 음성 지연·끊김, 중복 다운로드 바이트를 비교합니다.
- 수용 방향: 시작 대기는 줄고 수업 중 자료 대기·통화 품질은 악화되지 않아야 합니다. 정량 기준은 현재 분포 측정 후 정합니다.
검증 상태: 이 절의 입장 후 단계별 다운로드는 설계 제안입니다. 1차 대기 선다운로드의 구현·검증 범위는 위 PPI-1299 절을 참고하세요. 저속망 실험·실기기 성능 개선·운영 배포는 수행하지 않았습니다.
근거와 더 자세한 문서
코드 근거 펼치기
- 준비물 수집 —
apps/web/lib/api/services/resource.service.ts:47 - V2 데이터·캐싱·입장 게이트 —
apps/web/entities/guest-page-session/model/use-guest-page-session.ts:3035 - 프리캐시·lazy·메모리·저장 캐시 —
apps/web/hooks/use-resource-cache.ts:87 - 이어받기·재시도 —
apps/web/lib/fetch-resumable.ts:82 - 주소·화질 선택 —
apps/web/app/api/resources/urls/route.ts:113 - RESOURCE_ERROR 수집 —
apps/web/lib/lesson-log-sink.ts:48 - 기본 ghostwhite 영상 —
apps/web/shared/ui/meet-video.tsx:484 - 레거시 입장 정책 —
apps/web/hooks/use-guest-entry-socket.ts:30 - SW 등록 해제 —
apps/web/components/ui/service-worker-clean-up.tsx:7
기존 캐시·로그 분석 기준: 722d30f1e39ba01871cf0a7fb50254fd0ec58258. 이 절의 소스 링크는 당시 리비전에 고정되어 있습니다. PPI-1299 최신 구현 근거는 위 ‘확인한 것과 남은 것’을 참고하세요.