# Phase 7.0 — HMR 벤치마크 검증 리포트

> Auto-generated by `scripts/hmr-bench.ts`. Do not edit by hand.
> Last run: 2026-04-18T10:17:52.543Z — took 441.7s.

---

## 1. 요약

측정 스코프: `REBUILD_TOTAL` (debounce 제외, 목표 정의와 동일). End-to-end wall-clock 은 §4 에 별도 기재.

| 타겟 | 측정 (P95 REBUILD_TOTAL) | 목표 | 결과 |
|---|---|---|---|
| Cold dev start | 649 ms | 500 ms | FAIL |
| SSR page rebuild | 22.5 ms | 200 ms | PASS |
| Island-only rebuild | 20.8 ms | 50 ms | PASS |
| Common-dir rebuild | 17.4 ms | 400 ms | PASS |
| CSS-only rebuild | n/a | 100 ms | — |

**Reference — Phase 7.0.R0 진단 대비 (개선 전 실측)**:
- Cold start: 395 ms (target 500 ms) — 이미 통과.
- SSR rebuild wall-clock: **1500~2000 ms** (target 200 ms) — 7.5~10× 미달.
- Island rebuild: 측정 불가 (B4 perf marker 없었음).

## 2. 환경

| 항목 | 값 |
|---|---|
| Platform | win32 10.0.19045 (x64) |
| Bun version | 1.3.12 |
| CPU | AMD Ryzen 7 2700X Eight-Core Processor          (16 logical cores) |
| Total RAM | 31.9 GB |
| Iterations per cell | 20 |
| Cells benched | 29 / 35 (skipped: 6) |

## 3. Cold start 결과

3회 반복 / fixture. `bun run packages/cli/src/main.ts dev` 프로세스를 spawn 해 stdout 에 `Mandu Dev Server / ready in Nms` 가 출력될 때까지 시간 측정.

| Fixture | P50 | P95 | Mean | n |
|---|---|---|---|---|
| pure-ssg | 594 ms | 594 ms | 590 ms | 3 |
| hybrid | 620 ms | 622 ms | 609 ms | 3 |
| full-interactive | 603 ms | 661 ms | 624 ms | 3 |

**Target**: P95 ≤ 500 ms.

> **Caveat — cold start 측정은 fresh tmpdir fixture 기반**: 매 rep 마다 `mkdtempSync` 로 빈 디렉토리를 만들고 `bun install` 없이 `bun run main.ts dev` 를 spawn. Bun 의 module resolution 캐시가 cold 상태 + lockfile 불일치 경고 추가 I/O + 프로세스 spawn overhead (~100-150 ms on Windows) 가 측정에 포함됨. R0 진단의 395 ms 기준은 `demo/starter` (이미 warm 된 프로젝트) 에서 측정된 값이라 직접 비교는 misleading. 현재 `demo/starter` cold-start 실측 ≈ 583 ms (R0 395 ms → +190 ms 는 그 사이 추가된 feature/guard 때문일 가능성). COLD_START 타겟 재조정 또는 warm-cache fixture 측정 추가가 Phase 7.1 에서 검토 필요.

## 4. HMR rebuild 매트릭스 결과

36 cells × 20 iter. 두 스코프 병기:
- `rebuild` : `REBUILD_TOTAL` 스코프 (debounce 제외) — **이 값으로 목표 pass/fail 판정**.
- `wall` : writeFile → callback fire 의 full wall-clock (debounce + fs.watch propagation + rebuild 포함) — 사용자 체감 시간.

