관측성

메트릭 설계 안티패턴

meellon 2026. 6. 25. 05:50

학습 목표

cardinality(시계열 수)가 Prometheus·Grafana 성능·비용에 미치는 영향을 설명할 수 있다.

user_id·trace_id·무제한 path 같은 라벨 안티패턴을 구분할 수 있다.

RED에 맞는 저카디널리티 라벨 설계 원칙을 적용할 수 있다.

metric_relabel·recording rule·aggregation으로 폭발을 완화하는 방법을 설명할 수 있다.

문제 상황

  • /metrics endpoint가 수 MB — scrape 타임아웃
    • 라벨 unique 값이 수십만
  • Grafana 대시보드 로딩이 30초 — PromQL sum by가 TSDB 전체 스캔
    • http_requests_total{path="/users/12345"} per-user 라벨
  • 배포 후 Prometheus 메모리가 8GB → 32GB
    • version·pod·instance 외에 order_id 추가
  • trace_id를 metric label로 넣었다가 TSDB crash
    • 무한에 가까운 시계열 — 로그/트레이스 영역 침범
  • 팀마다 라벨 이름이 달라 join 불가
    • status vs http_status vs code

Grafana·PromQL까지 갖췄다. 이번 편은 메트릭을 망가뜨리는 설계고치는 법이다.

1. Cardinality란

Cardinality(metric name + label set) 조합마다 생기는 시계열 개수.

시계열 수 ≈ metric × label1_values × label2_values × …
요소
metric name http_requests_total
label set {job="api", method="GET", status="200"}
새 unique 조합 새 시계열 — 메모리·인덱스·쿼리 비용
규모 체감
< 10k series 소규모 — 여유
100k ~ 1M 쿼리·compact 느려짐
> 1M scrape 지연, OOM, alert miss
  • counter·histogram 버킷도 series를 늘린다 — _bucket{le="0.1"}
  • 앞서 메트릭 기초에서 라벨 주의를 언급 — 이번 편에서 운영 사고로 연결

2. 라벨 안티패턴

안티패턴 왜 나쁜가 대안
user_id, order_id 사용자·주문마다 series 로그·트레이스 attribute
trace_id, request_id 요청마다 series exemplar·로그 correlation
URL path with ID /users/123 무한 path route template /users/:id
unbounded enum error_message="..." error_type="ValidationError"
timestamp 라벨 매 scrape 새 series timestamp는 sample
high-cardinality IP client IP 전부 /24 aggregate 또는 로그
# BAD — cardinality bomb
http_requests_total{user_id="u-9281", path="/api/v1/orders/55102"} 1

# GOOD — bounded dimensions
http_requests_total{route="/api/v1/orders/:id", method="POST", status="201"} 1
instrument 할 것 instrument 하지 말 것
service, job, env 개별 tenant (수천 이상)
method, status class (2xx/5xx) raw status 600종 (가능하면 class)
route template full query string
  • REDrate·errors·duration — 차원은 서비스 경계 수준
  • 디버그용 일시 high-card metric은 별도 job + 짧은 retention (비권장 기본)

3. 좋은 라벨 설계

1. 질문을 먼저 — "어떤 PromQL·알람에 쓸 라벨인가?"
2. bounded 값만 — enum·template·status class
3. naming convention — snake_case, Prometheus exporter 관례
4. cardinality 예산 — 서비스당 목표 series 수 (예: 5k)
5. code review — /metrics PR에 라벨 diff
라벨 용도
job, instance scrape target (Prometheus 부여)
service, team ownership·대시보드 변수
env, region staging/prod 분리
method, route, status RED drill-down
# exposition (요지) — OpenMetrics/Prometheus text format
# TYPE http_requests_total counter
http_requests_total{service="api",method="GET",route="/health",status="200"} 42
http_request_duration_seconds_bucket{service="api",route="/health",le="0.1"} 38
http_request_duration_seconds_bucket{service="api",route="/health",le="0.5"} 41
http_request_duration_seconds_bucket{service="api",route="/health",le="+Inf"} 42
http_request_duration_seconds_sum{service="api",route="/health"} 3.2
http_request_duration_seconds_count{service="api",route="/health"} 42
  • histogram bucket 수는 10~20 이내 — p99 목표에 맞춰 le 선택
  • code/bad-metrics.exposition, good-metrics.exposition 대비

4. 완화 — relabel · aggregate · recording

scrape / metric relabel

# prometheus.yml — drop high-card label at ingest
scrape_configs:
  - job_name: api
    metric_relabel_configs:
      - source_labels: [user_id]
        regex: .+
        action: labeldrop
      - source_labels: [path]
        regex: '/users/[0-9]+'
        target_label: route
        replacement: '/users/:id'
action 용도
labeldrop 위험 라벨 수집 전 제거
labelkeep allowlist만 유지
replace path → route 정규화

recording rule

# rules/recording.yml
groups:
  - name: api_agg
    interval: 30s
    rules:
      - record: job:http_requests:rate5m
        expr: sum by (job, status) (rate(http_requests_total[5m]))
  • 대시보드·알람은 aggregated metric만 조회 — raw high-card 드물게
  • 앞서 Grafana SLO 패널 — recording으로 job:error_rate5m pre-compute

기타

방법 설명
federation / remote write tier별 보관 — raw short, agg long
logs for detail breakdown은 LogQL — 메트릭은 요약
exemplars histogram + trace link — trace_id 라벨 아님

5. 관측·거버넌스

# TSDB head series (Prometheus 2.x)
prometheus_tsdb_head_series

# top cardinality by metric name (예시 패턴)
topk(20, count by (__name__) ({__name__=~".+"}))
실무  
PR checklist 새 라벨·새 metric — cardinality estimate
lint promtool check rules, Grafana dashboard review
budget alert prometheus_tsdb_head_series > threshold
postmortem cardinality spike 원인·relabel timeline
# exposition 크기 확인
curl -s localhost:8080/metrics | wc -c

# unique series per metric (rough)
curl -s localhost:8080/metrics | grep -v '^#' | cut -d'{' -f1 | sort | uniq -c | sort -rn | head

6. 실무 체크리스트

항목 권장
라벨 bounded — route template, status class
ID 로그·트레이스 — metric label 금지
histogram bucket 최소 — native histogram 검토 (고급)
relabel ingest에서 drop/normalize
dashboard sum by (job, status) — raw instance 나열 지양
ownership 팀별 series budget
안티패턴  
debug label in prod user_id, trace_id
copy-paste path /a/1, /a/2
metric per feature flag value flag 수 × route 폭발
no cardinality review 배포 후 TSDB OOM

7. 정리

  • cardinality = 시계열 수 — Prometheus·Grafana 성능의 핵
  • user_id·trace_id·raw path — 메트릭이 아니라 로그/트레이스 영역
  • RED 라벨은 서비스·route template·status 수준
  • relabel·recording rule·aggregation으로 완화
  • Part 2 메트릭 마무리 — 다음 분산 트레이싱 (span, trace, propagation)

다음에 다룰 것

  • 분산 트레이싱 개요
  • span, trace, context propagation

해당 내용은 Observability Engineering (Charity Majors, Liz Fong-Jones, George Miranda) 및 Prometheus Documentation (prometheus.io/docs) 의 내용을 기반으로 합니다.

'관측성' 카테고리의 다른 글

OpenTelemetry  (0) 2026.06.27
분산 트레이싱 개요  (0) 2026.06.26
Grafana  (0) 2026.06.24
Prometheus  (0) 2026.06.23
메트릭 기초  (0) 2026.06.22