부팅 즉시 그리기 — 재부팅 간 지속 캐시(PR·일지 이번달·그룹) (2026-08-31)
- 기간: 2026-08-31 (세션 1, #1013 D2 "둘다 해줘"의 후속 분리 트랙 → 오너 go "홈까지 포함해서 go. 그리고 이거 update에 기록 남겨줘")
- 랜딩: PR #1018(
0c06eac4) — 마이그레이션 없음, Vercel 자동 배포, Actions 결제 블로커로 admin 머지 - 설계서: 이슈 #1016 본문(Phase 계획 + 예상 효과)
- 정본:
src/react/services/bootReadModelCache.ts(저장소) · 시드 3지점 =remoteDataController.ts(PR)·calendarReadModelStore.ts(일지)·groupStore.ts(그룹) - 도구: 없음(코드 트랙 — 크기·시점 실측은 #1013에서 확보)
- 게이트:
tests/react/bootReadModelCache.test.mjs(기능 4 + 배선 계약 1) - 버그리포트: 없음(성능 트랙)
- 계약: 원문 저장 + 화면 어댑터 재검증(계약 버전 불일치·오염 = 자동 축출) — 카탈로그 캐시 패턴의 일반화
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | 원문 캐시 저장소(카탈로그 캐시 일반화) | ✅ PR #1018 |
| Phase 2 | PR 오버뷰 재수화(셸 스냅샷 단계 편입) | ✅ PR #1018 |
| Phase 3 | 일지 이번달·그룹 재수화 | ✅ PR #1018 |
| Phase 4 | 검증(계약 테스트·전체 회귀)·기록 | ✅ 2,251건 통과 + 이 문서 |
1. 배경
재부팅 간 지속 캐시는 홈 대시보드 스냅샷(IndexedDB, 14일)과 종목 카탈로그(localStorage, 버전 대조)뿐이었다. 홈 화면의 PR 섹션(레벨카드·주요 종목 보드·전광판)은 서버에 적립돼 있어 조회는 44ms지만 기기에 안 남아 매 부팅 287kB를 다시 내려받는 동안 쉬머였고 — 오너가 물은 "메인페이지 부팅이 꽤 느린데"의 몸통 — 일지·그룹은 #1013 워밍이 부팅 직후 선수신하지만 워밍이 끝나기 전 몇 초는 여전히 콜드였다.
2. 문제 제기
"켜자마자 직전 데이터로 그리고 뒤에서 갱신"이 세 화면에만 없었다
홈 대시보드는 이미 이 문법(스냅샷 즉시 표시 → 프리페치 도착 시 교체)으로 동작한다. PR 섹션·일지·그룹만 예외라서, 같은 부팅 안에서 어떤 부분은 즉시 뜨고 어떤 부분은 쉬머로 남는 비대칭이 체감 저하의 원인이었다.
3. 해결 방안
원칙 (오너 결정, 2026-08-31)
- D2(#1013) "둘다 해줘" — 워밍과 별개로 재부팅 간 지속 캐시도 확장.
- "홈까지 포함해서 go" — PR 오버뷰(홈 부팅 체감 몸통) 포함 확정 + 작업 기록 필수.
접근
- 홈 스냅샷(1,401줄 엄격 화이트리스트 투영)을 넓히는 대신 카탈로그 캐시의 검증된 패턴을 일반화: 서버 원문 페이로드를 owner+슬롯 키로 저장하고, 다음 부팅에 같은 화면 어댑터로 재검증해 시드. 검증기를 새로 만들지 않아 계약이 한 벌로 유지되고, 계약 버전이 오르면 옛 캐시는 어댑터 검증 실패로 자동 축출된다.
- 신선도 판단은 캐시가 하지 않는다 — 시드는 항상 낡은 것 취급이고, 갱신은 기존 부팅 프리페치·#1013 워밍·각 스토어 TTL이 그대로 담당(새 규칙 발명 없음).
- 리포트 볼륨은 제외 — 응답 최대 3MB로 기기 저장 부적합, #1013 워밍이 담당.
4. 적용한 내용
Phase 1 — 원문 캐시 저장소
bootReadModelCache.ts: 슬롯 3종(pr_overview·calendar_month·groups), 쓰기는 유휴 배치(큰 직렬화가 화면 그리기를 막지 않게), 깨진 JSON은 읽기에서 축출, 오너 전환·로그아웃 시 카탈로그 캐시와 같은 2지점에서 전 슬롯 소거. 저장 훅은 callScreenRpc의 bootCacheSlot 옵션 — 어댑터 검증을 통과한 성공 응답만 저장된다.
Phase 2 — PR 오버뷰 재수화 (핵심 위험 구간)
PR 데이터는 "셸 적용 후"로 순서가 고정돼 있다(셸 적용이 PR_OVERVIEW를 보존하는 기존 규칙). 시드를 셸 스냅샷 재수화 함수의 진입부에 편입해 이 순서를 그대로 재사용: 캐시로 먼저 그려도 셸 적용이 지우지 않고, 부팅 병렬 프리페치가 도착하면 신선본+freshness로 덮는다. 시드는 로드 래치·freshness를 건드리지 않아 갱신 경로가 불변이다.
Phase 3 — 일지 이번달·그룹 재수화
- 일지: owner 채택 시 캐시 원문을 어댑터로 변환해 월 캐시에 넣고 즉시 stale 마킹 — 화면은 바로 그려지고 로드는 background 갱신으로 진행(월 캐시의 기존 stale-표시-후-갱신 문법 재사용). 오늘이 포함된 달의 응답만 저장(과거 달 열람이 캐시를 오염시키지 않게).
- 그룹: owner 채택 시 목록을 ready로 시드하되 로드 래치는 그대로 둬서 탭 진입·워밍의 신선본 refresh가 종전대로 발사돼 덮는다.
주요 결정과 그 근거
- 원문 저장 + 읽을 때 재검증(카탈로그 패턴) 채택 — 화이트리스트 투영 신설 대비 코드가 작고, 서버 계약 변경 시 캐시가 화면을 깨뜨릴 수 없다(검증 실패=축출=콜드 부팅일 뿐).
- 시드가 래치·신선도에 손대지 않는 것을 불변식으로 — 캐시는 "먼저 그리는 것"만 하고 "갱신을 막는 것"은 절대 못 하게. 그룹 테스트가 이 불변식(시드 후에도 refresh 1회 발사)을 계약으로 잠근다.
작업 중 드러난 것
mergeAppShellWithPreservedLazyData의 PR_OVERVIEW 보존 규칙 덕에 Phase 2가 "진입부 한 줄 시드"로 끝났다 — 셸 적용 순서를 바꿀 필요가 없었다.
5. 적용 결과
| 항목 | 결과 |
|---|---|
| 홈 PR 섹션 부팅 | 매 부팅 287kB 수신 대기(쉬머) → 직전 데이터 즉시 + 뒤에서 갱신 |
| 일지·그룹 콜드 구간 | 부팅~워밍 완료 사이 콜드 → 직전 데이터 즉시 |
| 기기 저장 | +약 330kB/owner (볼륨 3MB는 제외) |
| 테스트 | 전체 2,251건 통과(신규 5) · tsc·경계·unused·manifest·utf8·dead-css 통과 |
| eslint·CI | 미검증 — Actions 결제 블로커 지속(오너 선례 지시 admin 머지) |
| 실기기 체감 | 시각 정보 항목(§14) — 캐시 실패 시 기존 콜드 경로 그대로라 기능 위험 없음 |
6. 이번 개선으로 향상된 것
부팅이 "받아둔 것으로 즉시 그리는" 동작이 됐다
홈 대시보드만 갖고 있던 "직전 데이터 즉시 + 배경 갱신" 문법이 PR 섹션·일지·그룹까지 확장돼, 재부팅 직후 화면 전반이 한 문법으로 즉시 반응한다. 구조적으로 남는 것: 원문 저장+어댑터 재검증 캐시 계층(슬롯 추가만으로 확장 가능) + 시드-래치 불변식 계약 테스트.
남은 것
- 낡은 데이터 노출 창(부팅~갱신 사이 직전 세션 기준 값) — 홈 대시보드와 동일 문법으로 오너 승인된 트레이드오프. 별도 조치 없음.
- GitHub Actions 결제/한도 복구(오너) — 전 PR CI 공통 블로커, 복구 시 첫 CI가 누적 eslint를 자연 판정.