릴리스 성능·보존 예산 — 상한·증가 추세·과부하 회복·관찰 기간·복구 평가법 (이슈 #1284, G04)
한 문장 — v0.18.0 을 Production 에 내보내기 전에 R04 가 대조할 숫자 상한과 판정 규칙이다. 값은 기준선의 실측에 허용 배율을 곱해 정했고 최종 부하 시험 전에 고정한다. 결과를 본 뒤 상한을 넓히는 것은 오너 결정(전역 지침 §26 ③)이며, 이 문서를 고치는 PR 이 그 결정을 인용해야 한다. 아직 재지 않은 처리량은 보장하지 않는다.
- 이슈: #1284 (계획 ID G04). 코드:
src/react/services/appPerformanceBudgets.tsRELEASE_PERFORMANCE_BUDGETS(이 표와 같은 값 —tests/react/performanceBudgetsRelease.test.mjs가 대조). 기존 화면 RPC 바이트·행 상한(APP_SCREEN_PERFORMANCE_BUDGETS)은 그대로다. - 누가 쓰나: R04(혼합 부하 합격), U05(번들·DOM), D11(통계 세대), S11(사본·복구), B02(환경), I01(인입). 담당은 자기 항목만 재고 같은 증거 형식(
scripts/performance/evidence/schema.json)으로 낸다.
1. 판정의 뜻
| 판정 | 조건 |
|---|---|
| 합격 | 같은 PC·같은 fixture·같은 절차(기준선 §5)로 두 번 이상 잰 p95 가 상한 이하이고, 증가 추세(§3)·회복(§4)도 만족 |
| 불합격 | 한 항목이라도 상한 초과. 상한을 넓혀 통과시키지 않는다 — 원인을 고치거나 오너 결정으로 정책을 바꾼다 |
| 미측정 | 계측이 없거나 플랫폼이 지원하지 않는 항목. 합격으로 합산하지 않고 표에 "미측정" 으로 남긴다 |
값이 없으면 null(미측정)이고, 0 은 잰 결과다 — 둘을 섞지 않는다(진단 규격 §5).
2. 항목별 상한 (기준선 p95 × 허용 배율)
허용 배율의 뜻: 구조 개선(v0.18.0)이 성능을 나쁘게 만들지 않았는가를 보는 문턱이다. 개선 목표치가 아니다(그것은 D11·U05 가 자기 이슈에서 정한다). 배율은 항목의 측정 잡음을 반영한다 — 서버 시간(잡음 작음) 1.3, 소진·WAL(잡 병합에 좌우) 1.5, 브라우저(CPU 스로틀·타이머 잡음) 1.5, 바이트(결정적) 1.1.
기준 SHA 16b8e996 · 측정 2026-09-07 · 호스트 local-docker(AMD Ryzen 9 5900X · Docker Desktop 24 vCPU/31GB · Postgres 17.6.1.127). 기준선 = 이력 프로필 가운데 가장 큰 p95(그 프로필 이름을 적었다). 단위 ms 는 서버 시계(읽기·쓰기·소진)와 브라우저 시계(브라우저 행)다.
| 항목 | 기준선 프로필 | 기준선 p95 | 배율 | 상한 | 근거 |
|---|---|---|---|---|---|
읽기 get_home_dashboard | history-4y | 90 ms | 1.3 | 117 ms | 홈 패키지. 1년 10.9·10년 18.5 — 4년 값이 가장 컸다(캐시 순서 효과 포함) |
읽기 get_calendar_month_summary | history-4y | 35.4 ms | 1.3 | 46 ms | 월 읽기 모델 |
읽기 get_calendar_day_summary | history-4y | 10.5 ms | 1.3 | 14 ms | 하루 요약 |
읽기 get_session_detail | history-10y | 15.1 ms | 1.3 | 20 ms | 세션 하나 |
읽기 get_pr_overview | history-10y | 9.5 ms | 1.3 | 12 ms | 스냅샷 투영 |
읽기 get_volume_overview | history-10y | 3104 ms | 1.3 | 4035 ms | 연차 비례로 커지는 유일한 응답(1년 164 → 4년 1219 → 10년 3104, 1.5MB). CI maxMs 1000 은 in-process mock 기준이라 그대로 두되, 실제 서버 값은 4년부터 그것을 넘는다 — D11/U05 개선 대상 |
읽기 get_profile_feed | history-1y | 53.9 ms | 1.3 | 70 ms | keyset 20건 |
읽기 get_session_search | history-4y | 804.4 ms | 1.3 | 1046 ms | keyset 120건·480KB — 4년에서 가장 컸다(1년 277·10년 230) |
읽기 get_user_manual_records_v1 | history-4y | 0.7 ms | 1.3 | 1 ms | 직접 입력 기록 |
쓰기 save_session_v5_create | mixed-read-write-import | 30.9 ms | 1.3 | 40 ms | 저장 문 서버 시간(p95). max 26,465ms 1건은 인입 근사 트랜잭션 중 관측 — 별도 조사(D08) |
통계 drainWallMs | history-10y | 127625 ms | 1.5 | 191438 ms | 10년(2,609세션) 일괄 적재 뒤 backlog 소진 벽시계 — worker 한 라운드 127초 = Production statement_timeout(120초)을 넘는 크기 |
통계 drainPerSessionMs | history-10y | 49 ms | 1.5 | 74 ms | 세션당 소진 비용(§3 추세 판정의 분모) |
통계 drainWalPerSessionKb | history-10y | 155 kb | 1.5 | 233 kb | 세션당 WAL(394.7MB / 2,609) |
통계 freshnessApplyP50Ms | history-10y | 847 ms | 1.3 | 1101 ms | 요청→반영(worker 가 집은 뒤) |
브라우저 homeReadyColdMs | history-1y | 965 ms | 1.5 | 1448 ms | 빈 캐시·CPU 4배 느리게·미리보기 빌드·로컬 스택에서 탭바가 붙기까지 |
브라우저 loadEventColdMs | history-1y | 304 ms | 1.5 | 456 ms | Navigation Timing loadEventEnd |
브라우저 scriptBytesCold | history-1y | 546758 bytes | 1.1 | 601434 bytes | 초기 script 전송 바이트(40개) |
브라우저 domNodesHome | history-1y | 267 nodes | 1.1 | 294 nodes | 홈이 그려진 뒤 DOM 노드 수 |
서버 읽기 배율 1.3 · 소진 1.5 · WAL 1.5 · 브라우저(ms) 1.5 · 바이트/노드 1.1 (RELEASE_PERFORMANCE_BUDGETS.allowance). 추세 상한(trend): 연차 무관 화면 2.0 · 볼륨 리포트 10 · 쓰기 1.5.
2-1. 기기 대기열(IndexedDB·미러) 드레인 예산 (S07, 이슈 #1337)
기기 대기열에 쌓인 미전송 기록 N 개를 서버로 보내는 동안 저장소 비용이 N 에 비례해 커지지 않아야 한다(행당 비용이 평평). 실제 Chromium 에서 IDBObjectStore·Storage 호출을 세는 shim 으로 잰다(tests/react/outboxCost.browser.mjs, 행 하나 2,007 bytes). 기준선 = S04 head 2abca52c(2026-09-08). 값은 RELEASE_PERFORMANCE_BUDGETS.outbox 와 같다. 정본 outbox-storage-index-mirror.md §5.
| 항목 | 뜻 | 기준선(N=1000) | S07 측정(N=1000) | 상한 |
|---|---|---|---|---|
payloadReadFactor | 드레인 동안 읽은 행 본문 bytes ÷ 전체 본문 bytes | 3004 (≈3N) | 7.84 | 12 |
mirrorFullRewriteFactor | 옛 형식 미러(전체 배열) 쓰기 bytes ÷ 전체 본문 bytes | 1005 (≈N) | 1.85 | 6 |
fullScansPerDrain | 드레인 한 번의 전체 읽기(getAll) 횟수 | 6004 (≈6N) | 15 | 40 |
| 벽시계(기록만) | 1000행 드레인 | 152,753 ms | 6,077 ms | — (PC·CI 마다 다름) |
판정은 N=100·1000 둘 다에서(N=1 은 상수항이 커 배율이 크다). 상한을 넓히는 것은 §6 절차(오너 결정). 남은 O(N) 항은 uuid 키 스캔(행당 ≈2회)과 옛 배열 묶어 쓰기(드레인당 ≤ 4회)다 — 본문 복사가 아니다.
3. 증가 추세 허용 (기록량이 늘 때 어떻게 커져도 되는가)
기록량은 사용자마다 매년 늘어난다. 항목이 연차에 비례해 커지면 10년 사용자에게서 터진다. 기준선의 1년→10년 비율을 상한으로 고정한다.
| 항목 | 기준선 비율(10년/1년) | 허용 | 뜻 |
|---|---|---|---|
읽기 p95 — get_home_dashboard·get_calendar_*·get_session_detail·get_pr_overview·get_profile_feed | ≤ 1.5 | ≤ 2.0 | 연차와 무관해야 하는 화면(투영·페이지 읽기). 2배를 넘으면 전체 이력을 다시 읽는 경로가 생긴 것 |
읽기 p95 — get_volume_overview | 17.7 (170ms → 3,002ms) | 연차 비례 이하 — 10년/1년 ≤ 10 | 연차에 비례해 커지는 유일한 응답(리포트). 비례보다 빠르게 커지면 불합격. 절대 상한은 §2 |
읽기 p95 — get_session_search | ≤ 1.2(페이지 고정) | ≤ 2.0 | keyset 페이지라 연차 무관해야 함 |
| 쓰기 p95 | ≤ 1.3 | ≤ 1.5 | 저장 문은 세션 하나만 만진다 |
| backlog 소진 시간 / 세션 | 10년 49ms·1년 19ms(세션당) | 세션당 ≤ 기준선 10년 값 × 1.5 | 오늘 append 의 비용이 전체 과거를 다시 쓰는 형태가 아니어야 한다(R04 완료 증거 ②). v0.18.0 목표는 "영향 범위만"(D04) — 그러면 이 값이 뚝 떨어진다 |
| 소진 WAL / 세션 | 10년 138KB·1년 0KB(병합 1잡) | 세션당 ≤ 기준선 10년 값 × 1.5 | 같음 |
4. 과부하 회복 (backlog 가 쌓였을 때)
| 기준 | 값 | 재는 법 |
|---|---|---|
| 회복 | fixture 적재(10년 = 2,609세션 일괄) 뒤 openAfter = 0 이 되기까지 ≤ 상한(§2 소진 시간) 이고 실패 잡 0 | measure/db.mjs recovery |
| 잡 실패 | failed = 0(재시도 뒤). 55000(스냅샷 없음)은 rollover 뒤 재시도로 풀려야 한다 | queue.failed |
| 격리 | 실패 잡이 다른 owner 의 잡을 막지 않는다(owner 10명 프로필에서 10잡 전부 처리) | multi-owner-10 queue.open = 0 |
| 읽기 고갈 없음 | 소진 중에도 읽기 p95 가 §2 상한 이하 | R04 가 혼합 부하에서 동시 측정 |
5. 관찰 기간과 복구(RPO/RTO) 평가 방법
- 관찰 기간: 릴리스 뒤 7일 동안 Production 프로브(
probe/production.mjs, 읽기 전용)를 매일 1회 돌려 큐 대기 p95·stale owner·잡 실패·클라이언트 오류 7일 건수·WAL 증가량을 기준선과 비교한다. 7일 안에 §2 상한을 넘는 날이 이틀 이상이면 불합격(회귀)으로 보고 forward fix(ADR §5). - RPO(잃어도 되는 최대 기간): 현재 사본은 매일 1회(03:40 KST) → 암묵 24시간. 평가 = 사본 워크플로의 마지막 성공 시각과 지금의 차(
user-data-copy아티팩트copied_at_utc). 사본이 존재한다는 사실만으로 RPO 달성으로 쓰지 않는다 — 복원이 되는지(RTO)가 같이 있어야 한다. - RTO(복구에 걸리는 시간): 리허설로만 잰다 — 샌드박스에 최신 사본을
restore_user_fact_v1/pg_restore 로 되살리고 ① 시작→행 복원 완료 ② 통계 재계산 완료(세대 반영)까지 두 시각을 적는다. 첫 리허설(S11/R03) 전까지 RTO 는 미측정이며 이 문서에 숫자를 쓰지 않는다. 리허설 절차·판정은docs/process/rollback.md§8 과 S11 이 정한다. - 완전성: 사본 안의 행 수·checksum 이 Production 카운트와 같은가(S11 이
completeness로 붙인다).
6. 이 표를 바꾸는 절차
- 상한을 낮추는 것(개선 반영)은 담당 PR 이 기준선 재측정 증거와 함께 한다.
- 상한을 넓히는 것은 오너 결정이 먼저다 — PR 본문에 결정 문장을 인용하고 §2 표의 "근거" 칸에 적는다.
- 어느 쪽이든
RELEASE_PERFORMANCE_BUDGETS와 이 표를 같은 PR 에서 바꾼다(대조 테스트가 막는다).