콘텐츠로 이동

Frontend Product Quality & RUM

분류: 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는 실제 결제 완료율이 변했는지 보여 준다. 접근성 검사는 키보드만으로 오류 필드에 도달하고 메시지를 이해할 수 있는지 확인한다. 어느 하나만으로 나머지 질문에 답할 수 없다.

2. 선행 방식의 한계 - 왜 RUM이 등장했는가

섹션 제목: “2. 선행 방식의 한계 - 왜 RUM이 등장했는가”

로컬 개발 환경과 CI(Continuous Integration, 지속적 통합)의 브라우저는 대개 안정적인 CPU, 네트워크, 빈 캐시, 준비된 테스트 계정을 사용한다. 실제 사용자는 오래된 기기, 데이터 절약 모드, 브라우저 확장, 큰 계정 데이터, 여러 탭, 불안정한 무선망을 사용한다. 로그인 상태와 캐시 상태도 다르다. 같은 코드가 이 환경 조합에서 전혀 다른 경험을 만든다.

lab data는 이 다양성을 제거하기 때문에 원인을 좁히기 좋다. 그러나 바로 그 통제가 실제 분포를 숨긴다. field data는 다양성을 보존해 “누가 실제로 느린가”를 보여 주지만, 동시에 여러 요인이 섞여 원인을 단정하기 어렵다. RUM의 철학은 모든 사용자를 한 숫자로 환원하는 것이 아니라 실제 경험의 분포와 맥락을 남겨 다음 재현 실험을 고르는 것이다.

synthetic/lab: 재현 가능한 조건에서 회귀를 발견한다
RUM/field: 실제 분포와 영향을 받는 segment를 찾는다
analytics: 같은 시점의 제품 행동 변화를 확인한다
실험·재현: 원인 가설을 검증하고 rollout 결정을 바꾼다

이 피드백 루프에서 RUM은 판결문이 아니라 관찰 증거다. 느린 사용자가 이탈했다는 상관관계는 중요하지만, 느림이 이탈을 일으켰다는 인과관계까지 자동으로 증명하지는 않는다.

3. 제품 품질을 구성하는 네 신호

섹션 제목: “3. 제품 품질을 구성하는 네 신호”

Frontend Product Quality Signal

Performance(성능)

사용자가 핵심 콘텐츠를 보고 입력·클릭의 결과를 확인하기까지 걸리는 시간이다.

LCP, INP, route transition time, 긴 main thread 작업

Reliability(신뢰성)

오류, 중복 처리, 데이터 손실 없이 핵심 행동을 완료할 수 있는 정도다.

JS error, API error, failed submit, duplicate submit

Accessibility(접근성)

장애, 입력 방식, 감각 조건과 관계없이 정보와 기능을 인지하고 조작할 수 있는 정도다.

semantic HTML, keyboard, focus, name·role·value, contrast

Recoverability(회복 가능성)

오류나 중단 뒤 입력과 맥락을 잃지 않고 다음 유효한 행동으로 돌아갈 수 있는 정도다.

inline validation, retry, undo, autosave, fallback UI

네 신호는 서로 대체하지 않는다. LCP가 빨라도 저장 버튼이 중복 요청을 만들면 실패다. 오류율이 낮아도 키보드 초점이 모달 안에 갇히면 일부 사용자는 전환할 수 없다. conversion이 올라도 오류 뒤 입력이 사라져 지원 문의와 재시도가 늘면 지속 가능한 개선이라 보기 어렵다.

2026년 7월 확인 기준으로 web.dev가 제시하는 Core Web Vitals와 권장 경계는 다음과 같다. good 판정은 모바일과 데스크톱을 나누어 페이지 방문의 p75(75번째 백분위수) 에 적용한다.

지표쉬운 정의GoodNeeds improvementPoor제품 관점 질문
LCPviewport 안에서 가장 큰 이미지나 텍스트 블록이 그려진 시점으로 보는 로딩 성능<= 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.25CTA가 움직여 오클릭이나 폼 맥락 손실을 만드는가

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 등급 판정값이 아니다.

퀴즈

전체 LCP p75는 2.3초인데 신규 모바일 activation route의 p75는 4.6초라면 어느 결론이 타당한가?

힌트: 전체 분포와 제품상 중요한 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_idvariant 같은 맥락을 더 빨리 붙일 수 있지만 계측과 개인정보 책임도 직접 진다.

CrUX도 모든 사용자를 대표하지 않는다. Chrome의 지원 플랫폼과 데이터 제공 조건을 만족한 사용자만 포함되며 iOS Chrome, WebView, 다른 브라우저 등은 빠질 수 있다. 자체 RUM도 consent 거부, 광고 차단기, 전송 실패, sampling 때문에 빠진 방문이 생긴다. field라는 이름이 곧 모집단 전체라는 뜻은 아니다.

lab은 동일한 네트워크와 기기 조건으로 변경 전후를 반복해 waterfall, 긴 task, layout shift 원인을 좁힌다. 하지만 한 synthetic profile의 성공을 실제 모든 segment의 성공으로 일반화할 수 없다. field에서 문제 segment를 찾고 lab에서 재현한 뒤 field에서 개선 여부를 다시 확인하는 왕복이 필요하다.

5. RUM 파이프라인: 관찰을 증거로 만드는 메커니즘

섹션 제목: “5. RUM 파이프라인: 관찰을 증거로 만드는 메커니즘”

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로 비교
  • page view는 한 문서 방문의 LCP·CLS·INP를 묶는 기본 단위다. SPA(Single-Page Application, 단일 페이지 애플리케이션)의 client-side route transition은 브라우저 navigation과 같지 않으므로 별도 route timing 계약이 필요하다.
  • interaction은 click, tap, keyboard 입력처럼 사용자가 시작한 조작이다. page-visit INP가 나쁠 때 interaction_nameinteraction_latency_ms를 별도 custom metric으로 보면 어떤 조작이 대표 최장 지연에 기여했는지 좁힐 수 있다.
  • error fingerprint는 같은 오류를 메시지 원문 대신 안정적인 code, release, component area로 묶는 키다. stack과 URL을 그대로 저장하는 것과 다르다.
  • denominator(분모) 가 없는 오류 건수는 해석하기 어렵다. affected sessions / eligible sessions, failed submits / submit attempts처럼 위험에 노출된 단위를 함께 기록한다.

5.1 RUM 이벤트와 analytics 이벤트의 경계

섹션 제목: “5.1 RUM 이벤트와 analytics 이벤트의 경계”

두 이벤트는 공통 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_idvariant를 임의 복제하기보다 experiment_exposure_id로 검증된 exposure에 연결하고, exposure가 없는 telemetry를 노출자 분석에 조용히 합치지 않는다. 여기서 experiment_exposure_idexperiment_exposed.event_id를 참조하는 외래 키다. 두 값을 별도로 발급하는 ID로 오해하면 join이 재현되지 않는다.

RUM의 interaction_name=invite_submitinteraction_latency_ms는 버튼 반응성을 진단하기 위한 낮은 cardinality(서로 다른 값의 개수) custom metric이다. Analytics의 dashboard_savedinvite_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_idvariant가 둘 다 있어야 하고, 없으면 둘 다 없어야 한다. 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 incidenceunique 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_idvariant를 항상 같은 envelope에 넣는다. variant만 단독으로 보내면 어느 실험의 값인지 알 수 없다. 반대로 RUM payload에 workspace_id를 복제하지 않고, 허용된 page_view_idexperiment_exposed를 통해 필요한 분석에서만 연결한다.

연결이 필요하면 원문 user ID를 모든 telemetry에 복제하기보다 목적에 맞는 짧은 수명의 page_view_id, experiment exposure ID, coarse route를 고려한다. 어떤 식별자와 결합이 허용되는지는 조직 정책과 관할 법률에 따라 달라진다.

5.2 sampling은 비용 절감과 추론 경계를 함께 만든다

섹션 제목: “5.2 sampling은 비용 절감과 추론 경계를 함께 만든다”

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에 넣는다고 편향이 사라지지는 않는다.

  • 느린 페이지에서 SDK가 늦게 로드되어 전송 전에 이탈하면 느린 방문이 덜 잡힌다.
  • consent한 사용자만 수집하면 consent하지 않은 집단과 기기·지역 구성이 다를 수 있다.
  • 오류 이벤트는 100%, 정상 page view는 10% 수집하면서 원시 건수만 나누면 오류율이 10배 과장될 수 있다.
  • session 단위 sampling과 event 단위 sampling을 섞으면 한 funnel 안의 사건 연결이 끊긴다.

따라서 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_erroreligible_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의 출렁임보다 기간을 늘린 분포와 표본 수를 함께 보여 주는 편이 낫다.

5.3 attribution은 원인 후보이지 인과 증명은 아니다

섹션 제목: “5.3 attribution은 원인 후보이지 인과 증명은 아니다”

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·코드 변경 비교·무작위 실험 중 적합한 방법으로 원인을 검증한다.

6. Error boundary와 회복 가능한 오류

섹션 제목: “6. Error boundary와 회복 가능한 오류”

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을 그대로 전송하면 디버깅 편의가 개인정보와 보안 경계를 침범한다.

7. Form reliability는 conversion reliability다

섹션 제목: “7. Form reliability는 conversion reliability다”

폼은 입력, 검증, 네트워크, 서버 규칙, 오류 회복, 접근성이 만나는 경계다. 결제, 초대, 가입, 프로젝트 생성에서는 작은 결함이 제품 지표의 분모와 분자를 직접 바꾼다.

실패관찰 가능한 신호제품 영향설계 기준
late validationsubmit 직후 여러 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 formkeyboard submit·error focus·label 연결 실패일부 사용자가 흐름을 완료할 수 없다label, description, error association, focus 이동을 검증한다

버튼을 disable하는 것만으로 duplicate submit이 해결되지는 않는다. 느린 반응 때문에 사용자가 다른 탭이나 클라이언트에서 다시 요청할 수 있고 네트워크 retry도 생긴다. UI는 반복 의도를 줄이고, 서버의 idempotency는 실제 중복 효과를 막는다. 서로 다른 실패 경계를 해결한다.

8. Accessibility와 product metric의 관계

섹션 제목: “8. Accessibility와 product metric의 관계”

Accessibility(접근성) 는 다양한 장애와 입력 환경에서도 웹 콘텐츠와 기능을 인지하고, 조작하고, 이해하며, 보조 기술과 호환되게 만드는 제품 속성이다. WCAG(Web Content Accessibility Guidelines, 웹 콘텐츠 접근성 지침) 2.2는 이를 perceivable(인지 가능), operable(조작 가능), understandable(이해 가능), robust(견고함)의 네 원칙과 검증 가능한 success criteria로 구조화한다.

  • semantic HTML과 accessible name·role·value는 보조 기술이 control의 의미를 전달하게 한다.
  • keyboard focus order와 visible focus는 포인터 없이도 UX flow를 따라가게 한다.
  • modal, dropdown, toast의 상태 변화는 시각적 표현뿐 아니라 programmatic state로 전달되어야 한다.
  • 색만으로 오류를 구분하지 않고 input, 설명, 오류 메시지의 관계를 명시한다.
  • loading, empty, error, success 상태에서 다음 행동을 이해할 수 있어야 한다.

접근성은 자동 점수 하나로 끝나지 않는다. 정적 검사와 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 areatoken·이메일·사용자 입력이 섞인 stack 원문
어느 환경이 취약한가coarse device·browser·connection class불필요한 fingerprint 조합과 정밀 위치

9.1 영국의 statistical purposes 예외는 조건부다

섹션 제목: “9.1 영국의 statistical purposes 예외는 조건부다”

ICO가 2026년 4월 확정한 Storage and Access Technologies 지침은 영국 PECR에서 statistical purposes exception(통계 목적 예외) 을 인정한다. 그러나 이는 analytics나 RUM 전체에 대한 포괄 면제가 아니다. 정보사회서비스 제공자가 기기 저장·접근 기술을 다음 조건에 맞게 사용할 때만, 해당 저장·접근에 대해 PECR consent 없이 예외를 검토할 수 있다.

  • 유일한 목적이 서비스 또는 웹사이트의 사용 통계를 만들어 그 서비스 자체를 개선하는 것이어야 한다.
  • 결과는 개인 데이터가 아닌 aggregate statistics(집계 통계)여야 하며, 개인이나 개인 집단을 식별·추적·모니터링하거나 그들에 관한 추론·결정을 하는 데 쓰면 안 된다.
  • 집계 전 individual-level data가 필요하더라도 집계에 필요한 기간보다 오래 보관하지 않고, 집계 결과에서 사람을 식별할 수 없게 기술적·조직적 조치를 적용해야 한다.
  • 사용자에게 기술과 목적을 명확하고 포괄적으로 알리고, 저장 또는 접근에 반대할 무료이며 간단한 방법을 제공해야 한다.
  • 제3자 provider를 사용한다면 그 provider도 오직 해당 서비스 개선을 돕는 목적으로만 정보를 사용해야 한다.

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·지원 신호 같은 다른 증거로 보완한다.

10. Worked example - 25% controlled dashboard experiment

섹션 제목: “10. Worked example - 25% controlled dashboard experiment”

feature flag로 새 dashboard를 트래픽의 25%에 노출하는 것만으로는 인과 실험이 되지 않는다. 시간대별 비율 변경, 특정 고객 allowlist, session별 재배정이 섞인 단순 rollout은 blast radius를 줄일 수 있지만 control과 treatment의 비교 가능성을 보장하지 않는다. 아래 예제는 제품 효과를 추정하기 위해 별도의 controlled experiment(통제 실험) 계약을 사용한다.

  • eligible population: enrollment 전에 고정한 조건을 만족하는 workspace 40,000개
  • randomization unit: workspace_id; session은 배정 단위가 아니며 같은 workspace의 모든 session은 같은 variant를 본다.
  • allocation: control 75%, treatment 25%; hash(experiment_id, workspace_id) 같은 stable assignment를 실험 종료까지 유지한다.
  • assignment: 배정 즉시 experiment_assigned를 한 번 기록하고, 실제 dashboard가 visible·interactive가 될 때 experiment_exposed를 기록한다.
  • primary metric: 배정된 eligible unique workspace 중 고정 window 안에 dashboard_saved가 발생한 unique workspace 비율을 ITT(Intention-to-Treat, 최초 배정 기준)로 비교한다.
  • experiment envelope: assignment, exposure, dashboard_savedworkspace_id, experiment_id, variant를 함께 기록한다.
  • performance guardrail: dashboard page visit의 INP p75가 200ms를 넘지 않고, interaction_name=save_settings인 custom interaction_latency_ms p75가 제품별 250ms hold 경계를 넘지 않는다.
  • reliability guardrail: 20% sampled RUM 안의 sampled_nonfatal_error_affected_rate가 baseline 대비 20% 넘게 악화되지 않고, unique attempted/failed attempt_id로 계산한 form_submit_failure_rate3%를 넘지 않는다. 100% sampling policy의 fatal·boot stream은 별도 post_assignment_observed_fatal_incidence_per_10k_app_boots로 본다.
  • accessibility guardrail: keyboard 저장 flow와 error focus synthetic test가 모두 통과한다.
  • observation policy: RUM은 page view 단위 20% uniform sampling을 적용하고 같은 page-view 선택을 non-fatal error 분자에도 적용한다. 치명 error와 incidence 분모인 eligible_app_boot는 별도 100% sampling policy를 쓰되 delivery coverage를 함께 본다. 이 20%는 실험의 25% treatment allocation과 다른 정책이다.

LaunchDarkly의 공식 randomization-unit 문서도 배정 단위를 variant에 무작위 배정되는 entity로 설명한다. 이 예제에서 entity를 session이 아니라 workspace로 고른 이유는 저장 결과와 협업 상태가 workspace에 귀속되고, 같은 workspace의 여러 session이 서로 독립이지 않기 때문이다.

10.1 Assignment, exposure, SRM을 먼저 확인한다

섹션 제목: “10.1 Assignment, exposure, SRM을 먼저 확인한다”

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인 것처럼 취급해 인과 효과의 확신을 부풀리지 않는다.

10.2 품질 guardrail과 제품 outcome을 함께 읽는다

섹션 제목: “10.2 품질 guardrail과 제품 outcome을 함께 읽는다”
신호ControlTreatment해석
unique workspace dashboard_saved rate42.0%44.5%ITT 관찰 차이 +2.5%p
dashboard page-visit INP p75180ms220mstreatment가 CWV good 경계를 벗어남
저사양 모바일 dashboard page-visit INP p75260ms410ms두 집단 모두 good 밖이며 treatment가 더 악화
save_settings의 custom interaction_latency_ms p75160ms320ms제품별 250ms hold 경계 위반; CWV 등급은 적용하지 않음
sampled_nonfatal_error_affected_rate0.8%1.2%같은 20% page-view stream, 상대 +50%
post_assignment_observed_fatal_incidence_per_10k_app_boots1.04.0같은 experiment envelope의 post-assignment 관측 incidence 악화
unique-attempt form_submit_failure_rate2.0%5.0%failed/attempted unique attempt_id, 3% 경계 위반
keyboard/error-focus synthetic통과실패핵심 flow 차단

시나리오

ITT outcome은 늘었지만 품질 guardrail이 깨졌다

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의 분모를 함께 표시한다.

11. Rollout guardrail을 정하는 판단 기준

섹션 제목: “11. Rollout guardrail을 정하는 판단 기준”

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

Frontend Product Quality & RUM 체크

  • RUM, product analytics, synthetic monitoring의 목적과 스키마를 구분했다
  • LCP, CLS, INP의 현재 경계와 p75의 분모·기간·device 구분을 명시했다
  • INP는 rum_page_view의 page-visit 대표값으로 두고 개별 조작은 interaction_latency_ms로 분리했다
  • field와 lab의 모집단·재현성 차이를 알고 왕복 검증한다
  • route, release, device, browser, country·region, plan, variant별 표본 수와 sampling policy를 함께 본다
  • 실험은 workspace 단위 stable assignment와 experiment_id·variant envelope, assignment·exposure event를 갖는다
  • form_submit_attempted·failed는 공통 envelope와 attempt_id dedup을 쓰고 unique failed/attempted guardrail로 계산한다
  • 20% sampled non-fatal RUM과 100% fatal-error stream의 분자·분모를 섞지 않는다
  • JS error, API error, form failure를 올바른 denominator와 recovery 결과에 연결했다
  • error boundary가 잡는 오류와 잡지 못하는 오류를 구분했다
  • 폼은 validation, error mapping, idempotency, state preservation을 함께 다룬다
  • keyboard, screen reader, focus, name·role·value를 핵심 flow에서 검증한다
  • RUM payload에서 입력 원문, token, 실제 ID, 민감 query를 제거했다
  • consent·retention·access·deletion 정책과 관측 편향을 기록했다
  • 영국 statistical purposes 예외를 쓴다면 집계·목적 제한·개인 추적 금지·명확한 고지·무료 opt-out 조건을 검증했다
  • attribution과 상관관계를 인과 효과로 단정하지 않았다
  • rollout 전에 성능·오류·접근성 guardrail과 hold·rollback 행동을 정했다

프론트엔드 품질 신호는 제품 데이터 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 차이, 작은 분모, 선택 편향을 제품 흐름과 함께 해석한다.
  • Real User Monitoring
  • Synthetic Monitoring
  • Field Data / Lab Data
  • Core Web Vitals
  • LCP / CLS / INP
  • Percentile / p75
  • Frontend Error Rate
  • Error Boundary
  • Form Reliability
  • Accessibility / WCAG 2.2
  • Sampling / Segment Bias
  • Attribution / Causation
  • Release Guardrail