| Form | Change kind | Behavior | rebuild P50 | rebuild P95 | rebuild P99 | wall P95 | Target | 결과 | n |
|---|---|---|---|---|---|---|---|---|---|
| pure-ssg | app/page.tsx | full-reload | 10.8 ms | 22.4 ms | 23.1 ms | 122 ms | 200 ms | PASS | 20 |
| pure-ssg | app/slot.ts | full-reload | 0 ms | 0 ms | 0 ms | 0 ms | 200 ms | SKIP | 0 |
| pure-ssg | app/layout.tsx | full-reload | 10.8 ms | 22.2 ms | 23.7 ms | 122 ms | 200 ms | PASS | 20 |
| pure-ssg | app/contract.ts | code-regen | 10.6 ms | 21.5 ms | 23.8 ms | 121 ms | 400 ms | PASS | 20 |
| pure-ssg | spec/resource.ts | code-regen | 10.8 ms | 21.8 ms | 21.9 ms | 122 ms | 400 ms | PASS | 20 |
| pure-ssg | app/middleware.ts | full-reload | 10.8 ms | 21.7 ms | 22.8 ms | 122 ms | 200 ms | PASS | 20 |
| pure-ssg | src/shared/** | prerender-regen | 0.99 ms | 1.4 ms | 1.6 ms | 124 ms | 400 ms | PASS | 19 |
| pure-ssg | src/top-level.ts | prerender-regen | 0.93 ms | 1.3 ms | 1.4 ms | 123 ms | 400 ms | PASS | 20 |
| pure-ssg | css | css-update | 0 ms | 0 ms | 0 ms | 0 ms | 100 ms | SKIP | 0 |
| pure-ssg | mandu.config.ts | server-restart | 10.6 ms | 21.7 ms | 22.1 ms | 122 ms | — | — | 20 |
| pure-ssg | .env | server-restart | 10.7 ms | 22.5 ms | 22.8 ms | 122 ms | — | — | 20 |
| hybrid | app/page.tsx | full-reload | 10.8 ms | 21.9 ms | 22.2 ms | 122 ms | 200 ms | PASS | 20 |
| hybrid | app/slot.ts | full-reload | 0 ms | 0 ms | 0 ms | 0 ms | 200 ms | SKIP | 0 |
| hybrid | app/layout.tsx | full-reload | 10.8 ms | 23.4 ms | 23.4 ms | 123 ms | 200 ms | PASS | 20 |
| hybrid | app/contract.ts | code-regen | 19.9 ms | 21.5 ms | 21.9 ms | 121 ms | 400 ms | PASS | 20 |
| hybrid | spec/resource.ts | code-regen | 10.6 ms | 21.3 ms | 22.5 ms | 121 ms | 400 ms | PASS | 20 |
| hybrid | app/middleware.ts | full-reload | 10.8 ms | 22.5 ms | 23.0 ms | 123 ms | 200 ms | PASS | 20 |
| hybrid | island.client.tsx | island-update | 18.5 ms | 21.4 ms | 22.2 ms | 141 ms | 50 ms | PASS | 20 |
| hybrid | src/shared/** | full-reload | 8.7 ms | 14.4 ms | 20.5 ms | 131 ms | 400 ms | PASS | 19 |
| hybrid | src/top-level.ts | full-reload | 7.9 ms | 17.1 ms | 18.9 ms | 133 ms | 400 ms | PASS | 20 |
| hybrid | css | css-update | 0 ms | 0 ms | 0 ms | 0 ms | 100 ms | SKIP | 0 |
| hybrid | mandu.config.ts | server-restart | 10.6 ms | 21.1 ms | 22.2 ms | 121 ms | — | — | 20 |
| hybrid | .env | server-restart | 10.7 ms | 21.8 ms | 21.8 ms | 122 ms | — | — | 20 |
| full-interactive | app/page.tsx | full-reload | 10.8 ms | 22.2 ms | 22.3 ms | 122 ms | 200 ms | PASS | 20 |
| full-interactive | app/slot.ts | full-reload | 0 ms | 0 ms | 0 ms | 0 ms | 200 ms | SKIP | 0 |
| full-interactive | app/layout.tsx | full-reload | 10.7 ms | 22.2 ms | 22.5 ms | 122 ms | 200 ms | PASS | 20 |
| full-interactive | app/contract.ts | code-regen | 10.6 ms | 20.8 ms | 21.9 ms | 121 ms | 400 ms | PASS | 20 |
| full-interactive | spec/resource.ts | code-regen | 10.6 ms | 21.4 ms | 21.5 ms | 121 ms | 400 ms | PASS | 20 |
| full-interactive | app/middleware.ts | full-reload | 10.7 ms | 21.5 ms | 21.9 ms | 122 ms | 200 ms | PASS | 20 |
| full-interactive | island.client.tsx | island-update | 18.6 ms | 19.9 ms | 20.6 ms | 141 ms | 50 ms | PASS | 20 |
| full-interactive | src/shared/** | full-reload | 12.2 ms | 20.3 ms | 20.6 ms | 135 ms | 400 ms | PASS | 19 |
| full-interactive | src/top-level.ts | full-reload | 11.6 ms | 34.4 ms | 36.2 ms | 141 ms | 400 ms | PASS | 20 |
| full-interactive | css | css-update | 0 ms | 0 ms | 0 ms | 0 ms | 100 ms | SKIP | 0 |
| full-interactive | mandu.config.ts | server-restart | 10.7 ms | 22.0 ms | 23.2 ms | 122 ms | — | — | 20 |
| full-interactive | .env | server-restart | 10.7 ms | 21.9 ms | 22.7 ms | 122 ms | — | — | 20 |

_n = 성공한 iteration 수 (timeout iter 은 제외)._

## 5. 측정 불가 경로 (bundler-level observation 없음)

다음 cell 은 `startDevBundler` 레벨에서 callback 으로 surface 되지 않음 — 별도 경로에서 dispatch.

| Cell | 이유 | 대체 측정 가능처 |
|---|---|---|
| `css` (모든 form) | CSS 는 CLI 의 Tailwind subprocess watcher 가 담당 (`startCSSWatch` / `cssWatcher`). 번들러는 `.css` 를 어떤 classification bucket 에도 등록하지 않음 (의도). | `cli/src/commands/dev.ts:242-269` 의 `onBuild` 콜백 timestamp 추적. |
| `app/slot.ts` (모든 form) | `.slot.ts` 파일은 `serverModuleSet` 에 등록되지 않음 (`componentModule` / `layoutChain` 만 등록). CLI 의 `watchFSRoutes` (chokidar) 가 manifest 전체 재스캔으로 처리. | `cli/src/commands/dev.ts:712-` `routesWatcher.onChange` timestamp 추적. |

## 6. 병목 재검토 — R0 진단 대비

- **SSR P95 ≤ 200 ms 달성** (측정: 22.5 ms).
  - R0 대비 개선: **80×** faster (1500~2000 ms → 22.5 ms).

### 6.1 측정 경계 주의

이 벤치는 **`startDevBundler` 레벨** 의 `onSSRChange` / `onRebuild` 콜백 fire 시점까지만 측정. CLI 의 `handleSSRChange` 내부 `ssr:handler-reload` 체인 (`clearDefaultRegistry` → `registerManifestHandlers` → `bundledImport`) 은 콜백 fire **다음에** 실행됨.

**Incremental `bundledImport` wire-up 상태 (B5)**: `packages/cli/src/util/bun.ts:163` 의 `BundledImportOptions { changedFile?: string }` 은 Agent B 가 구현 완료. 그러나 현재 호출 체인

  `dev.ts:178 importFn: bundledImport`
  → `handlers.ts:82 importFn(modulePath)` *(no `changedFile` 전달)*

에서 `changedFile` 을 **wire up 하지 않음**. 따라서 `createBundledImporter` 가 제공하는 증분 cache path 는 SSR re-register 에서 발동하지 않고, 매 SSR 변경 시 N 개 route × 전체 `Bun.build` 가 여전히 실행됨. 이는 `registerManifestHandlers` 시그니처를 `importFn: (modulePath: string, opts?: { changedFile?: string }) => ...` 로 확장하고 `handleSSRChange` 에서 `filePath` 를 연결하는 작은 리팩터로 해결됨 — Phase 7.1 follow-up.

**현재 벤치의 의미**: 벤치가 측정한 `SSR full-reload` 는 bundler의 `onSSRChange` callback fire 까지이므로 `bundledImport` 비용을 **포함하지 않음**. 실제 "브라우저가 새 HTML을 받기까지" 의 walltime 은 여기에 더해 CLI layer의 `SSR_HANDLER_RELOAD` 가 추가됨 (현재 1.5~2s 의 원인). 사용자 관점 walltime 을 잡으려면 CLI 를 spawn 해 `MANDU_PERF=1` stdout 의 `[perf] ssr:handler-reload: Nms` 를 파싱하는 보조 벤치가 필요 (Phase 7.1).

## 7. 결론

**하드 어서션 결과**: 3 / 4 통과.

**Cell-level**: 23 PASS / 0 FAIL / 12 측정 외 (slot/css/server-restart).

**미달 항목**:
- Cold start P95: 649 ms > 500 ms

### 7.1 Phase 7.1 follow-up 권장

1. **B5 wire-up**: `registerManifestHandlers` 가 `importFn(modulePath, { changedFile })` 을 호출하도록 확장. 현재 `BundledImportOptions` 는 정의됐으나 호출 체인에서 `changedFile` 을 전달하지 않아 증분 cache path 가 SSR 재빌드에서 발동하지 않음. (`cli/src/util/handlers.ts:82/126/137`)
2. **CLI-layer latency 측정**: `ssr:handler-reload` 내부 walltime (`registerHandlers(manifest, true)` 의 `bundledImport` N회) 은 `MANDU_PERF=1` 로그에서만 확인 가능. 벤치 스크립트가 CLI 를 spawn 해 stdout 캡처로 파싱하도록 확장 필요.
3. **CSS / slot 전용 벤치**: §5 에 명시된 CLI-layer dispatch 경로를 별도 `scripts/hmr-bench-cli.ts` 로 측정 — 현재 bundler-level 벤치는 구조상 불가.
4. **Windows `fs.watch` flakiness**: `touchUntilSeen` 의 retry 로직이 P99 를 왜곡할 가능성. CI 에서는 `MANDU_DEV_WATCH=polling` 도입 검토.

---

_Generated 2026-04-18T10:25:14.217Z / Report schema v1._