Performance(성능)
사용자가 핵심 콘텐츠를 보고 입력·클릭의 결과를 확인하기까지 걸리는 시간이다.
LCP, INP, route transition time, 긴 main thread 작업분류: Layer 13 - Product Engineering & Growth Systems
프론트엔드 품질은 “깔끔한 UI”나 테스트 통과만을 뜻하지 않는다. 사용자가 실제 기기와 네트워크에서 콘텐츠를 제때 보고, 의도한 조작의 결과를 빠르게 확인하고, 오류에서 회복하며, 접근성 장벽 없이 핵심 행동을 완료할 수 있는 정도다. 이 품질이 activation(활성화), conversion(전환), retention(유지) 같은 제품 지표를 지탱하는지까지 설명해야 제품 품질이 된다.
RUM(Real User Monitoring, 실제 사용자 모니터링) 은 실제 방문자의 브라우저에서 성능, 오류, 상호작용 같은 기술 신호를 수집해 운영 환경의 경험 분포를 관찰하는 방식이다. 반면 synthetic monitoring(합성 모니터링) 은 통제한 기기·네트워크·스크립트로 같은 시나리오를 반복 실행해 회귀와 가용성을 확인하는 방식이다. 둘은 경쟁 도구가 아니라 서로 다른 질문에 답한다.
Core Web Vitals(핵심 웹 지표) 는 Google이 실제 사용자 경험의 로딩, 반응성, 시각적 안정성을 나타내기 위해 고른 세 지표다. 현재 구성은 LCP(Largest Contentful Paint), INP(Interaction to Next Paint), CLS(Cumulative Layout Shift)다. 이때 INP는 버튼 하나의 시간이 아니라 한 페이지 방문 동안 발생한 click·tap·keyboard interaction의 지연을 관찰해, 이상치를 제한한 대표 최장 지연을 그 방문의 값으로 삼는 CWV다. field data(현장 데이터) 는 실제 사용자 환경에서 모은 값이고, lab data(실험실 데이터) 는 재현 가능한 통제 환경에서 측정한 값이다. RUM은 field data의 한 종류이고 synthetic monitoring은 주로 lab data를 만든다.
측정 도구를 고를 때는 “어느 도구가 더 정확한가”보다 “어떤 불확실성을 줄이는가”를 먼저 묻는다.
| 방식 | 관찰 대상 | 강점 | 경계 |
|---|---|---|---|
| RUM | 실제 방문의 성능·오류·브라우저 상태 | 기기, 네트워크, route, 사용자 흐름의 긴 꼬리를 본다 | 사용자 구성과 계측 정책에 편향되며 재현이 어렵다 |
| Product analytics | 제품 행동과 상태 전이 | funnel과 제품 outcome을 정의한다 | 느림이나 오류의 브라우저 원인을 직접 설명하지 않는다 |
| Synthetic monitoring | 정해진 위치·환경·시나리오 | 변경 전후를 반복 가능하게 비교하고 장애를 빠르게 감지한다 | 실제 사용자 분포와 예상하지 못한 행동을 대표하지 않는다 |
| 수동·보조 기술 검사 | 키보드, 스크린 리더, 확대 등 실제 사용 가능성 | 의미, 초점, 안내 문구처럼 자동 신호가 놓치는 장벽을 발견한다 | 지속적인 전체 트래픽 분포를 만들지는 않는다 |
예를 들어 synthetic checkout은 매 5분마다 정상 결제가 가능한지 알려 준다. RUM은 같은 checkout page visit의 INP 분포와 특정 제출 조작의 custom latency가 저사양 모바일에서 길어지는지 보여 준다. Analytics는 실제 결제 완료율이 변했는지 보여 준다. 접근성 검사는 키보드만으로 오류 필드에 도달하고 메시지를 이해할 수 있는지 확인한다. 어느 하나만으로 나머지 질문에 답할 수 없다.
로컬 개발 환경과 CI(Continuous Integration, 지속적 통합)의 브라우저는 대개 안정적인 CPU, 네트워크, 빈 캐시, 준비된 테스트 계정을 사용한다. 실제 사용자는 오래된 기기, 데이터 절약 모드, 브라우저 확장, 큰 계정 데이터, 여러 탭, 불안정한 무선망을 사용한다. 로그인 상태와 캐시 상태도 다르다. 같은 코드가 이 환경 조합에서 전혀 다른 경험을 만든다.
lab data는 이 다양성을 제거하기 때문에 원인을 좁히기 좋다. 그러나 바로 그 통제가 실제 분포를 숨긴다. field data는 다양성을 보존해 “누가 실제로 느린가”를 보여 주지만, 동시에 여러 요인이 섞여 원인을 단정하기 어렵다. RUM의 철학은 모든 사용자를 한 숫자로 환원하는 것이 아니라 실제 경험의 분포와 맥락을 남겨 다음 재현 실험을 고르는 것이다.
synthetic/lab: 재현 가능한 조건에서 회귀를 발견한다 ↓RUM/field: 실제 분포와 영향을 받는 segment를 찾는다 ↓analytics: 같은 시점의 제품 행동 변화를 확인한다 ↓실험·재현: 원인 가설을 검증하고 rollout 결정을 바꾼다이 피드백 루프에서 RUM은 판결문이 아니라 관찰 증거다. 느린 사용자가 이탈했다는 상관관계는 중요하지만, 느림이 이탈을 일으켰다는 인과관계까지 자동으로 증명하지는 않는다.
사용자가 핵심 콘텐츠를 보고 입력·클릭의 결과를 확인하기까지 걸리는 시간이다.
LCP, INP, route transition time, 긴 main thread 작업오류, 중복 처리, 데이터 손실 없이 핵심 행동을 완료할 수 있는 정도다.
JS error, API error, failed submit, duplicate submit장애, 입력 방식, 감각 조건과 관계없이 정보와 기능을 인지하고 조작할 수 있는 정도다.
semantic HTML, keyboard, focus, name·role·value, contrast오류나 중단 뒤 입력과 맥락을 잃지 않고 다음 유효한 행동으로 돌아갈 수 있는 정도다.
inline validation, retry, undo, autosave, fallback UI네 신호는 서로 대체하지 않는다. LCP가 빨라도 저장 버튼이 중복 요청을 만들면 실패다. 오류율이 낮아도 키보드 초점이 모달 안에 갇히면 일부 사용자는 전환할 수 없다. conversion이 올라도 오류 뒤 입력이 사라져 지원 문의와 재시도가 늘면 지속 가능한 개선이라 보기 어렵다.
2026년 7월 확인 기준으로 web.dev가 제시하는 Core Web Vitals와 권장 경계는 다음과 같다. good 판정은 모바일과 데스크톱을 나누어 페이지 방문의 p75(75번째 백분위수) 에 적용한다.
| 지표 | 쉬운 정의 | Good | Needs improvement | Poor | 제품 관점 질문 |
|---|---|---|---|---|---|
| LCP | viewport 안에서 가장 큰 이미지나 텍스트 블록이 그려진 시점으로 보는 로딩 성능 | <= 2.5s | > 2.5s, <= 4.0s | > 4.0s | 핵심 내용을 기다리다 onboarding을 떠나는가 |
| INP | 한 페이지 방문의 모든 click·tap·keyboard interaction을 관찰해 정한 대표 최장 지연 | <= 200ms | > 200ms, <= 500ms | > 500ms | 방문 중 가장 나쁜 반응 경험이 중복 행동을 만드는가 |
| CLS | 예상하지 못한 layout shift가 만든 시각적 불안정성의 누적 점수 | <= 0.1 | > 0.1, <= 0.25 | > 0.25 | CTA가 움직여 오클릭이나 폼 맥락 손실을 만드는가 |
LCP는 전체 페이지가 모두 로드된 시각이 아니고, INP는 API 요청의 최종 완료 시간 전체가 아니다. INP는 한 페이지 방문 동안 상호작용 이후 다음 화면 갱신이 막힌 시간을 관찰하고, 일반적인 방문에서는 가장 긴 interaction을 대표값으로 삼는다. 상호작용이 매우 많은 페이지에서는 우연한 이상치의 영향을 줄이기 위한 조정이 적용된다. 따라서 save_settings INP처럼 개별 조작에 CWV 이름을 붙이지 않는다. 개별 저장 조작은 interaction_latency_ms 같은 custom metric으로 측정하고, page-visit INP와 나란히 진단한다. CLS도 의도한 애니메이션 자체를 벌주는 점수가 아니라 사용자가 예상하지 못한 이동을 포착한다.
백분위수는 평균이 아니다. 방문 1,000개의 LCP를 빠른 값부터 정렬했을 때 p75는 750번째 부근의 값이다. p75 LCP가 2.4s라면 적어도 약 75%의 방문이 2.4s 이하였다는 뜻이다. p75가 good 경계 안에 있으면 “모든 방문이 빠르다”가 아니라 대다수 방문이 권장 경계를 만족한다는 뜻이다.
다음 8개 LCP가 있다고 하자.
1.2s, 1.4s, 1.7s, 2.0s, 2.2s, 2.4s, 4.8s, 7.0s대략 75%인 앞의 6개 방문은 2.4s 이하다. 평균은 약 2.84s지만 이 평균에 good, needs improvement, poor라는 공식 CWV 등급을 붙이면 안 된다. 공식 경계는 page visit 분포의 p75에 적용하므로, 이 단순 예제에서 p75를 2.4s로 잡는 계산법이라면 LCP는 good이다. 실제 percentile 구현의 보간 방식은 도구마다 확인해야 한다. 반대로 평균이 작아도 느린 방문이 25% 이상이면 p75가 경계를 넘을 수 있다. 평균은 분포를 탐색하는 보조값일 뿐 CWV 등급 판정값이 아니다.
퀴즈
힌트: 전체 분포와 제품상 중요한 segment의 분포는 다른 질문이다.
전체 사이트의 대다수 방문은 good 경계지만 신규 모바일 activation 경험은 poor다. 전체 통과를 근거로 rollout을 계속하지 말고, 이 segment의 표본 수와 route 정의를 확인한 뒤 LCP 구성 요소를 lab에서 재현해야 한다.
CrUX(Chrome User Experience Report, Chrome 사용자 경험 보고서)는 자격 조건을 만족한 실제 Chrome 사용 경험을 집계한 공개 field dataset이다. PageSpeed Insights의 field 영역은 최근 28일 분포를 보여 주므로 오늘 배포의 효과를 즉시 판정하기에는 느리다. 자체 RUM은 release version, route template, experiment_id와 variant 같은 맥락을 더 빨리 붙일 수 있지만 계측과 개인정보 책임도 직접 진다.
CrUX도 모든 사용자를 대표하지 않는다. Chrome의 지원 플랫폼과 데이터 제공 조건을 만족한 사용자만 포함되며 iOS Chrome, WebView, 다른 브라우저 등은 빠질 수 있다. 자체 RUM도 consent 거부, 광고 차단기, 전송 실패, sampling 때문에 빠진 방문이 생긴다. field라는 이름이 곧 모집단 전체라는 뜻은 아니다.
lab은 동일한 네트워크와 기기 조건으로 변경 전후를 반복해 waterfall, 긴 task, layout shift 원인을 좁힌다. 하지만 한 synthetic profile의 성공을 실제 모든 segment의 성공으로 일반화할 수 없다. field에서 문제 segment를 찾고 lab에서 재현한 뒤 field에서 개선 여부를 다시 확인하는 왕복이 필요하다.
RUM은 브라우저 SDK(Software Development Kit, 소프트웨어 개발 도구)가 값을 보내는 것으로 끝나지 않는다. 수집 단위와 분모가 안정적이어야 release 전후를 비교할 수 있다.
browser observation -> privacy filter -> sampling decision -> page-view / interaction / error event -> validation and deduplication -> percentile and rate aggregation -> route / device / browser / country·region / plan / variant segment -> product metric과 제한된 context로 비교interaction_name과 interaction_latency_ms를 별도 custom metric으로 보면 어떤 조작이 대표 최장 지연에 기여했는지 좁힐 수 있다.affected sessions / eligible sessions, failed submits / submit attempts처럼 위험에 노출된 단위를 함께 기록한다.두 이벤트는 공통 context를 가질 수 있지만 서로의 역할을 대신하지 않는다.
rum_page_view event_id, occurred_at, schema_version, sampling_policy_version, page_view_id, route_template, lcp_ms, cls, inp_ms, device_class, connection_class, release, sample_rate, experiment_exposure_id?
rum_interaction event_id, occurred_at, schema_version, sampling_policy_version, page_view_id, interaction_name, interaction_latency_ms, target_role, release, experiment_exposure_id?
frontend_error event_id, occurred_at, schema_version, sampling_policy_version, page_view_id, error_code, boundary_area, release, recovered, experiment_exposure_id?
form_submit_attempted event_id, occurred_at, schema_version, sampling_policy_version, workspace_id, form_id, attempt_id, experiment_id?, variant?
form_submit_failed event_id, occurred_at, schema_version, sampling_policy_version, workspace_id, form_id, attempt_id, error_code, field_group, route_template, source, experiment_id?, variant?
fatal_frontend_error event_id, occurred_at, schema_version, sampling_policy_version, app_boot_id, release, fatal_error_code, incident_id, experiment_id?, variant?
eligible_app_boot event_id, occurred_at, schema_version, sampling_policy_version, app_boot_id, release, experiment_id?, variant?
experiment_assigned event_id, occurred_at, schema_version, sampling_policy_version, workspace_id, experiment_id, variant
experiment_exposed event_id, occurred_at, schema_version, sampling_policy_version, workspace_id, experiment_id, variant, page_view_id
dashboard_saved event_id, occurred_at, schema_version, sampling_policy_version, workspace_id, dashboard_id, experiment_id, variant모든 이벤트는 Tracking 문서의 공통 envelope인 event_id, occurred_at, schema_version, sampling_policy_version을 재사용한다. 표본 stream은 rum-pageview-20-v1, 전량 stream은 full-v1처럼 sampling key·rate·unit 계약을 version에 고정한다. 실험 맥락의 RUM은 experiment_id와 variant를 임의 복제하기보다 experiment_exposure_id로 검증된 exposure에 연결하고, exposure가 없는 telemetry를 노출자 분석에 조용히 합치지 않는다. 여기서 experiment_exposure_id는 experiment_exposed.event_id를 참조하는 외래 키다. 두 값을 별도로 발급하는 ID로 오해하면 join이 재현되지 않는다.
RUM의 interaction_name=invite_submit과 interaction_latency_ms는 버튼 반응성을 진단하기 위한 낮은 cardinality(서로 다른 값의 개수) custom metric이다. Analytics의 dashboard_saved나 invite_accepted는 도메인 상태가 실제로 바뀐 뒤 기록하는 구체적인 과거형 사업 사건이다. 클릭이 빨랐다고 저장이 완료된 것은 아니고, 저장이 완료됐다고 그 과정이 접근 가능하거나 빨랐던 것도 아니다.
form_submit_attempted는 client validation을 통과한 하나의 논리적 제출 시도가 전송되기 직전에 발생한다. form_submit_failed는 같은 시도가 client transport, server validation, 권한, 도메인 규칙 등의 이유로 완료되지 않았을 때 같은 attempt_id로 발생한다. form_id는 DOM 위치가 아니라 dashboard_settings처럼 form 계약을 식별하는 안정적인 값이고, 사용자의 새 제출 의도에는 전역적으로 유일한 새 attempt_id를 발급한다. collector 재전송은 같은 ID를 유지하며 각 event type 안에서 (event_type, attempt_id)로 중복 제거한다.
두 form event의 공통 envelope는 event_id, occurred_at, schema_version, sampling_policy_version, workspace_id, form_id, attempt_id다. 실험 맥락이 있으면 experiment_id와 variant가 둘 다 있어야 하고, 없으면 둘 다 없어야 한다. orphan form_submit_failed, 즉 대응하는 attempted가 없는 failure는 데이터 품질 격리 대상으로 두고 canonical guardrail에 조용히 합치지 않는다.
form_submit_failure_rate = count(distinct failed.attempt_id joined to attempted on attempt_id where workspace_id, form_id, analysis window, experiment_id, variant가 모두 일치) / count(distinct attempted.attempt_id)분자와 분모는 같은 form_id, 분석 window, eligible population, experiment envelope를 사용한다. 한 attempt가 client와 server에서 여러 failure row를 만들더라도 unique attempt_id 한 번만 세고, retry가 새로운 사용자 제출 의도라면 새 attempt로 센다. form_submit_failed는 원인을 설명하는 진단 event이며 dashboard_saved 같은 성공 사건의 대체 분자가 아니다.
eligible_app_boot는 실험 대상 앱이 부팅되어 해당 release와 experiment assignment를 적용할 수 있는 상태가 된 한 번의 boot를 기록한다. fatal_frontend_error는 같은 app_boot_id와, 실험 맥락이 있다면 같은 experiment_id·variant를 공유한다. 두 event 모두 experiment field가 있으면 쌍으로 존재해야 하며 mismatch는 격리한다. Variant별 post-assignment observed fatal incidence는 unique fatal incident_id / unique eligible app_boot_id x 10,000으로 계산하고, 분자와 분모를 같은 release·실험·variant·window로 제한한다. Assignment 적용 전에 앱이 종료된 초기 bootstrap fatal은 이 variant 지표의 범위 밖이므로, 전체 release 안전성은 더 이른 boot-attempt counter나 별도 crash telemetry로 관찰한다.
Tracking 계약에 맞춰 제품 event에는 workspace_id를 쓰고, 실험 맥락이 있는 사건은 experiment_id와 variant를 항상 같은 envelope에 넣는다. variant만 단독으로 보내면 어느 실험의 값인지 알 수 없다. 반대로 RUM payload에 workspace_id를 복제하지 않고, 허용된 page_view_id와 experiment_exposed를 통해 필요한 분석에서만 연결한다.
연결이 필요하면 원문 user ID를 모든 telemetry에 복제하기보다 목적에 맞는 짧은 수명의 page_view_id, experiment exposure ID, coarse route를 고려한다. 어떤 식별자와 결합이 허용되는지는 조직 정책과 관할 법률에 따라 달라진다.
Sampling(표본 추출) 은 전체 관찰 중 일부만 수집하는 정책이다. 하루 100,000 page view에서 page view 단위로 독립적인 10% uniform sampling을 하면 기대 표본은 약 10,000개다. 전체의 5%인 저사양 모바일 방문은 기대값으로 약 500개가 남는다. 해당 segment의 p75가 release 판단에 중요하다면 이 분모가 충분한지 별도로 봐야 한다.
web.dev의 CrUX와 RUM이 다른 이유 공식 가이드도 RUM의 sampling rate를 낮췄을 때 표본이 작거나 전체 모집단을 대표하지 못하면 결과가 왜곡될 수 있다고 설명한다. Best practices for measuring Web Vitals in the field는 평균 대신 분포와 p75를 보고, metric ID와 page visit 단위를 보존하는 field measurement 계약을 제시한다. 이 지침은 특정 비율을 보편 정답으로 주지 않으므로 서비스 트래픽, 중요한 segment의 최소 분모, 비용을 함께 정해야 한다.
sampling 비율만 payload에 넣는다고 편향이 사라지지는 않는다.
따라서 sampling key, rate, unit을 버전 관리하고 분모·분자에 같은 정책을 적용한다. 이 문서의 20% sampled RUM stream에서는 page view를 선택할 때 그 page_view_id에 속한 rum_page_view, interaction, non-fatal frontend_error를 함께 포함하거나 제외한다. 그러면 sampled_nonfatal_error_affected_rate = error가 하나 이상 있는 unique sampled page_view_id / unique sampled page_view_id처럼 같은 stream 안에서 비율을 계산할 수 있다.
fatal_frontend_error와 eligible_app_boot는 비용과 탐지 목적이 다른 별도 100% sampling policy stream이다. 이를 20% sampled page-view 분모로 나누지 않는다. 두 event를 같은 app_boot_id와 experiment envelope로 수집할 때 post_assignment_observed_fatal_incidence_per_10k_app_boots = unique incident_id / unique eligible app_boot_id x 10,000으로 정의한다. 100% sampling을 의도해도 프로세스 종료·전송 실패 때문에 delivery coverage가 100%라는 뜻은 아니다. delivery coverage를 검증하지 못했다면 이름처럼 관측된 incidence로만 해석한다. 같은 policy의 boot 분모가 없다면 fatal_frontend_error_count만 보고하고 incidence rate라고 부르지 않는다. 작은 segment는 하루 p75의 출렁임보다 기간을 늘린 분포와 표본 수를 함께 보여 주는 편이 낫다.
Attribution(귀속 정보) 은 나쁜 지표와 가장 관련 있어 보이는 element, interaction target, script, timing 구간을 붙여 디버깅 범위를 줄이는 정보다. Google의 web-vitals/attribution build는 표준 build보다 진단 필드를 더 제공한다. 예를 들어 한 page visit의 INP가 420ms이고 그 대표 interaction target이 저장 버튼이며 긴 script가 겹쳤다는 정보는 재현할 후보를 준다. 저장 버튼 자체의 반복 분포는 별도 interaction_latency_ms로 본다.
그러나 같은 방문에서 INP가 길고 conversion이 낮았다는 관찰만으로 긴 script가 conversion 하락의 원인이라고 말할 수는 없다. 큰 workspace 데이터가 script와 이탈을 동시에 늘렸을 수 있다. attribution으로 가설을 만들고, lab profile·performance trace·코드 변경 비교·무작위 실험 중 적합한 방법으로 원인을 검증한다.
Error boundary(오류 경계) 는 하위 UI tree의 rendering 오류를 포착해 전체 화면 대신 fallback UI를 보여 주고 오류를 보고하는 격리 경계다. React에서는 rendering 중 하위 component가 던진 오류를 경계가 잡을 수 있다. 경계를 route 전체 하나에만 두면 작은 widget 오류가 화면 전체를 가리고, 너무 잘게 두면 사용자 작업 맥락과 오류 보고가 분절된다. 독립적으로 대체 가능한 제품 영역에 맞춰 경계를 둔다.
Error boundary는 모든 오류를 잡는 전역 안전망이 아니다. 일반적으로 event handler에서 난 오류, 비동기 callback 실패, API의 실패 응답, 경계 자체의 오류까지 자동으로 처리하지 않는다. 네트워크 실패는 요청 상태와 retry 정책으로, 제출 실패는 폼 상태와 server error mapping으로 다뤄야 한다.
좋은 오류 이벤트는 error_code, boundary_area, release, route_template, recovered를 남긴다. 사용자가 fallback에서 retry해 성공했다면 단순 error count와 recovery rate를 함께 본다. 반대로 stack 원문, query string, 입력값, access token을 그대로 전송하면 디버깅 편의가 개인정보와 보안 경계를 침범한다.
폼은 입력, 검증, 네트워크, 서버 규칙, 오류 회복, 접근성이 만나는 경계다. 결제, 초대, 가입, 프로젝트 생성에서는 작은 결함이 제품 지표의 분모와 분자를 직접 바꾼다.
| 실패 | 관찰 가능한 신호 | 제품 영향 | 설계 기준 |
|---|---|---|---|
| late validation | submit 직후 여러 field error가 동시에 증가 | 수정 비용이 커져 이탈한다 | 입력 중 방해하지 않는 inline validation과 submit 시 전체 검증을 조합한다 |
| unclear error | 같은 error code에서 retry와 이탈이 반복 | 무엇을 고칠지 몰라 conversion이 줄어든다 | 필드별 메시지와 recovery CTA를 제공한다 |
| duplicate submit | 한 interaction 뒤 동일 요청이 여러 번 발생 | 결제·초대·생성이 중복된다 | pending state와 idempotency key를 함께 쓴다 |
| lost input | 오류 뒤 입력 길이가 0으로 돌아감 | 재입력 비용과 지원 문의가 늘어난다 | 안전한 state preservation과 필요한 범위의 autosave를 둔다 |
| inaccessible form | keyboard submit·error focus·label 연결 실패 | 일부 사용자가 흐름을 완료할 수 없다 | label, description, error association, focus 이동을 검증한다 |
버튼을 disable하는 것만으로 duplicate submit이 해결되지는 않는다. 느린 반응 때문에 사용자가 다른 탭이나 클라이언트에서 다시 요청할 수 있고 네트워크 retry도 생긴다. UI는 반복 의도를 줄이고, 서버의 idempotency는 실제 중복 효과를 막는다. 서로 다른 실패 경계를 해결한다.
Accessibility(접근성) 는 다양한 장애와 입력 환경에서도 웹 콘텐츠와 기능을 인지하고, 조작하고, 이해하며, 보조 기술과 호환되게 만드는 제품 속성이다. WCAG(Web Content Accessibility Guidelines, 웹 콘텐츠 접근성 지침) 2.2는 이를 perceivable(인지 가능), operable(조작 가능), understandable(이해 가능), robust(견고함)의 네 원칙과 검증 가능한 success criteria로 구조화한다.
접근성은 자동 점수 하나로 끝나지 않는다. 정적 검사와 synthetic test는 label 누락이나 일부 contrast 문제를 빠르게 찾지만, 문구가 이해 가능한지, focus가 작업 순서와 맞는지, 스크린 리더로 오류를 고칠 수 있는지는 수동·보조 기술 검사가 필요하다. RUM도 특정 장애 여부를 추론하는 도구가 아니다.
제품 지표와의 연결은 장벽이 있는 흐름의 결과를 검증하는 것이다. 예를 들어 keyboard-only synthetic flow의 성공 여부, 오류 focus 이동 성공, 핵심 flow의 task completion, support ticket을 함께 본다. 특정 접근성 결함과 conversion이 같이 나빠졌다는 사실은 우선순위를 높이는 증거지만, 소수 사용자의 치명적 차단을 “전체 conversion 영향이 작다”는 이유로 미뤄서는 안 된다.
WCAG 적합성과 법적 의무는 동일한 문장이 아니다. 적용되는 기준과 의무는 국가, 서비스 유형, 계약, 조직 정책에 따라 다르므로 이 문서는 법률 판정을 제공하지 않는다. 관할과 정책을 확인하되, 제품 사용을 막는 장벽은 지표 크기와 무관하게 결함으로 다룬다.
RUM은 운영 telemetry라고 부른다고 자동으로 개인정보 규칙 밖에 놓이지 않는다. 기기·네트워크·정밀 timestamp·URL·selector·식별자를 결합하면 사람이나 행동을 알아볼 가능성이 커진다. sampling은 수집량을 줄일 뿐 익명화를 보장하지 않는다.
W3C Privacy Principles의 data minimization(데이터 최소화)과 purpose limitation(목적 제한)을 RUM에 적용하면 질문은 단순하다. “이 필드가 성능·신뢰성 문제를 진단하는 데 필요한가? 다른 목적으로 재사용하지 않는가? 사용자가 거부하거나 철회할 경로와 보존·삭제 정책이 있는가?”
| 진단 질문 | 더 좁은 데이터 | 피해야 할 데이터 |
|---|---|---|
| 어느 화면이 느린가 | /workspace/:id/settings 같은 route template | 실제 workspace ID와 query string 전체 |
| 어느 조작이 느린가 | invite_submit, button 같은 허용 목록 | DOM text, 입력값, 임의 selector 전체 |
| 어느 오류가 늘었나 | 정규화한 error code와 component area | token·이메일·사용자 입력이 섞인 stack 원문 |
| 어느 환경이 취약한가 | coarse device·browser·connection class | 불필요한 fingerprint 조합과 정밀 위치 |
ICO가 2026년 4월 확정한 Storage and Access Technologies 지침은 영국 PECR에서 statistical purposes exception(통계 목적 예외) 을 인정한다. 그러나 이는 analytics나 RUM 전체에 대한 포괄 면제가 아니다. 정보사회서비스 제공자가 기기 저장·접근 기술을 다음 조건에 맞게 사용할 때만, 해당 저장·접근에 대해 PECR consent 없이 예외를 검토할 수 있다.
page load speed, bounce·exit page, aggregate device·browser 통계는 지침이 예외 가능 사례로 드는 범주와 가깝다. 반면 장기간의 개인 방문 기록, cross-service tracking, 광고 측정, 개인 또는 범주에 관한 profiling, 집계 뒤 individual-level data 보유는 예외 범위를 벗어난다. workspace_id와 장기간 행동을 연결하는 product analytics까지 같은 RUM storage에 섞으면 “서비스 사용 집계만”이라는 유일 목적 조건을 만족하기 어렵다.
이 예외는 영국 PECR의 storage/access consent에 관한 조건부 경계다. personal data를 처리하면 UK GDPR 의무는 별도로 남고, 다른 국가에는 다른 규칙이 적용될 수 있다. 실제 RUM 설계에서는 관할, 기술의 저장·접근 방식, 목적, vendor, retention을 privacy·legal 담당자와 검토한다. 예외 조건을 충족하지 않거나 목적이 섞이면 consent가 필요할 수 있다.
consent 뒤에만 RUM을 시작한다면 관측 모집단이 consent한 사용자로 제한된다는 사실도 대시보드에 남긴다. 영국 예외에 의존하더라도 opt-out한 사용자는 제외한다. consent·opt-out rate가 release·region·device별로 다르면 전후 segment 구성이 달라질 수 있다. privacy 선택을 우회해서 편향을 없애는 것이 아니라, 편향을 명시하고 synthetic·CrUX·지원 신호 같은 다른 증거로 보완한다.
feature flag로 새 dashboard를 트래픽의 25%에 노출하는 것만으로는 인과 실험이 되지 않는다. 시간대별 비율 변경, 특정 고객 allowlist, session별 재배정이 섞인 단순 rollout은 blast radius를 줄일 수 있지만 control과 treatment의 비교 가능성을 보장하지 않는다. 아래 예제는 제품 효과를 추정하기 위해 별도의 controlled experiment(통제 실험) 계약을 사용한다.
40,000개workspace_id; session은 배정 단위가 아니며 같은 workspace의 모든 session은 같은 variant를 본다.75%, treatment 25%; hash(experiment_id, workspace_id) 같은 stable assignment를 실험 종료까지 유지한다.experiment_assigned를 한 번 기록하고, 실제 dashboard가 visible·interactive가 될 때 experiment_exposed를 기록한다.dashboard_saved가 발생한 unique workspace 비율을 ITT(Intention-to-Treat, 최초 배정 기준)로 비교한다.dashboard_saved에 workspace_id, experiment_id, variant를 함께 기록한다.200ms를 넘지 않고, interaction_name=save_settings인 custom interaction_latency_ms p75가 제품별 250ms hold 경계를 넘지 않는다.sampled_nonfatal_error_affected_rate가 baseline 대비 20% 넘게 악화되지 않고, unique attempted/failed attempt_id로 계산한 form_submit_failure_rate가 3%를 넘지 않는다. 100% sampling policy의 fatal·boot stream은 별도 post_assignment_observed_fatal_incidence_per_10k_app_boots로 본다.eligible_app_boot는 별도 100% sampling policy를 쓰되 delivery coverage를 함께 본다. 이 20%는 실험의 25% treatment allocation과 다른 정책이다.LaunchDarkly의 공식 randomization-unit 문서도 배정 단위를 variant에 무작위 배정되는 entity로 설명한다. 이 예제에서 entity를 session이 아니라 workspace로 고른 이유는 저장 결과와 협업 상태가 workspace에 귀속되고, 같은 workspace의 여러 session이 서로 독립이지 않기 때문이다.
40,000개 workspace의 기대 assignment는 control 30,000개, treatment 10,000개다. 이것이 SRM(Sample Ratio Mismatch, 표본 비율 불일치) 검정의 사전 기대치인 75:25다. 50:50이나 page-view RUM 표본 비율과 비교하면 계약이 틀린다.
다음 계산은 원리를 확인하기 위한 선택 심화다. Pearson 카이제곱 검정은 범주별 관측 개수와 사전에 기대한 개수의 차이가 우연한 변동으로 설명될 만한지 보는 방법이다. 범주가 control과 treatment 둘뿐이면 합계가 정해진 뒤 독립적으로 달라질 수 있는 값이 하나라서 자유도는 1이다.
실제 exposure가 총 39,000개이고 control 29,180개, treatment 9,820개라면 같은 총수에서 기대 exposure는 control 29,250개, treatment 9,750개다. Pearson 카이제곱 통계량은 (29,180-29,250)^2/29,250 + (9,820-9,750)^2/9,750, 즉 chi-square ≈ 0.67이고 자유도 1에서 p ≈ 0.41이다. 사전 유의수준 5%를 쓴다면 이 관찰값은 SRM mismatch 신호가 아니다. p-value는 기대 비율이 맞다는 가정 아래 이 정도 이상 차이가 관측될 확률이다. 배정이 옳을 확률이 41%라는 뜻이 아니라, 75:25 가정 아래 이 정도 이탈이 드물지 않다는 뜻이다.
반대로 같은 총 39,000 exposure가 control 30,000개, treatment 9,000개라면 기대치는 여전히 29,250개와 9,750개이고 chi-square ≈ 76.9, p < 0.001이다. 이때는 uplift 해석을 멈추고 assignment, execution, collection, join 단계에서 treatment가 빠졌는지 조사한다. Microsoft Research의 SRM 논문도 SRM을 실험 효과가 아니라 배정·실행·수집 파이프라인이 깨졌음을 알리는 데이터 품질 진단으로 다룬다.
assignment와 exposure는 각각 SRM을 검사한다. 같은 workspace에 variant가 둘 이상이거나 outcome은 있는데 assignment가 없으면 uplift를 읽기 전에 randomization key, cache, event join을 고친다.
session을 독립 표본으로 세면 활동량이 많은 workspace가 여러 표를 가진 것처럼 과대 계산된다. 제품 outcome은 randomization unit인 unique workspace로 분석한다. RUM의 page-visit p75는 운영 guardrail 분포로 볼 수 있지만, 반복 방문을 독립 workspace인 것처럼 취급해 인과 효과의 확신을 부풀리지 않는다.
| 신호 | Control | Treatment | 해석 |
|---|---|---|---|
unique workspace dashboard_saved rate | 42.0% | 44.5% | ITT 관찰 차이 +2.5%p |
| dashboard page-visit INP p75 | 180ms | 220ms | treatment가 CWV good 경계를 벗어남 |
| 저사양 모바일 dashboard page-visit INP p75 | 260ms | 410ms | 두 집단 모두 good 밖이며 treatment가 더 악화 |
save_settings의 custom interaction_latency_ms p75 | 160ms | 320ms | 제품별 250ms hold 경계 위반; CWV 등급은 적용하지 않음 |
sampled_nonfatal_error_affected_rate | 0.8% | 1.2% | 같은 20% page-view stream, 상대 +50% |
post_assignment_observed_fatal_incidence_per_10k_app_boots | 1.0 | 4.0 | 같은 experiment envelope의 post-assignment 관측 incidence 악화 |
unique-attempt form_submit_failure_rate | 2.0% | 5.0% | failed/attempted unique attempt_id, 3% 경계 위반 |
| keyboard/error-focus synthetic | 통과 | 실패 | 핵심 flow 차단 |
시나리오
Treatment의 dashboard_saved rate는 +2.5%p지만 dashboard page-visit INP, save_settings interaction_latency_ms, sampled non-fatal error rate, fatal incidence, unique-attempt form failure, keyboard flow가 함께 악화되었다.
실험 노출과 rollout을 계속하기 전에 무엇을 결정하고 무엇을 추가 검증할 것인가?합리적인 첫 결정은 treatment exposure를 25%에서 hold하거나 위험한 variant를 끄는 것이다. primary metric 개선이 사전 guardrail 위반을 상쇄한다고 사후에 규칙을 바꾸면 guardrail의 의미가 없다. 특히 접근성 차단은 전체 결과의 개선으로 보상할 수 없다.
다음으로 원인 가설을 좁힌다. chart script를 포함한 profile과 제외한 profile을 같은 lab 조건에서 반복하고, 개별 save interaction의 input delay·processing duration·presentation delay를 interaction_latency_ms와 함께 본다. page-visit INP가 220ms인 방문에서 save button이 대표 interaction target이었다는 attribution은 chart script를 후보로 올릴 뿐이다. 수정 전후 비교나 통제된 후속 실험 없이 인과를 확정하지 않는다.
마지막으로 segment bias를 확인한다. randomization이 정상이어도 exposure·consent·RUM sampling 뒤 treatment의 저사양 모바일 비중이 control보다 높아질 수 있다. 같은 segment 안에서도 dashboard INP p75가 260ms -> 410ms라면 구성 차이만으로 설명되지 않지만, 이는 page-visit 분포의 guardrail 비교이지 workspace-level primary outcome과 같은 분석 단위는 아니다. assignment SRM, exposure SRM, variant별 전송 실패를 분리해 확인한 뒤 rollout 판단을 갱신한다.
통제 실험이 아닌 release 전후 관찰에서 release 전에는 desktop 80%, mobile 20%였고 release 후 마케팅 때문에 desktop 50%, mobile 50%가 되었다고 하자. 각 device 안의 LCP p75가 모두 0.2s 개선되어도 느린 mobile 비중이 늘면 전체 혼합 분포의 p75는 악화될 수 있다. 반대로 빠른 desktop 유입이 늘면 각 segment의 p75가 나빠졌는데 전체 p75는 좋아질 수 있다.
이것은 “segment를 많이 나누면 진실이 나온다”는 뜻도 아니다. 표본이 작은 segment를 수십 개 만들면 우연한 극값과 privacy 위험이 커진다. 제품 가설과 rollout 위험에 직접 연결된 route, device class, browser, release, variant부터 보고 각 cell의 분모를 함께 표시한다.
guardrail은 장애가 난 뒤 그럴듯한 임계치를 붙이는 알람이 아니다. rollout 전에 baseline 변동, 사용자 영향, 중단 비용을 바탕으로 판정과 행동을 연결한 계약이다.
단순 rollout의 전후 차이는 운영 판단 신호이지 자동으로 인과 효과가 되지 않는다. controlled experiment라면 stable assignment와 ITT 분석을 유지하고, rollout 비율을 바꿀 때도 이미 배정된 workspace의 variant를 session마다 다시 뽑지 않는다. 두 경우 모두 guardrail은 노출 확대를 멈추는 안전 경계지만, 인과 주장 가능성은 다르다.
| 상황 | 기본 판단 | 이유 |
|---|---|---|
| primary metric 개선, 치명 접근성 flow 실패 | 즉시 hold 또는 rollback | 평균 이득으로 사용 불가능 상태를 보상할 수 없다 |
| CWV가 경계 근처이고 작은 표본에서만 변동 | 비율 유지, 더 긴 window와 재현 확인 | 작은 분모의 p75는 몇 표본에 민감하다 |
| 특정 release·route의 error rate 급증 | 해당 variant 중단 후 fingerprint 조사 | blast radius와 원인 후보가 비교적 좁다 |
| 특정 form error code가 급증 | 영향 variant를 hold하고 hotfix 범위를 좁힌다 | 전체 실패율이 오류 유형별 급증을 숨길 수 있다 |
| RUM 악화, synthetic 정상 | 실제 segment·데이터 크기·확장·캐시 조건 재현 | 통제 환경이 빠뜨린 조건일 수 있다 |
| synthetic 실패, RUM 변화 없음 | 배포 범위와 실제 노출·표본을 확인 | 아직 노출이 적거나 lab-only 문제가 있을 수 있다 |
| conversion과 성능이 함께 개선 | rollout 확대 전 사전 guardrail과 segment 재확인 | 상관관계만으로 장기 효과와 모든 집단을 보장하지 않는다 |
| conversion은 개선됐지만 support ticket 증가 | 현재 비율에서 hold하고 문의 유형·회복률을 조사 | 지연된 신뢰성 문제를 primary metric이 숨길 수 있다 |
기존의 “route-level JS error rate가 baseline 대비 20% 이상 악화되면 중단” 같은 수치는 제품별 예시이지 보편 표준이 아니다. baseline이 0.01%인 오류와 5%인 오류는 같은 상대 20% 변화라도 의미가 다르다. 상대 변화, 절대 변화, affected user 수, 오류의 심각도를 함께 정한다.
| 실패 신호 | 의미할 수 있는 것 | 먼저 확인할 경계 |
|---|---|---|
| 전체 p75는 좋지만 핵심 route conversion이 하락 | 중요한 segment가 평균에 가려짐 | route·device·variant 분모와 제품 사건 정의 |
| release 직후 모든 CWV가 동시에 급변 | 실제 회귀 또는 계측 버전 변화 | SDK·route mapping·sampling 정책 version |
| error count 증가, affected session rate 유지 | 트래픽 증가나 한 session의 반복 보고 | dedup key와 올바른 denominator |
| INP가 없는 page view가 많음 | 사용자가 상호작용하지 않았거나 전송이 누락됨 | INP는 상호작용 없는 방문에서 보고되지 않는다는 경계 |
| CrUX와 자체 RUM이 크게 다름 | 모집단·기간·iframe·browser 범위 차이 | 각 dataset의 eligibility와 measurement scope |
| consent rate 변화와 성능 변화가 같은 날 발생 | 관측 모집단 구성 변화 | region·device별 consent와 sampling coverage |
| assignment·exposure 비율이 75:25에서 이탈 | 배정·실행·수집·join 중 한 층이 깨짐 | 단계별 unique workspace SRM |
| 같은 workspace에 두 variant가 기록됨 | stable assignment가 깨짐 | randomization key, cache, identity merge |
| boundary error는 줄었지만 이탈은 증가 | fallback이 작업을 회복시키지 못함 | recovered action, lost input, retry success |
| 접근성 자동 점수는 유지되지만 문의가 증가 | 자동 검사가 의미·순서·보조 기술 문제를 놓침 | 수동 keyboard·screen reader task flow |
프론트엔드 품질 신호는 제품 데이터 governance와 직접 연결된다. RUM과 analytics는 모두 사용자 행동과 환경에 관한 데이터를 다루지만 목적과 허용 범위는 다르다.
Privacy, Consent & Product Data Governance: RUM, analytics, error payload의 목적, 동의, 보존, 삭제, vendor 경계를 정한다.Experimentation & Feature Flags: 품질 guardrail을 사전 계약으로 만들고 rollout·hold·rollback에 반영한다.Product Analytics & Tracking Plan: 제품 행동 사건과 RUM context를 필요한 범위에서 연결한다.Funnel, Cohort & Retention Metrics: segment 차이, 작은 분모, 선택 편향을 제품 흐름과 함께 해석한다.