Skip to content

성능·보존 진단 규격 — 열쇠·채널·금지 필드 (이슈 #1284, G04)

한 문장 — 유저 A 의 저장 한 건이 브라우저·기기 대기열·서버 잡·통계 세대·릴리스를 지나는 동안 남기는 흔적을 같은 열쇠로 잇는다. 어느 계측이 어느 열쇠를 싣는지, 무엇을 절대 싣지 않는지를 한 곳에 적는다. 새 계측을 켜지 않고 기존 계측에 열쇠와 모양만 맞춘다(측정 경로 비용 제한).

  • 이슈: #1284 (계획 ID G04). 선행 계약: G03 도메인 계약 §8(owner 세대)·§9(통계 세대)·§13(영수증)·§15(저장 단계). 현행 로그 규칙: 관측·로깅.
  • 코드: src/react/services/performanceEvidenceReport.ts(PERFORMANCE_EVIDENCE_SCHEMA_VERSION = 1, 증거 JSON 조립·위생 검사) · src/react/services/appErrorReporter.ts resolveReleaseIdentity(릴리스 식별).
  • 검사: tests/react/performanceEvidenceReport.test.mjs(열쇠·위생·릴리스 식별).
  • 형제 문서: 기준선(기준 SHA 의 숫자) · 릴리스 예산(상한과 추세).

1. 잇는 열쇠 (evidence key)

열쇠어디서 나오나어느 계측에 실리나
release이 번들·서버 상태를 만든 커밋 SHA(7~40 hex). 주입 전에는 앱 청크 파일 이름(app-XXXX.js)빌드가 globalThis.__BARBELIC_RELEASE__ 로 주입(§2)오류 이벤트 release, 증거 JSON release, 서버 프로브 release(HEAD SHA)
operationId한 작업의 식별자(rpc:<이름>:…)barbelicRepository.createOperationIdRPC 디버그 행, 오류 이벤트 operation_id
clientMutationId쓰기 명령의 멱등 키(uuid) — 계약 §3앱(durableMutationIdentity)대기열 행, 영수증 client_mutation_id, 쓰기 실패 오류 이벤트의 operation_id(규칙 §4)
jobId통계 잡 id(uuid)서버 user_exercise_stats_refresh_jobs.id잡 행, 관측 뷰 job_id
generation통계 세대 {requested, applied}계약 §9영수증 stats_requested_version / 읽기 모델 freshness영수증, 읽기 모델, user_stats_refresh_state
ownerHash사용자 id 의 SHA-256 앞 16 hex — 원문 id 대신서버 프로브가 계산(encode(digest(user_id::text,'sha256'),'hex'))서버 프로브 집계행. 브라우저 증거는 비운다(사용자 한 명의 기기이므로 필요 없다)
at절대 시각(ISO, Z/오프셋) — 계약 §4각 계측의 시계전부

한 저장 건의 사슬: clientMutationId → 영수증(committed_at, stats_requested_version) → 잡(source_ref·target_version) → user_stats_refresh_state.applied_version/applied_at → 화면 읽기 모델 freshness. 이 사슬로 "저장 → 서버 확정 → 통계 반영" 세 시각을 잇는다(계약 §15).

2. 릴리스 식별

  • 정본 = 커밋 SHA. 빌드가 globalThis.__BARBELIC_RELEASE__ = "<sha>" 를 주입하면 resolveReleaseIdentity 가 그것을 쓴다(소문자 7~40 hex 만 인정). 주입은 vite.config.mjsdefine 한 줄(VERCEL_GIT_COMMIT_SHAGITHUB_SHA"dev")이며 HQ 통합 슬롯(B02) 이 넣는다 — G04 갱신안 항목.
  • 주입 전에는 종전대로 앱 청크 파일 이름이다(2026-09-07 실측: 30일 동안 58종). 청크 이름과 SHA 의 대응은 Vercel 배포 기록에서만 알 수 있으므로 비교 자료로 쓰지 않는다.
  • 서버 쪽 릴리스 = Production 프로브가 기록하는 git rev-parse HEAD(main) + deploymentManifest.EXPECTED_LATEST_MIGRATION. 앱과 서버는 릴리스 PR 로 함께 나가므로 같은 SHA 다.

3. 금지 필드 (telemetry 위생)

증거·오류 이벤트·프로브 집계 어디에도 기록 본문·메모·제목·인입 원문·인증 토큰·이메일·사용자 id 원문 을 싣지 않는다. 검사는 필드 이름으로 한다(값을 보고 판단하지 않는다 — 값이 비어 있어도 이름이 걸리면 실패).

EVIDENCE_FORBIDDEN_FIELD_NAMES(정규화: 대소문자·_·- 무시): note(s)·memo·title·review·text·body·comment·message / payload·raw(Payload)·request·response·params·rows·record·session·exercises·sets / token·accessToken·refreshToken·authorization·apiKey·secret·password·cookie·jwt / email·phone·displayName·handle·avatarUrl·userId·user·owner.

현행 오류 채널 record_client_error_event 는 allowlist 21키에 sanitizeDiagnosticText(토큰·이메일·제목 치환)를 이미 갖췄다 — 이 규격은 그것을 바꾸지 않고, 증거 JSON 과 프로브 출력에 같은 기준을 적용한다. readModel 증거의 keyResourceKey 문자열의 종류 조각만 남긴다(owner id 를 담을 수 있으므로).

4. 채널 대응표 — 지금 있는 계측이 무엇을 싣고, 규격이 무엇을 더하나

채널어디에 남나지금 싣는 것규격이 정하는 것담당
RPC 디버그(recordScreenRpcDebug)sessionStorage["barbelic-rpc-debug-log"]·globalThis.__liftGuildRpcDebug(120건)at·event·operationName·rpcName·durationMs·responseBytes·status·errorCode·operationId·paramKeys증거 JSON rpc[] 로 내보낸다(paramKeys 제외). 서버 업로드 없음G04(내보내기)
read-model 메트릭(recordReadModelMetric)메모리 200건kind·scope·key·durationMs증거 readModel[](key 는 종류만)G04
Navigation/Resource Timing브라우저 API표준 구간증거 navigation(dns·connect·request·response·domInteractive·DCL·load·transferBytes·cold/warm)·resources(script/style/font/other 개수·바이트·가장 느린 script)·dom.nodeCountG04
오류 이벤트(record_client_error_event)client_error_events(90일)allowlist 21키, release·operation_id·duration_ms, kind 에 perf_tab_switchrelease 는 §2 resolver. 쓰기 실패(sync_failure·rpc_failure of save_session_v5/delete_session_v5)의 operation_idclientMutationId — 대기열 행·영수증과 잇기 위해. 새 성능 kind 추가는 SQL CHECK 변경(마이그레이션)규칙 = G04 · 적용 = S05(전송기)·S11 · kind 추가 = D 계열
대기열 행IndexedDB pendingSavesclientMutationId·savedAt·state·sentAt큐 age = now − savedAt(queued)·전송 중 age = now − sentAt. 기기에서 집계해 증거에 싣는 것은 S05/S11(지금 미측정)S05·S11
영수증workout_mutation_receiptsclient_mutation_id·committed_at·stats_requested_version·server_revision서버 확정 시각의 정본. 클라 전송 시각은 서버에 없다 — "요청 → 확정" 은 기기 증거(sentAt)와 영수증(committed_at)을 clientMutationId 로 이어 계산G04(프로브)
잡 행·관측 뷰user_exercise_stats_refresh_jobs·…_observabilitycreated_at·metadata.processing_started_at·completed_at·duration_ms(파생)큐 age = processing_started_at − created_at, 처리 = completed_at − processing_started_at. 결함: 두 시각이 같은 트랜잭션 now() 라 처리 시간이 항상 0 — clock_timestamp() 로 바꾸는 것은 D08D08
세대 상태user_stats_refresh_staterequested_at·applied_at·두 버전freshness 지연 = applied_at − requested_at, stale = requested > appliedG04(프로브)·D11
cron 이력cron.job_run_detailsstart_time·end_time·status잡별 실행 시간 분포·5초 초과 횟수. 보존 정리 없음(7/20~ 127k행) — 정리는 D08G04(프로브)
Edge 함수consoleduration_ms로그 전용(Supabase 로그 API). 증거로 모으지 않는다(호출 빈도 낮음)
DB 통계pg_stat_wal·pg_stat_user_tables·pg_stat_database·extensions.pg_stat_statements누적값프로브가 두 시점의 차로 WAL·행·캐시 적중률을 낸다. pg_stat_statements 는 PostgREST 래핑 때문에 RPC 이름이 안 보인다 — 함수별 시간은 track_functions = pl(DB 설정, D08/HQ) 뒤에만G04(프로브)·D08

5. 증거 JSON (schema v1)

PerformanceEvidenceReport {
  schemaVersion: 1, release, collectedAt, userAgent,
  rpc[]:       { kind:"rpc", release, at, operationId, event, rpcName, durationMs, responseBytes, status, errorCode }
  readModel[]: { kind:"read_model", release, at, metric, scope, key(종류만), durationMs }
  navigation:  { kind:"navigation", dnsMs, connectMs, requestMs, responseMs, domInteractiveMs, domContentLoadedMs, loadEventMs, transferBytes, cacheState } | null
  resources:   { kind:"resources", scriptCount, scriptBytes, styleCount, styleBytes, fontCount, fontBytes, otherCount, otherBytes, slowestScriptMs } | null
  dom:         { kind:"dom", nodeCount } | null
}
  • 값이 없으면 null(미측정). 0 은 "잰 결과 0" 이다 — 둘을 섞지 않는다(완료 조건: 누락 계측은 미측정으로 보고).
  • buildPerformanceEvidence 는 조립 뒤 assertEvidenceHygiene 를 통과해야 결과를 돌려준다. 통과 못 하면 증거를 만들지 않는다.
  • 서버 프로브 출력(scripts/performance/probe)은 같은 열쇠 이름과 같은 금지 목록을 쓴다(schema.json, Phase 3).

6. 측정 경로 자체의 비용

  • 브라우저: 새 타이머·observer 를 켜지 않는다. collectPerformanceEvidence 는 러너가 부를 때 한 번 읽는다. perf_tab_switch 프로브의 기존 상한(세션당 20건·5초 간격)은 그대로.
  • 서버: 프로브는 읽기 전용·집계 쿼리만, Production 은 Management API read_only: true. 매분 cron 이 이미 쓰는 비용 위에 새 쓰기를 더하지 않는다.
  • 저장: 증거 JSON 은 파일(scripts/performance/evidence/<sha>/…)이며 저장소에는 기준 SHA 결과만 문서 표로 남긴다.