GA 입문 5편. Data API · Admin API 깊이 — runReport · batchRunReports · runPivotReport · runRealtimeReport 의 4 메서드, Dimension/Metric/DateRange/Filter/OrderBy 구조, 페이지네이션, quota, Admin API 의 Property·Stream·Custom Dimension/Metric·Audience·Conversion·Property Linking CRUD, 인증 (Service Account · OAuth), Python·Node 코드 패턴, Terraform·Slackbot·BI 도구 통합까지. UI 가 아닌 코드 자동화 자리.
이 글은 Google Analytics 입문에서 운영까지 시리즈 5편이에요. 4편 Event · Audience 까지 = 데이터 수집 + segment 자리. 이번 글부터는 데이터 추출 · 운영 자동화 자리.
이번 글의 범위
GA4 (구글 애널리틱스 4세대) 의 API 자동화. 두 가지 API + 각자의 자리:
| API | 자리 |
|---|---|
| Data API | report 데이터를 우리 시스템으로 추출 (BI · Slackbot · dashboard) |
| Admin API | Property · Stream · Audience · Custom Dimension 관리 (Terraform · IaC) |
UI 클릭만으로 운영 = 작은 회사 OK, 큰 회사 운영 자동화 X. 두 API 가 GA4 의 코드 인터페이스 전부.
Data API — Report 데이터 추출
4 메서드
┌────────────────────────┬────────────────────────┐
│ Method │ 용도 │
├────────────────────────┼────────────────────────┤
│ runReport │ 단일 report (가장 흔함) │
│ batchRunReports │ 여러 report 한 호출 │
│ runPivotReport │ pivot table (다축 분석) │
│ runRealtimeReport │ 최근 30분 실시간 data │
└────────────────────────┴────────────────────────┘
runReport 의 기본 구조
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import (
DateRange, Dimension, Metric, RunReportRequest,
Filter, FilterExpression, OrderBy
)
client = BetaAnalyticsDataClient()
request = RunReportRequest(
property=f"properties/{property_id}",
dimensions=[
Dimension(name="country"),
Dimension(name="deviceCategory"),
],
metrics=[
Metric(name="activeUsers"),
Metric(name="sessions"),
Metric(name="totalRevenue"),
],
date_ranges=[
DateRange(start_date="2026-04-01", end_date="2026-04-30"),
],
)
response = client.run_report(request)
for row in response.rows:
country = row.dimension_values[0].value
device = row.dimension_values[1].value
users = row.metric_values[0].value
sessions = row.metric_values[1].value
revenue = row.metric_values[2].value
print(f"{country} | {device} | {users} | {sessions} | {revenue}")
Dimension · Metric 의 카탈로그
GA4 의 모든 dimension · metric 이름 은 공식 카탈로그:
Dimension 예 (분석 축):
- 시간: date · hour · dateHour · ...
- 사용자: country · city · language · deviceCategory · ...
- 트래픽 소스: source · medium · campaignName · ...
- 페이지/콘텐츠: pagePath · pageTitle · screenName · ...
- Event: eventName · ...
- E-commerce: itemId · itemName · itemCategory · ...
- Custom: customUser:tier · customEvent:section · ...
Metric 예 (측정값):
- 사용자: totalUsers · activeUsers · newUsers · ...
- 세션: sessions · averageSessionDuration · bounceRate · ...
- Engagement: engagedSessions · engagementRate · userEngagementDuration · ...
- Event: eventCount · eventCountPerUser · ...
- E-commerce: totalRevenue · purchaseRevenue · transactions · ...
- Conversion: conversions · conversionRate · ...
여기서 시험 함정 — Dimension 과 Metric 의 호환성. 모든 조합이 가능 X. 예 — transactionId (dimension) + bounceRate (metric) = 호환 안 됨 (트랜잭션은 bounce 와 무관). 호환 안 되면 400 error 또는 빈 데이터.
Filter 의 구조
# 1. 단일 dimension filter
request = RunReportRequest(
...,
dimension_filter=FilterExpression(
filter=Filter(
field_name="country",
string_filter=Filter.StringFilter(value="KR")
)
)
)
# 2. AND / OR 조합
request = RunReportRequest(
...,
dimension_filter=FilterExpression(
and_group=FilterExpression.AndGroup(expressions=[
FilterExpression(filter=Filter(
field_name="country",
string_filter=Filter.StringFilter(value="KR")
)),
FilterExpression(filter=Filter(
field_name="deviceCategory",
string_filter=Filter.StringFilter(value="mobile")
))
])
)
)
# 3. Metric filter (예 — revenue > 50000)
request = RunReportRequest(
...,
metric_filter=FilterExpression(
filter=Filter(
field_name="totalRevenue",
numeric_filter=Filter.NumericFilter(
operation=Filter.NumericFilter.Operation.GREATER_THAN,
value={"int64_value": 50000}
)
)
)
)
dimension_filter = dimension 으로 행 줄이기, metric_filter = metric 값으로 행 줄이기.
OrderBy · Limit · Offset
request = RunReportRequest(
...,
order_bys=[
OrderBy(metric=OrderBy.MetricOrderBy(metric_name="totalRevenue"),
desc=True),
OrderBy(dimension=OrderBy.DimensionOrderBy(dimension_name="country"))
],
limit=100,
offset=0,
)
페이지네이션
# 1차 호출
response = client.run_report(request)
print(f"Total rows: {response.row_count}") # 전체 행 수
print(f"This page rows: {len(response.rows)}") # 이 호출에서 받은 행
# 다음 페이지 (limit=100 이면 offset 100, 200, 300, ...)
all_rows = []
offset = 0
limit = 100
while True:
request.limit = limit
request.offset = offset
response = client.run_report(request)
all_rows.extend(response.rows)
if len(response.rows) < limit:
break # 마지막 페이지
offset += limit
Quota — 호출 한도
Standard Property:
- Core Tokens: 25,000 / 일
- Realtime Tokens: 10,000 / 일
- Properties Per Project: 50
Property 360:
- Core Tokens: 250,000 / 일 (Standard 10배)
- Realtime Tokens: 50,000 / 일
호출 1번이 복잡한 query 면 여러 token 소비. 너무 복잡한 query = single call 이지만 quota 빨리 소진.
Quota header 응답에서 확인 가능:
# response.metadata 의 token 사용량
for usage in response.property_quota.tokens_per_day.consumed:
print(f"Consumed: {usage}")
batchRunReports — 여러 report 한 호출
from google.analytics.data_v1beta.types import BatchRunReportsRequest
batch_request = BatchRunReportsRequest(
property=f"properties/{property_id}",
requests=[
# Report 1: 사용자별 매출
RunReportRequest(
dimensions=[Dimension(name="userType")],
metrics=[Metric(name="totalRevenue")],
date_ranges=[DateRange(start_date="7daysAgo", end_date="today")]
),
# Report 2: 국가별 사용자
RunReportRequest(
dimensions=[Dimension(name="country")],
metrics=[Metric(name="activeUsers")],
date_ranges=[DateRange(start_date="7daysAgo", end_date="today")]
),
# 최대 5개 report 한 호출
]
)
batch_response = client.batch_run_reports(batch_request)
for report in batch_response.reports:
print(f"Report rows: {len(report.rows)}")
용도 — dashboard 의 여러 위젯 을 한 호출 로 가져옴. latency 감소 + quota token 절약.
runPivotReport — pivot table
일반 report:
country | device | users
───────────────────────
KR | mobile | 100
KR | desktop| 50
US | mobile | 80
US | desktop| 30
Pivot report:
mobile desktop
───────────────────────
KR | 100 | 50
US | 80 | 30
from google.analytics.data_v1beta.types import RunPivotReportRequest, Pivot
request = RunPivotReportRequest(
property=f"properties/{property_id}",
dimensions=[Dimension(name="country"), Dimension(name="deviceCategory")],
metrics=[Metric(name="activeUsers")],
date_ranges=[DateRange(start_date="7daysAgo", end_date="today")],
pivots=[
Pivot(
field_names=["country"],
limit=10
),
Pivot(
field_names=["deviceCategory"],
limit=5
)
]
)
response = client.run_pivot_report(request)
복잡한 다축 분석 = pivot 이 효율적. 일반 report 는 행 늘어남.
runRealtimeReport — 실시간
from google.analytics.data_v1beta.types import RunRealtimeReportRequest
request = RunRealtimeReportRequest(
property=f"properties/{property_id}",
dimensions=[Dimension(name="country"), Dimension(name="eventName")],
metrics=[Metric(name="activeUsers"), Metric(name="eventCount")]
)
response = client.run_realtime_report(request)
Realtime 한계:
- 최근 30분 (Standard) · 60분 (360) 만 query 가능
- DateRange 없음 (모든 query 가 "지금")
- 일부 dimension/metric 제한 (custom dimension 일부 X)
- 가장 최근 event 박힌 후 *몇 초 내* 반영
용도 — 실시간 dashboard · 이벤트 모니터링 · 현재 활성 사용자 표시.
Admin API — 운영 자동화
Admin API 의 6 영역
1. Account · Property · Stream (구조 관리)
2. Custom Dimension · Custom Metric (분석 축 관리)
3. Audience (사용자 segment CRUD)
4. Conversion event (Key Event) (전환 표시)
5. Property Linking (BigQuery · Google Ads · Firebase · DV360 · SA360)
6. Access Binding (alpha) (사용자 권한)
Property · Stream 관리
from google.analytics.admin_v1beta import AnalyticsAdminServiceClient
from google.analytics.admin_v1beta.types import Property, DataStream
client = AnalyticsAdminServiceClient()
# Property 만들기
new_property = Property(
parent="accounts/123456789",
display_name="Production Site",
industry_category="TECHNOLOGY",
time_zone="Asia/Seoul",
currency_code="KRW"
)
created = client.create_property(property=new_property)
print(f"Created: {created.name}") # "properties/987654321"
# Web Data Stream 만들기
new_stream = DataStream(
parent=created.name,
type_=DataStream.DataStreamType.WEB_DATA_STREAM,
display_name="Production Web",
web_stream_data=DataStream.WebStreamData(
default_uri="https://example.com"
)
)
stream = client.create_data_stream(data_stream=new_stream, parent=created.name)
print(f"Measurement ID: {stream.web_stream_data.measurement_id}")
Custom Dimension · Metric
from google.analytics.admin_v1beta.types import CustomDimension, CustomMetric
# Custom Dimension (event scope)
custom_dim = CustomDimension(
parameter_name="user_tier",
display_name="User Tier",
scope=CustomDimension.DimensionScope.USER,
description="premium · standard · free"
)
client.create_custom_dimension(
parent="properties/987654321",
custom_dimension=custom_dim
)
# Custom Metric
custom_metric = CustomMetric(
parameter_name="cart_value",
display_name="Cart Value",
measurement_unit=CustomMetric.MeasurementUnit.CURRENCY,
scope=CustomMetric.MetricScope.EVENT,
description="합계 cart 가치"
)
client.create_custom_metric(
parent="properties/987654321",
custom_metric=custom_metric
)
여기서 정말 중요한 시험 함정 — Custom Dimension 의 scope 가 event · session · user · item (이전 글 2편 의 4 layer). 한 번 만들면 변경 불가. 삭제 후 재생성 만 가능. 코드로 만들 때 scope 신중.
Audience CRUD (생성·조회·수정·삭제)
from google.analytics.admin_v1beta.types import (
Audience, AudienceFilterClause, AudienceSimpleFilter,
AudienceFilterExpression, AudienceFilter
)
# 카트 이탈자 audience
audience = Audience(
display_name="Cart Abandoners 7d",
description="최근 7일 add_to_cart 했지만 purchase 안 함",
membership_duration_days=30,
filter_clauses=[
# Include: add_to_cart
AudienceFilterClause(
clause_type=AudienceFilterClause.AudienceClauseType.INCLUDE,
simple_filter=AudienceSimpleFilter(
scope=AudienceFilterScope.AUDIENCE_FILTER_SCOPE_WITHIN_SAME_SESSION,
filter_expression=AudienceFilterExpression(
dimension_or_metric_filter=AudienceFilter(
field_name="eventName",
string_filter=AudienceFilter.StringFilter(value="add_to_cart")
)
)
)
),
# Exclude: purchase
AudienceFilterClause(
clause_type=AudienceFilterClause.AudienceClauseType.EXCLUDE_PERMANENTLY,
simple_filter=AudienceSimpleFilter(
scope=AudienceFilterScope.AUDIENCE_FILTER_SCOPE_ACROSS_ALL_SESSIONS,
filter_expression=AudienceFilterExpression(
dimension_or_metric_filter=AudienceFilter(
field_name="eventName",
string_filter=AudienceFilter.StringFilter(value="purchase")
)
)
)
)
]
)
created_audience = client.create_audience(
parent="properties/987654321",
audience=audience
)
UI 의 audience builder 와 동등 기능. 단 한 번 만들면 대부분 변경 불가 (membership duration · display_name 만 변경 가능).
Conversion Event 등록
from google.analytics.admin_v1beta.types import KeyEvent
# 회원가입 = conversion (Key Event)
key_event = KeyEvent(
event_name="sign_up",
counting_method=KeyEvent.CountingMethod.ONCE_PER_SESSION,
default_value=KeyEvent.DefaultValue(
numeric_value=5000.0,
currency_code="KRW"
)
)
client.create_key_event(
parent="properties/987654321",
key_event=key_event
)
GA4 의 conversion event = 2026 부터 Key Event 로 명명. 동일 개념. — 공식 docs
Property Linking
# BigQuery Export 자동화 (6편의 핵심)
from google.analytics.admin_v1beta.types import BigQueryLink
bq_link = BigQueryLink(
project="our-gcp-project",
daily_export_enabled=True,
streaming_export_enabled=True,
export_streams=["streams/12345"],
excluded_events=["debug_event"],
dataset_location="asia-northeast3" # 서울
)
client.create_big_query_link(
parent="properties/987654321",
big_query_link=bq_link
)
# Google Ads Linking
from google.analytics.admin_v1beta.types import GoogleAdsLink
ads_link = GoogleAdsLink(
customer_id="1234567890",
ads_personalization_enabled=True
)
client.create_google_ads_link(
parent="properties/987654321",
google_ads_link=ads_link
)
property linking = UI 의 클릭 자동화. 새 property 만들 때 동시에 BQ · Ads · Firebase linking 코드 1번.
Access Binding (alpha) — 권한 관리
from google.analytics.admin_v1alpha.types import AccessBinding
# 사용자 권한 추가
binding = AccessBinding(
user="alice@company.com",
roles=["predefinedRoles/read", "predefinedRoles/analyst"]
)
client.create_access_binding(
parent="properties/987654321",
access_binding=binding
)
알파 단계이지만 대규모 권한 관리 에 매우 유용. SAML (인증 표준) /SSO (단일 로그인) 와 결합 시 전사 권한 자동화.
인증 — Service Account vs OAuth
Service Account (서버 자동화)
사용 case:
- 서버 cron job
- dashboard backend
- Slackbot
- ETL pipeline
방식:
1. GCP Console > Service Accounts > 새 service account 생성
2. JSON 키 다운로드 (절대 git X)
3. GA4 Property > Property Access Management > service account email 추가
- Role: Viewer (Data API only) 또는 Editor (Admin API)
import os
from google.analytics.data_v1beta import BetaAnalyticsDataClient
# 환경 변수 또는 명시
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = '/path/to/service-account.json'
client = BetaAnalyticsDataClient()
# 자동 인증 — 별도 코드 X
OAuth (사용자 인증)
사용 case:
- 사용자 본인의 GA 데이터 접근 (SaaS · 분석 도구)
- 한 사람의 여러 property 자동 발견
방식:
1. GCP Console > OAuth Client ID 생성
2. Redirect URI 설정
3. 사용자 → "Sign in with Google" → 사용자 허락
4. OAuth token 으로 client 초기화
대부분 회사 = Service Account. OAuth = 외부 사용자 GA 접근 SaaS (구독형 소프트웨어) 만.
인증 함정
- service account JSON 을 git public 에 박음 → 즉시 GA 데이터 누출
- Property Access 에 service account email 안 추가 → 401 error
- Editor role 안 주고 Admin API 호출 → 403 error
- OAuth scope 부족 (analytics.readonly 만 → Admin API 불가)
해결 — secret 관리 (Secret Manager · Vault) + scope 정확.
Terraform 으로 GA4 관리 (IaC, Infrastructure as Code)
Terraform Provider
terraform {
required_providers {
googleworkspace = {
source = "hashicorp/googleworkspace"
}
}
}
# 또는 별도 GA Provider (community)
provider "googleanalytics" {
credentials = file("service-account.json")
}
Property + Stream 정의
resource "googleanalytics_property" "main" {
account_id = "accounts/123456789"
display_name = "Production Site"
industry_category = "TECHNOLOGY"
time_zone = "Asia/Seoul"
currency_code = "KRW"
}
resource "googleanalytics_data_stream" "web" {
property_id = googleanalytics_property.main.name
display_name = "Production Web"
type = "WEB_DATA_STREAM"
web_data {
default_uri = "https://example.com"
}
}
resource "googleanalytics_custom_dimension" "user_tier" {
property_id = googleanalytics_property.main.name
parameter_name = "user_tier"
display_name = "User Tier"
scope = "USER"
}
resource "googleanalytics_audience" "cart_abandoners" {
property_id = googleanalytics_property.main.name
display_name = "Cart Abandoners 7d"
membership_duration_days = 30
# ... 조건
}
장점 — Property 가 git 으로 추적되고, staging · production 의 일관성이 자동으로 잡힌다. PR review 로 모든 변경을 검토할 수 있고, 재해 복구도 setup 재현으로 끝난다.
단점:
- 일부 resource 의 Provider 미완성 (alpha 기능)
- Audience 같은 복잡한 spec 의 HCL (HashiCorp Configuration Language) 표현 복잡
외부 통합 패턴
Pattern 1: 일 dashboard 자동화
# Cron: 매일 09:00 Seoul
import schedule
from datetime import date, timedelta
def daily_report():
yesterday = (date.today() - timedelta(days=1)).isoformat()
request = RunReportRequest(
property=f"properties/{PROPERTY_ID}",
dimensions=[Dimension(name="country")],
metrics=[
Metric(name="activeUsers"),
Metric(name="sessions"),
Metric(name="totalRevenue"),
Metric(name="conversions")
],
date_ranges=[DateRange(start_date=yesterday, end_date=yesterday)]
)
response = client.run_report(request)
# Slack 보내기
slack_message = format_daily_report(response)
post_to_slack("#analytics", slack_message)
schedule.every().day.at("09:00").do(daily_report)
Pattern 2: Slackbot — "어제 매출은?"
@bot.command("/ga")
async def ga_query(ctx, query: str):
# query = "어제 매출" · "지난 7일 사용자" 등
parsed = parse_natural_query(query) # LLM 기반
request = RunReportRequest(
property=f"properties/{PROPERTY_ID}",
dimensions=parsed["dimensions"],
metrics=parsed["metrics"],
date_ranges=parsed["date_ranges"]
)
response = client.run_report(request)
return format_for_slack(response)
Pattern 3: BI 도구 (Looker · Metabase) 통합
대부분 BI 도구:
- GA4 connector 내장
- 내부적으로 Data API 호출
- 우리가 직접 코드 X
코드 통합 case:
- 회사 자체 BI dashboard
- GA + 사내 데이터베이스 결합 view
# 우리 dashboard 의 backend API
@app.get("/api/dashboard/revenue")
async def get_revenue(start: str, end: str):
# GA4 revenue
ga_response = client.run_report(...)
# 우리 DB 의 실제 매출 결합 (GA 측정 누락 검증)
db_revenue = await db.fetch_revenue(start, end)
return {
"ga_revenue": parse_ga(ga_response),
"db_revenue": db_revenue,
"delta_percent": calculate_delta(...)
}
Pattern 4: 이상 감지 (Anomaly Detection)
# 매시 평소와 다른 패턴 감지
def check_anomalies():
realtime_request = RunRealtimeReportRequest(
property=f"properties/{PROPERTY_ID}",
metrics=[Metric(name="activeUsers")]
)
current = client.run_realtime_report(realtime_request)
# 지난 7일 같은 시각 평균
avg_last_7d = get_baseline(hour=datetime.now().hour)
current_users = int(current.rows[0].metric_values[0].value)
if current_users < avg_last_7d * 0.5:
alert(f"활성 사용자 50% 이하: {current_users} vs {avg_last_7d}")
if current_users > avg_last_7d * 2.0:
alert(f"활성 사용자 2배 (좋은 일 또는 봇): {current_users} vs {avg_last_7d}")
Pattern 5: 회사 dashboard 의 KPI (핵심 성과 지표)
# CEO dashboard — 5초마다 refresh
async def get_company_kpis():
batch = BatchRunReportsRequest(
property=f"properties/{PROPERTY_ID}",
requests=[
# 오늘 매출
RunReportRequest(
metrics=[Metric(name="totalRevenue")],
date_ranges=[DateRange(start_date="today", end_date="today")]
),
# 오늘 신규 가입
RunReportRequest(
dimensions=[Dimension(name="eventName")],
metrics=[Metric(name="eventCount")],
date_ranges=[DateRange(start_date="today", end_date="today")],
dimension_filter=event_filter("sign_up")
),
# 실시간 활성 사용자
# ... 최대 5개 report 한 호출
]
)
response = client.batch_run_reports(batch)
return format_kpis(response)
1 batch 호출 = 5 개 KPI 동시 조회. 매번 refresh 시 5 호출 → 1 호출. quota 절약.
함정 정리
사고 1: Dimension · Metric 호환 안 됨
원인 — transactionId (dimension) + bounceRate (metric) 같은 논리적 불가 조합.
해결 — 공식 카탈로그의 Compatibility chart 확인. 의심 시 작은 query 로 검증 먼저.
사고 2: 페이지네이션 누락 — 데이터 잘림
원인 — limit=10000 만 주고 response.row_count 안 봄 → 큰 데이터 일부만 처리.
해결 — row_count 와 len(rows) 비교. 부족하면 offset 늘려 다시.
사고 3: Realtime 의 dimension 제한
원인 — runRealtimeReport 에 Custom Dimension (user scope) 사용 → error 또는 빈 값.
해결 — Realtime 에서 허용 dimension 만. 공식 docs 의 Realtime-compatible 확인.
사고 4: Property Quota 소진
원인 — 너무 자주 호출 (5초마다 dashboard refresh) → quota 소진 → 429 error.
해결 — cache + batchRunReports + quota header 모니터.
사고 5: Service Account 권한 부족
원인 — Data API 만 박혔는데 Admin API 호출.
해결 — Editor role 또는 Admin role 부여. Admin API 는 Editor 이상.
사고 6: Custom Dimension scope 잘못
원인 — event scope 로 만든 후 user scope 이 더 적합 알아챔. 변경 불가.
해결 — 삭제 후 재생성. 또는 처음부터 scope 신중.
사고 7: Audience 변경 시 데이터 손실
원인 — Audience 의 filter 변경 후 재생성 → 기존 audience 의 history 손실.
해결 — Audience 는 대부분 변경 불가. 새 audience 만들고 기존 audience 유지 + 광고 연결 새 audience 로 마이그.
사고 8: Property Linking 의 순서
원인 — BigQuery Linking 했지만 BQ 의 dataset 권한 부여 X → linking 보이지만 데이터 도착 X.
해결 — Linking + BQ dataset 의 IAM 권한 둘 다.
사고 9: API key 의 service account JSON git commit
원인 — 실수로 service account JSON 을 git commit + push public.
해결 — 즉시 key 폐기 + git 히스토리 정리 (BFG · filter-repo) + Secret Manager · Vault. 이미 노출되었으면 해당 key 사용 history 감사.
사고 10: Terraform 의 audience drift
원인 — Terraform 으로 만든 audience 를 UI 에서 직접 수정 → drift (state ≠ 실제) → 다음 apply 시 복원.
해결 — UI 수정 금지. 모든 변경 Terraform 거쳐. drift detection job + alert.
운영 권장 패턴
Pattern 1: 환경 분리 + IaC
환경:
- dev → Property A
- staging → Property B
- prod → Property C
Terraform:
- terraform/ga4/dev/main.tf
- terraform/ga4/staging/main.tf
- terraform/ga4/prod/main.tf
→ 동일 모듈, 다른 값
→ CI/CD: terraform plan + manual approval + apply
Pattern 2: Quota 모니터
# 매 호출 후 quota 사용량 로그
def log_quota(response):
quota = response.property_quota
logger.info(f"Core tokens remaining: {quota.tokens_per_day.remaining}")
if quota.tokens_per_day.remaining < 1000:
alert("Quota soon depleted!")
Pattern 3: cache 전략
# 같은 query 의 결과 cache
@functools.lru_cache(maxsize=128)
def get_revenue_report(start_date: str, end_date: str):
# 같은 인자 = 같은 결과 → cache hit
return client.run_report(...)
# Redis 사용 시 — 만료 5분
@cache_redis(ttl=300)
def get_realtime_users():
return client.run_realtime_report(...)
Pattern 4: Error retry + backoff
import time
from google.api_core import retry
@retry.Retry(
initial=1.0,
maximum=60.0,
multiplier=2.0,
predicate=retry.if_exception_type(ResourceExhausted, ServiceUnavailable)
)
def query_with_retry(request):
return client.run_report(request)
Pattern 5: 데이터 검증 — DB vs GA
# 매일 GA 매출 vs DB 매출 비교
async def reconcile_revenue(date_str: str):
ga_revenue = await get_ga_revenue(date_str)
db_revenue = await get_db_revenue(date_str)
delta_percent = abs(ga_revenue - db_revenue) / db_revenue * 100
if delta_percent > 5:
alert(
f"GA vs DB 매출 불일치 {date_str}: "
f"GA={ga_revenue}, DB={db_revenue}, delta={delta_percent}%"
)
GA 측정의 실제 비즈니스 데이터 vs 일치 확인 — 측정 누락 · Consent block · ad blocker 검증.
시험 직전 한 번 더 — Data API · Admin API 함정 압축 노트
Data API 4 메서드
runReport— 단일 report (가장 흔함)batchRunReports— 최대 5개 한 호출 (latency 절약)runPivotReport— pivot table (다축 분석)runRealtimeReport— 최근 30분 (Standard) · 60분 (360)
Data API 구조
- Dimension (분석 축) + Metric (측정값) + DateRange (기간) + Filter + OrderBy + Limit + Offset
dimension_filter(행 줄이기) vsmetric_filter(값 조건)- Dimension · Metric 호환 안 되면 error 또는 빈 값
- Realtime 의 dimension 제한 (Custom Dimension 일부 X)
Quota
- Standard Property: Core 25,000 / 일 · Realtime 10,000 / 일
- 360: Core 250,000 / 일 (10배)
- 한 호출이 token 여러 개 소비 (복잡한 query)
response.property_quota로 사용량 확인
Admin API 6 영역
- Account · Property · Stream (구조 관리)
- Custom Dimension · Custom Metric (분석 축)
- Audience CRUD (대부분 변경 불가)
- Conversion (Key Event) (전환 표시)
- Property Linking (BQ · Ads · Firebase · DV360 · SA360)
- Access Binding (alpha — 권한)
Custom Dimension 의 scope 함정
- event · session · user · item 4 layer
- 한 번 만들면 scope 변경 불가
- 삭제 후 재생성만 가능
- 코드 자동화 시 scope 신중
Audience 의 변경 한계
display_name·membership_duration_days만 변경 가능- filter 조건 변경 X
- 변경 필요 시 새 audience 만들고 광고 마이그
인증
- Service Account = 서버 자동화 (cron · backend · bot)
- OAuth = 사용자 인증 (SaaS · 외부 GA 접근)
- JSON key git commit 금지 (Secret Manager · Vault)
- Property Access 에 service account email 추가 필수
권한 role
- Viewer = Data API 만 (report 읽기)
- Analyst = audience · key event 만들기 가능
- Editor = Admin API 전체 + 모든 변경
- Administrator = 사용자 권한 관리 포함
Terraform / IaC
- Provider 일부 alpha 영역 미완성
- staging · production 일관성 + git 추적
- drift = UI 수정 시 발생 (UI 수정 금지)
- terraform plan + approval + apply (CI/CD)
통합 패턴
- 일 dashboard cron (batchRunReports + Slack)
- Slackbot (자연어 → GA query)
- BI 도구 (Looker · Metabase 의 GA connector)
- 이상 감지 (Realtime + baseline)
- KPI batch (5초 refresh의 batch 호출)
사고
- Dimension · Metric 호환 안 됨
- 페이지네이션 누락 (큰 데이터 잘림)
- Realtime dimension 제한 무시
- Quota 소진 (429 error)
- Service Account 권한 부족
- Custom Dimension scope 잘못 (변경 불가)
- Audience 변경 시 history 손실
- Property Linking 의 BQ IAM 권한 누락
- API key git commit (즉시 폐기)
- Terraform drift (UI 수정 금지)
공식 문서: Data API v1 · Admin API v1 에서 client library 와 API 레퍼런스를 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 1편 — GA4 종합 · Event 기반 · 4 측정 방법
- 2편 — 데이터 모델 깊이 · User ID · Session · Custom Dimension
- 3편 — 측정 통합 4 방법 깊이 (gtag · GTM · Firebase · MP)
- 4편 — Event · Conversion · Audience 설계 깊이
다음 글: