Skip to content

릴리스 성능·보존 예산 — 상한·증가 추세·과부하 회복·관찰 기간·복구 평가법 (이슈 #1284, G04)

한 문장 — v0.18.0 을 Production 에 내보내기 전에 R04 가 대조할 숫자 상한과 판정 규칙이다. 값은 기준선의 실측에 허용 배율을 곱해 정했고 최종 부하 시험 전에 고정한다. 결과를 본 뒤 상한을 넓히는 것은 오너 결정(전역 지침 §26 ③)이며, 이 문서를 고치는 PR 이 그 결정을 인용해야 한다. 아직 재지 않은 처리량은 보장하지 않는다.

  • 이슈: #1284 (계획 ID G04). 코드: src/react/services/appPerformanceBudgets.ts RELEASE_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_dashboardhistory-4y90 ms1.3117 ms홈 패키지. 1년 10.9·10년 18.5 — 4년 값이 가장 컸다(캐시 순서 효과 포함)
읽기 get_calendar_month_summaryhistory-4y35.4 ms1.346 ms월 읽기 모델
읽기 get_calendar_day_summaryhistory-4y10.5 ms1.314 ms하루 요약
읽기 get_session_detailhistory-10y15.1 ms1.320 ms세션 하나
읽기 get_pr_overviewhistory-10y9.5 ms1.312 ms스냅샷 투영
읽기 get_volume_overviewhistory-10y3104 ms1.34035 ms연차 비례로 커지는 유일한 응답(1년 164 → 4년 1219 → 10년 3104, 1.5MB). CI maxMs 1000 은 in-process mock 기준이라 그대로 두되, 실제 서버 값은 4년부터 그것을 넘는다 — D11/U05 개선 대상
읽기 get_profile_feedhistory-1y53.9 ms1.370 mskeyset 20건
읽기 get_session_searchhistory-4y804.4 ms1.31046 mskeyset 120건·480KB — 4년에서 가장 컸다(1년 277·10년 230)
읽기 get_user_manual_records_v1history-4y0.7 ms1.31 ms직접 입력 기록
쓰기 save_session_v5_createmixed-read-write-import30.9 ms1.340 ms저장 문 서버 시간(p95). max 26,465ms 1건은 인입 근사 트랜잭션 중 관측 — 별도 조사(D08)
통계 drainWallMshistory-10y127625 ms1.5191438 ms10년(2,609세션) 일괄 적재 뒤 backlog 소진 벽시계 — worker 한 라운드 127초 = Production statement_timeout(120초)을 넘는 크기
통계 drainPerSessionMshistory-10y49 ms1.574 ms세션당 소진 비용(§3 추세 판정의 분모)
통계 drainWalPerSessionKbhistory-10y155 kb1.5233 kb세션당 WAL(394.7MB / 2,609)
통계 freshnessApplyP50Mshistory-10y847 ms1.31101 ms요청→반영(worker 가 집은 뒤)
브라우저 homeReadyColdMshistory-1y965 ms1.51448 ms빈 캐시·CPU 4배 느리게·미리보기 빌드·로컬 스택에서 탭바가 붙기까지
브라우저 loadEventColdMshistory-1y304 ms1.5456 msNavigation Timing loadEventEnd
브라우저 scriptBytesColdhistory-1y546758 bytes1.1601434 bytes초기 script 전송 바이트(40개)
브라우저 domNodesHomehistory-1y267 nodes1.1294 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 ÷ 전체 본문 bytes3004 (≈3N)7.8412
mirrorFullRewriteFactor옛 형식 미러(전체 배열) 쓰기 bytes ÷ 전체 본문 bytes1005 (≈N)1.856
fullScansPerDrain드레인 한 번의 전체 읽기(getAll) 횟수6004 (≈6N)1540
벽시계(기록만)1000행 드레인152,753 ms6,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_overview17.7 (170ms → 3,002ms)연차 비례 이하 — 10년/1년 ≤ 10연차에 비례해 커지는 유일한 응답(리포트). 비례보다 빠르게 커지면 불합격. 절대 상한은 §2
읽기 p95 — get_session_search≤ 1.2(페이지 고정)≤ 2.0keyset 페이지라 연차 무관해야 함
쓰기 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 소진 시간) 이고 실패 잡 0measure/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. 이 표를 바꾸는 절차

  1. 상한을 낮추는 것(개선 반영)은 담당 PR 이 기준선 재측정 증거와 함께 한다.
  2. 상한을 넓히는 것은 오너 결정이 먼저다 — PR 본문에 결정 문장을 인용하고 §2 표의 "근거" 칸에 적는다.
  3. 어느 쪽이든 RELEASE_PERFORMANCE_BUDGETS 와 이 표를 같은 PR 에서 바꾼다(대조 테스트가 막는다).