탭 첫 진입 데이터 워밍 — "캐시 없으면 느린 탭 전환"에서 유휴 선요청까지 (2026-08-31)
- 기간: 2026-08-31 (세션 1, 오너 보고 "탭들 사이에서 이동할때 캐싱되어있는거 없으면 반응이 너무 느린데 개선할 방법이 있을까?" → D1 "둘다 해줘")
- 랜딩: PR #1014(
f4c10dcf, Phase 1) — 마이그레이션 없음, Vercel 자동 배포, Actions 결제 블로커로 admin 머지 - 설계서: 없음(이슈 #1013 본문이 정본 — 전수 데이터 경로 매핑 + Production RPC 실측 포함)
- 정본:
src/react/appController.tsx탭 데이터 워밍 이펙트(주석 앵커 "탭 첫 진입 데이터 워밍(이슈 #1013") - 도구: Production RPC 실측 = Management API 읽기 프로브(jwt 주입·끝 raise 롤백)
- 게이트:
tests/react/tabDataWarming.test.mjs(계약 6 — 게이트·유휴 스케줄·순서·allowInactive·래치) - 버그리포트: 없음(성능 트랙)
- 계약: 없음
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | 유휴 데이터 워밍(일지·그룹·리포트) | ✅ PR #1014 (f4c10dcf) |
| Phase 2 | 일지 대기열 수리 | ❌ 불필요 판정 — 전면 로드는 큐 우회(코드 실측) |
| Phase 3 | 카탈로그 낙관 시작 | ➜ 지속 캐시 트랙으로 편입(이득이 콜드 캐시 한정) |
| Phase 4 | 지속 캐시 확장(D2) | ➜ 별도 트랙(후속 이슈) — 리비전 정합 설계 동반 |
1. 배경
화면 코드는 부팅 유휴 시간에 전량 프리로드(2026-08-19)하지만 데이터는 홈·피드·PR 전광판만 부팅 병렬 프리페치였다. 일지(3왕복)·그룹(1왕복)·리포트(응답 최대 3MB)는 첫 진입 때 네트워크를 그대로 기다렸고, 재부팅 간 지속 캐시는 홈 스냅샷뿐이라 "캐싱 없으면 느리다"는 체감이 정확했다.
2. 문제 제기
탭별 첫 진입 대기가 데이터 로드 시점 정책의 비대칭에서 왔다
Production 실측(오너 계정): 홈 42ms/43kB·피드 52ms/38kB(부팅 선수신 — 빠름) vs 일지 18ms/40kB×3왕복·리포트 425ms/716kB·카탈로그 328ms/913kB(지연 로드). 서버는 대체로 빠르고, 체감 지연 = 왕복 횟수·페이로드 다운로드·로드 시점.
3. 해결 방안
원칙 (오너 결정, 2026-08-31)
- D1 "둘다 해줘" — 리포트(볼륨) 워밍 포함(데이터 사용 증가 감수) + 지속 캐시 확장(D2)도 진행.
- 워밍 규약은 코드 워밍과 동일: 유휴·순차·실패 조용히·중복은 기존 래치가 차단.
접근
- 새 캐시 계층을 만들지 않고 기존 로더를 유휴 시간에 미리 한 번 호출 — 첫 진입 코드 경로가 그대로 캐시 히트가 된다. 달력은 allowInactive 탈출구(백필 리프레시가 쓰던 것) 경유.
- 리포트를 마지막 순번으로(응답 최대), 일지 탭 활성 중엔 달력 워밍 생략(loadDay의 선택 날짜 요청 abort 방지).
4. 적용한 내용
Phase 1 — 워밍 이펙트 (#1014)
appController에 모바일 전용 유휴 이펙트: ready 후 requestIdleCallback(4s 타임아웃)으로 일지 이번달(allowInactive·background)+오늘 상세 → 그룹 목록(activateForGroupTab 래치 선점) → 볼륨(loadPrDashboard screen:volume, 진입 경로와 동일 모양·force 없음). 오너당 1회 래치, 취소 정리 포함.
주요 결정과 그 근거
- 원계획 Phase 2 기각: "전면(보이는 달) 요청이 ±1 프리페치 큐에 밀린다"는 가설은 코드 실측으로 반증 — loadMonth 직접 호출은 ensureMonth 큐(동시 2)를 타지 않는다.
- 원계획 Phase 3 축소: 카탈로그는 캐시 워밍 시 이미 네트워크 0(셸 후 localStorage 히트) — 콜드 설치·버전 범프의 셸 뒤 줄서기만 남고, 이는 D2 트랙에서 리비전 정합(homeFragmentExpectation의 셸 결합)과 함께 풀어야 안전.
작업 중 드러난 것
- get_pr_overview는 서버 적립형(user_exercise_pr_events — 저장 시 계산해 적립, 조회는 읽기만 44ms). 기기 미저장이라 부팅마다 287kB 재수신 — 부팅 체감의 몸통, D2 대상.
- 워밍으로 그룹 목록의 "첫 진입 = 신선" 의미가 "부팅 시점 신선"으로 이동 — 종전에도 첫 진입 후 래치라 등가.
5. 적용 결과
| 항목 | 결과 |
|---|---|
| 일지 첫 진입 | 3왕복 대기 → 워밍 완료 시 캐시 히트(0왕복) |
| 그룹 첫 진입 | 1왕복 → 0왕복 |
| 리포트 첫 진입 | 716kB~ 다운로드 대기 → 0 (부팅 유휴에 선수신) |
| 테스트 | 전체 2,246건 통과(신규 6) · tsc·경계·unused·manifest 통과 |
| eslint·CI | 미검증 — Actions 결제 블로커 지속(오너 선례 지시 admin 머지) |
| 실기기 체감 | 시각 정보 항목(§14) — 워밍 실패 시에도 기존 경로 그대로라 기능 위험 없음 |
6. 이번 개선으로 향상된 것
탭 전환이 "받아둔 것을 여는" 동작이 됐다
코드 워밍(8/19)과 데이터 워밍이 짝을 이뤄, 하단 탭 전부가 부팅 직후부터 즉시 반응. 구조적으로 남는 것: 워밍 규약(유휴·순차·조용한 실패·래치 위임) + 계약 테스트.
남은 것
- D2 지속 캐시 확장 — 후속 이슈로 진행(오너 승인): PR 오버뷰·일지 이번달·그룹을 기기에 남겨 부팅 즉시 그리기. 리비전 정합(셸 적용 순서)·신선도 규칙 설계 동반. 볼륨(최대 3MB)은 지속 저장 제외 권장.
- GitHub Actions 결제/한도 복구(오너) — 전 PR CI 공통 블로커.