GA 입문 8편. 운영 함정 + Privacy 깊이 — Sampling 의 정체 (10M event 초과 시) · Threshold Applied 의 데이터 누락 · Cardinality 폭발 의 (other) 행 · iOS 14+ ITP · Cookie 차단 · Server-side 회피 · Consent Mode v2 의 4 consent type (analytics_storage · ad_storage · ad_user_data · ad_personalization) · Behavioral/Conversion Modeling · EU DMA · Korea PIPA · PII filter · IP anonymization · Data Retention (2개월 ~ 50개월). 운영 안 한 사람은 안 보이는 사고들 자리.
이 글은 Google Analytics 입문에서 운영까지 시리즈 8편이에요. 7편 Reports · Explorations 까지 = 기능 이해. 8편 = 실제 운영의 무거운 사고 자리.
이번 글의 범위
GA 를 1년 운영 후 진짜 보이는 함정. 두 영역 묶음:
| 영역 | 자산 |
|---|---|
| 데이터 한도 함정 | Sampling · Threshold · Cardinality · Data Retention |
| Privacy 의무 | iOS ITP · Cookie · Consent Mode v2 · GDPR · PIPA |
각 함정 = 처음엔 안 보임. 분석 결과가 이상함 으로 발견. 운영자 의 디버그 능력 핵심.
Sampling — 데이터 추정의 함정
Sampling 의 발생 조건
Reports (표준 리포트):
→ sampling 없음 (사전 집계 데이터)
Explorations:
→ 1,000만 event 초과 시 sampling
→ 360 Property = 10억 event 초과 시
여기서 시험 함정 — Reports vs Explorations 의 차이.
표준 Reports = 사전 집계 (sampling 없음) / Explorations = 1천만 event 초과 시 sampling. — 공식 docs
Sampling 의 표시
Exploration UI 상단:
🟢 100% 데이터 (sampling 없음)
🟡 75% 데이터 (25% sample)
🔴 10% 데이터 (sampling 강함)
10% 데이터 = 대상 event 의 1/10 만 분석. 통계적 추정 후 원래 크기로 환원. 결과 = 근사값.
Sampling 회피
1. 기간 짧게 — 7일 → 3일 → sampling 없음
2. Segment 좁히기 — 모든 사용자 → 특정 segment
3. Filter 사전 적용 — 100M event → 10M event
4. 360 업그레이드 — 10억 event 한도
5. BigQuery Export → SQL 직접 (sampling 없음)
사고 — Sampling 인지 못한 분석
case:
Exploration: "최근 30일 채널별 매출"
→ 매출 표시 = 추정값
→ "Newsletter 가 50,000원" = 실제 45,000~55,000원
→ 작은 채널은 *오차율 크다*
해결 — 큰 channel 만 신뢰. 작은 channel = BQ 로 정확 확인.
Threshold (Threshold Applied) — 데이터 누락 함정
Threshold 의 의미
Threshold = 작은 데이터 segment 의 식별 가능성 보호 위해 데이터 일부 누락.
case:
segment: "20대 여자 + Seoul + premium"
→ 해당 사용자 5명
→ Threshold trigger → 데이터 표시 X (또는 "(other)" 로 통합)
Threshold 의 조건
Trigger 조건:
- Google Signal 활성 (Demographics · Interests)
- User ID 사용
- 작은 cohort (보통 < 30 user)
영향:
- Demographics report
- Interests report
- User-level 분석 (User Explorer · User Lifetime)
- 일부 dimension 의 작은 segment
Threshold 회피
1. Google Signal 끄기 — Demographics 정확
(단 Demographics 자체 사용 불가)
2. Segment 크게 — 30+ user 보장
3. BigQuery Export → threshold 없음
사고 — Threshold Applied UI 표시 못 봄
UI 상단의 경고:
"⚠️ Threshold Applied"
운영자가 모르고 *데이터 부족* 만 보고 → 의사 결정 잘못.
해결 — 모든 segment report 의 threshold 경고 의식. 행 수 갑자기 줄어듦 = threshold 의심.
Cardinality 폭발 — (other) 행
Cardinality 의 정의
한 dimension 의 *unique 값 수*:
- event_name: ~50개 (제어 가능)
- country: ~200개 (자연 한도)
- page_path: 수만 ~ 수십만 (URL 다양성)
- session_id: 수백만 (사용자 × session)
- user_pseudo_id: 모든 사용자
High Cardinality 의 한도
Dimension 의 unique 값 500 초과 시 (event scope) → 초과 분 (other) 로 집계. — 일반 GA 규칙
예 — page_path 의 cardinality:
/home (정상 row)
/products/123 (정상 row)
/products/124 (정상 row)
...
/products/501 (500번째 까지 정상)
/products/502 → (other) 에 합산
/products/503 → (other) 에 합산
...
(other) 행 = 수많은 작은 path 의 합계. 개별 page 분석 불가.
Cardinality 폭발 원인
- URL 의 query parameter 그대로 (?sessionid=xyz, ?ref=abc 등)
- 동적 URL (/products/{id}/{variant})
- Search query 를 path 에 포함
- Custom dimension 에 *user_id 비슷한 unique 값* 박음
- event_name 에 timestamp · counter 포함
- Page title 에 *현재 시각* 포함
Cardinality 회피
1. URL normalization
- query parameter strip (단 분석 필요한 utm_* 빼고)
- dynamic id → "/products/[id]" 같은 pattern
2. Event name 표준
- "click_button_12345" → "click_button" + button_id parameter
3. Custom dimension 의 cardinality 의식
- user_id 같은 unique = user property X
- segment-able grouping ("premium" · "free")
4. BigQuery 활용
- BQ 는 cardinality 한도 없음
- "수많은 unique URL" 분석 = BQ
사고 — (other) 의 데이터 손실
운영 case:
page_path 의 cardinality 5,000 초과
→ "/products/*" 의 *모든 상품 페이지* 가 (other)
→ 어느 상품 페이지가 인기? 답할 수 없음
해결 — cardinality 측정 (BQ 의 COUNT DISTINCT page_path) + normalization + BQ 분석.
Data Retention — 데이터 보유 기간
Standard Property 한도
Standard Property:
- 2 months (default)
- 14 months (max)
360 Property:
- 2 months · 14 months · 26 months · 38 months · 50 months
Retention 의 영향 자리
영향 받음:
- Explorations 의 historical data
- Funnel · Path · Cohort 의 lookback
- User Lifetime metric
영향 안 받음:
- 표준 Reports (사전 집계 데이터, 더 오래 유지)
- BigQuery Export (별도 retention — BQ 의 storage 정책)
Reset on New Activity
Reset on new activity = ON:
사용자가 새 활동 → retention 시계 리셋
예: 14 month retention + 13 month 에 활동 → 14 month 더
Reset on new activity = OFF:
사용자 마지막 활동 시점 부터 14 month → 만료
권장 = ON (활성 사용자 데이터 유지).
Property 의 Large 자동 축소
Standard property 가 Large 가 됨 (10억 event/월 초과):
→ retention 자동 *2개월* 로 축소
→ 알림 이메일
360 property 가 XL 이 됨:
→ 자동 2개월 축소
대규모 데이터 = retention 짧음 + 360 업그레이드 필요 자리.
사고 — 분석 시점에 데이터 없음
case:
10월에 "지난 1년 cohort 분석" 시도
→ retention 14 month 였지만 *2 month 로 축소* 알림 누락
→ 9월 까지 데이터 모두 삭제
→ cohort 분석 X
해결 — retention 변경 알림 모니터 + 중요 데이터 BQ Export. BQ 가 backup 자리.
iOS 14+ ITP · Cookie 차단
iOS 의 ITP (Intelligent Tracking Prevention)
ITP 의 영향:
- 3rd-party cookie 차단 (광고 추적)
- 1st-party cookie 의 retention 짧음 (7일 → 1일)
- JavaScript 의 document.cookie API 제한
- Cross-site tracking 매우 제한
GA4 의 영향:
1. Client ID 의 *짧은 retention*
- 7일 후 같은 사용자 = *새 client_id*
- GA 의 *returning user 식별 어려움*
2. Attribution 정확도 하락
- 광고 click → 7일 후 conversion = *attribution 안 됨*
3. Cross-device tracking 어려움
- User ID 없으면 *iPhone vs MacBook* = 다른 사용자
Cookie 차단 (브라우저 default)
Safari (default 차단):
- 3rd-party cookie 차단
- ITP
Firefox (default 차단):
- Enhanced Tracking Protection
- 3rd-party cookie · cross-site tracking 차단
Chrome (전환 중):
- 2026 부터 3rd-party cookie 점진 차단
- Privacy Sandbox 대안 (FLEDGE · Topics API)
회피 방법
1. User ID 활용
- 로그인 사용자 = *영구 식별* (cookie 무관)
- 모든 사용자 의 *로그인 유도* (소셜 로그인 등)
2. Server-side GTM (sGTM) — 3편
- 우리 server 가 prox y → 1st-party hit
- ITP 영향 적음
3. First-party cookie 적극 활용
- GTM · gtag 의 cookie_flags · cookie_domain 설정
4. Consent Mode + Modeling
- 동의 안 한 사용자 = modeling 으로 추정
Consent Mode v2 — EU + Korea 의무
Consent Mode 의 의미
Consent Mode = 사용자의 cookie · 앱 식별자 동의 상태 를 Google 에 전달.
사용자 동의 안 함:
→ cookie 박지 않음 + cookieless ping 만 전송
→ Google ML이 *modeling 으로 데이터 추정*
사용자 동의 함:
→ 정상 cookie + 추적
4 Consent Type (v2)
┌─────────────────────────┬─────────────────────────────────┐
│ Consent Type │ 의미 │
├─────────────────────────┼─────────────────────────────────┤
│ analytics_storage │ GA 의 cookie · 분석 데이터 │
│ ad_storage │ 광고 cookie · click 추적 │
│ ad_user_data │ 광고 목적 사용자 데이터 (DMA 추가) │
│ ad_personalization │ 광고 개인화 (DMA 추가) │
└─────────────────────────┴─────────────────────────────────┘
여기서 정말 중요한 시험 함정 — v2 의 ad_user_data · ad_personalization 가 DMA (EU Digital Markets Act) 의 2024 의무. 안 박으면 EU 의 conversion 데이터 빈값.
Default vs Update
// 1. Default — Cookie banner 표시 전
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500 // 500ms 동안 update 대기
});
// 2. 사용자가 cookie banner 의 "Accept All" 클릭
gtag('consent', 'update', {
analytics_storage: 'granted',
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted'
});
// 3. 사용자가 "Reject All" 클릭
gtag('consent', 'update', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied'
});
// 4. 사용자가 부분 동의 (분석 OK · 광고 X)
gtag('consent', 'update', {
analytics_storage: 'granted',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied'
});
지역별 default
// EU 에서 더 엄격한 default
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
region: ['EU', 'GB', 'EEA'] // 이 region 에서만 적용
});
// 다른 region (US 등) — 더 느슨
gtag('consent', 'default', {
analytics_storage: 'granted', // 기본 허용
ad_storage: 'denied', // 광고만 default 차단
region: ['US', 'CA']
});
// Korea (PIPA) — 명시 동의 필요
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
region: ['KR']
});
Modeling — 데이터 보강
Cookieless ping 이 modeling 에 사용. 데이터 gap 보강.
1. Behavioral Modeling
- 동의 안 한 사용자 의 *행동 추정*
- 동의한 사용자 의 patten 으로 학습
- 'visitors' · 'events' 같은 추정값
2. Conversion Modeling
- 동의 안 한 사용자 의 *conversion 추정*
- Google Ads 입찰 최적화에 활용
- Click → conversion 의 *unconfirmed* 신호
필수 조건 — Consent Mode 박힘 + Google Signal 활성 + 충분한 historical data (보통 28일).
EU DMA · GDPR
EU DMA (Digital Markets Act, 2024):
- 4 consent type 모두 명시 의무
- "Reject All" 옵션 의무 (Accept · Reject 평등)
- dark pattern 금지
GDPR:
- 사용자 명시 동의 필수
- 동의 철회 쉬워야 함
- 데이터 처리 목적 명시
Korea PIPA (개인정보보호법)
- 명시 동의 의무
- "전체 동의" vs "선택 동의" 구분
- 광고/분석 목적 분리 동의
- 동의 안 한 데이터의 *수집/이용 금지*
- 외국 이전 시 *추가 동의*
사고 — Consent Mode 없이 EU 사용자
case:
Consent Mode 박지 않고 GA 운영
→ EU 사용자의 GA 데이터 = legal risk
→ 또는 모든 EU 사용자 차단
→ DMA 2024 의무 위반 시 *벌금* (전 세계 매출 10% max)
해결 — Consent Mode v2 + 4 consent type + 지역별 default.
PII (Personally Identifiable Information) 함정
PII 의 정의 (GA 측)
GA 가 직접 금지:
- 이메일 (alice@company.com)
- 전화번호
- 사회보장번호 (US SSN · Korea 주민번호)
- 신용카드 번호
- 정부 ID
권장 안 함 (legal risk):
- 이름
- 정확한 주소
- IP address (raw)
PII 누출 사고
case 1: URL 에 email 박힘
/reset-password?email=alice@company.com
→ GA 가 자동 수집 → 위반
case 2: Custom event 의 parameter
gtag('event', 'form_submit', {
user_email: 'alice@company.com' // ❌
});
case 3: page_title 에 PII
document.title = "주문 상세 - alice@company.com"
→ page_view event 의 page_title 에 자동 박힘
PII filter — Data Stream 의 자동 redaction
GA4 console > Data Streams > Configure tag settings > Redact data
자동 redaction (URL parameter 에서):
- Email (X@Y.Z 패턴)
- 사용자 정의 patten (regex 으로 추가)
회피 패턴
1. URL 의 PII parameter 제거
- 결제 페이지 URL = /checkout (parameter 없음)
- 인증 token 같은 sensitive = path 또는 fragment
2. Custom event parameter 의 hash
- 직접 email 박지 X
- SHA-256 hash 같이 *식별 불가* 변환
3. User ID 의 hash
- DB 의 user_id 그대로 X
- hash(internal_user_id) 만 GA 로
4. PII 자동 scan
- 정기 BQ scan 으로 email 패턴 감지
- 발견 시 즉시 stream 의 redaction 룰 추가
IP Anonymization · Data Residency
IP Anonymization (default ON)
GA4:
- IP address 의 *마지막 octet 자동 제거*
- 192.168.1.123 → 192.168.1.0
- geo 추정 만 가능 (city 단위 X 또는 region 단위)
- 별도 setting X (자동)
Data Residency
Standard:
- 데이터 region 자동 선택 (보통 US)
- 한국 사용자의 데이터 = US 의 GA 데이터 센터
360:
- Data residency 선택 가능 (US · Europe 등)
- 한국 = Asia Pacific (asia-northeast 등)
- GDPR · 한국 PIPA 의 *외국 이전* 동의 의무 회피
Korea PIPA 의 외국 이전
한국 사용자 데이터 → US 의 GA 데이터센터:
= 외국 이전
→ 별도 명시 동의 필요
회피:
- 360 + Asia Pacific data residency
- Server-side GTM (sGTM) + 한국 server
- Consent banner 의 *외국 이전* 명시
운영 권장 패턴
Pattern 1: Sampling 감지 자동화
# Exploration API 호출 후 sampling 확인
response = client.run_report(...)
if response.metadata.data_loss_from_other_row:
logger.warning("Data lost from (other) row — Cardinality 폭발")
if hasattr(response, 'sampling_metadata') and response.sampling_metadata.sampling_rate < 1.0:
logger.warning(f"Sampling 적용: {response.sampling_metadata.sampling_rate * 100}%")
Pattern 2: Cardinality 모니터
-- BQ 에서 매일 cardinality 측정
SELECT
'page_path' AS dimension,
COUNT(DISTINCT (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location')) AS distinct_count
FROM `our-project.analytics_123.events_*`
WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
UNION ALL
SELECT
'event_name' AS dimension,
COUNT(DISTINCT event_name) AS distinct_count
FROM `our-project.analytics_123.events_*`
WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
500 초과 dimension = (other) 위험 alert.
Pattern 3: Consent Mode + Cookie Banner
<!-- 1. GTM Tag 또는 gtag.js 박기 전 -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// Default — 모든 region 에서 한 번
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500
});
gtag('js', new Date());
gtag('config', 'G-XXXXXXXX');
</script>
<!-- 2. Cookie banner JS -->
<script>
function onAcceptAll() {
gtag('consent', 'update', {
analytics_storage: 'granted',
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted'
});
localStorage.setItem('consent', 'all');
}
function onAcceptAnalyticsOnly() {
gtag('consent', 'update', {
analytics_storage: 'granted',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied'
});
localStorage.setItem('consent', 'analytics');
}
function onRejectAll() {
// 모두 denied 유지
localStorage.setItem('consent', 'none');
}
// 페이지 로드 시 — 이전 동의 복원
const consent = localStorage.getItem('consent');
if (consent === 'all') onAcceptAll();
else if (consent === 'analytics') onAcceptAnalyticsOnly();
else if (consent === 'none') onRejectAll();
// 새 사용자 → cookie banner 표시 + default denied 유지
</script>
Pattern 4: User ID 의 점진 적용
// 1. 비로그인 사용자 = client_id 만
// (자동)
// 2. 로그인 후
function onUserLogin(userId, userTier) {
// 내부 user_id → hash
const hashedId = sha256(userId + SALT);
// GA 에 박기
gtag('config', 'G-XXXXXXXX', {
user_id: hashedId
});
// User Property
gtag('set', 'user_properties', {
user_tier: userTier
});
}
// 3. 로그아웃
function onUserLogout() {
gtag('config', 'G-XXXXXXXX', {
user_id: null
});
}
Pattern 5: PII Scan 자동화
-- 매일 BQ scan — PII 누출 감지
SELECT
event_date,
event_name,
param.key,
param.value.string_value
FROM `our-project.analytics_123.events_*`,
UNNEST(event_params) AS param
WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE() - 1)
AND param.value.string_value IS NOT NULL
AND (
REGEXP_CONTAINS(param.value.string_value, r'[\w.+-]+@[\w-]+\.[\w.-]+') -- email
OR REGEXP_CONTAINS(param.value.string_value, r'\d{3}-\d{4}-\d{4}') -- 전화번호
OR REGEXP_CONTAINS(param.value.string_value, r'\d{6}-[1-4]\d{6}') -- 주민번호 패턴
)
LIMIT 100
발견 시 즉시 alert + redaction 룰 추가 + 데이터 삭제.
Pattern 6: Data Retention Backup
중요 데이터 보호:
1. Data Retention 14 month (Standard max)
2. BigQuery Export — 별도 BQ retention (Google 의 정책 + 우리 의 retention policy)
3. BQ → GCS (Cloud Storage) Cold Backup
- 매월 raw data archive
- $0.004/GB/월 (archive tier)
4. 회사 의 데이터 보존 정책 따름 (GDPR · 한국 PIPA)
시험 직전 한 번 더 — 운영 함정 + Privacy 압축 노트
Sampling
- Reports = 사전 집계 (sampling 없음)
- Explorations = 1천만 event 초과 시 (360 = 10억)
- 표시 — 🟢 100% · 🟡 부분 · 🔴 강함
- 회피 — 기간 짧게 · segment · BQ
Threshold (Threshold Applied)
- 작은 segment 의 식별 보호 (보통 < 30 user)
- Google Signal · User ID 활성 시 발생
- Demographics · Interests · User-level 영향
- 회피 — Signal 끄기 · segment 크게 · BQ
Cardinality
- 한 dimension 의 unique 값 500 초과 시 (other)
- 원인 — URL parameter · dynamic id · timestamp · custom dim 의 unique 값
- 회피 — URL normalization · event name 표준 · custom dim 의식 · BQ
Data Retention
- Standard: 2개월 · 14개월 (max)
- 360: 2 · 14 · 26 · 38 · 50개월
- Large/XL 자동 2개월 축소
- Reports 영향 X · Exploration/Funnel 영향
- Reset on new activity = ON 권장
- 중요 데이터 = BQ Export 로 backup
iOS ITP · Cookie
- Safari · Firefox = default 차단
- Chrome = 2026 부터 점진 차단
- Client ID retention 7일 → 1일
- 회피 — User ID · Server-side GTM · 1st-party cookie
Consent Mode v2
- 4 consent type: analytics_storage · ad_storage · ad_user_data · ad_personalization
- v2 = DMA 의 ad_user_data · ad_personalization 추가
- default → update (사용자 동의 후)
- region 별 default 다름
Modeling
- Behavioral Modeling — 사용자 행동 추정
- Conversion Modeling — 동의 안 한 사용자 conversion 추정
- 필수 — Consent Mode + Google Signal + 28일 데이터
EU DMA · GDPR
- 4 consent type 필수
- "Reject All" 의 평등 옵션
- 동의 철회 쉬워야 함
- 벌금 = 전 세계 매출 10% max
Korea PIPA
- 명시 동의
- "전체 동의" vs "선택 동의" 구분
- 광고/분석 목적 분리
- 외국 이전 = 별도 동의
PII
- 이메일 · 전화 · 주민번호 · 신용카드 자동 금지
- 이름 · 주소 · IP raw 권장 안 함
- 누출 원인 — URL parameter · custom event · page_title
- Data Stream 의 자동 redaction (email 등)
- 회피 — URL 정리 · hash · 정기 BQ scan
IP Anonymization
- GA4 자동 (last octet 제거)
- city 단위 정도까지만 추정
Data Residency
- Standard = US (default)
- 360 = Europe · Asia Pacific 선택 가능
- 한국 사용자 = 외국 이전 동의 필요 (없으면 360 Asia)
사고
- Sampling 인지 못한 분석 (작은 channel 의 큰 오차)
- Threshold Applied 인지 못함 (segment 의 row 누락)
- (other) 의 데이터 손실
- Property Large 자동 retention 2개월 (알림 누락)
- iOS Client ID 의 7일 retention
- Consent Mode 없이 EU 사용자 (DMA 벌금)
- PII URL/Parameter 누출 (자동 수집)
- 한국 사용자 외국 이전 동의 누락
패턴
- Sampling 자동 감지 (API metadata)
- Cardinality BQ 모니터 (매일)
- Consent Mode + Cookie banner (default denied · update)
- User ID 점진 적용 (로그인 시 hash)
- PII 자동 scan (매일 BQ regex)
- Data Retention Backup (BQ + GCS archive)
공식 문서: Consent Mode v2 · Data Retention 에서 더 깊은 spec 을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 3편 — 측정 통합 4 방법 깊이 (gtag · GTM · Firebase · MP)
- 4편 — Event · Conversion · Audience 설계 깊이
- 5편 — Data API · Admin API · 외부 통합 깊이
- 6편 — BigQuery Export 깊이 (schema · UNNEST · 비용)
- 7편 — Reports · Explorations · Attribution 깊이
다음 글: