본문 바로가기
Infra2026년 7월 28일6분 읽기

Prometheus Exemplar — 메트릭 그래프에서 트레이스로 한 번에 점프하기

YS
김영삼
조회 6
Prometheus Exemplar — 메트릭 그래프에서 트레이스로 한 번에 점프하기

Exemplar(예시자)는 Prometheus 메트릭 샘플에 붙는 작은 꼬리표로, "이 지연 시간 값을 만든 실제 요청의 트레이스 ID가 이거다"라고 알려 주는 링크다. 히스토그램 버킷 하나하나에 대표 요청의 trace_id를 심어 두면, 그래프에서 튀는 점을 클릭해 곧바로 그 요청의 분산 트레이스로 건너뛸 수 있다. 메트릭과 트레이스 사이의 다리인 셈이다.

관측성을 하다 보면 늘 이 벽에 부딪힌다. p99 지연이 튀는 건 그래프로 보이는데, "그래서 어떤 요청이?"에서 막힌다. 로그를 시간대로 뒤지고, 트레이스를 헤매고. Exemplar는 그 사이의 점프를 한 번의 클릭으로 만든다.

왜 히스토그램만으론 부족한가

Prometheus 히스토그램은 집계값이다. "0.5~1초 버킷에 몇 건"까지는 알아도, 그 안의 특정 요청 하나는 알 수 없다. 평균과 분위수는 원인을 지목하지 못한다. Exemplar는 이 집계 안에 개별 요청의 지문(트레이스 ID)을 하나씩 끼워 둬서, 통계에서 개별 사례로 내려가는 통로를 뚫는다.

계측 방법 — 관측 시점에 trace_id를 심는다

핵심은 히스토그램을 관측할 때 현재 컨텍스트의 트레이스 ID를 exemplar 라벨로 함께 넘기는 것이다. Go 클라이언트 예시.

import (
  "github.com/prometheus/client_golang/prometheus"
  "go.opentelemetry.io/otel/trace"
)
hist := prometheus.NewHistogramVec(prometheus.HistogramOpts{
  Name:    "http_request_duration_seconds",
  Buckets: prometheus.DefBuckets,
}, []string{"route"})
// 요청 처리 끝에서
sc := trace.SpanContextFromContext(ctx)
obs := hist.WithLabelValues(route).(prometheus.ExemplarObserver)
obs.ObserveWithExemplar(elapsed.Seconds(), prometheus.Labels{
  "trace_id": sc.TraceID().String(),
})

그리고 노출 형식과 스크레이프 양쪽에서 exemplar를 켜 줘야 한다. exemplar는 OpenMetrics 형식에서만 실리기 때문이다. 노출된 메트릭 줄은 이렇게 생겼다.

http_request_duration_seconds_bucket{route="/pay",le="1.0"} 42 # {trace_id="a1b2c3..."} 0.87 1.699e9

# 뒤가 exemplar다. 대표 요청의 트레이스 ID, 실제 관측값(0.87초), 타임스탬프가 붙는다.

스크레이프·저장 쪽 설정

Prometheus는 기본적으로 exemplar 저장을 꺼 두는 버전이 있으니 스토리지에서 켜야 한다. 그리고 스크레이프 타깃이 OpenMetrics로 노출되도록 프로토콜을 맞춘다.

# prometheus.yml
storage:
  exemplars:
    max_exemplars: 100000   # exemplar용 링버퍼 크기
scrape_configs:
  - job_name: app
    # OpenMetrics 형식이라야 exemplar가 실림
    static_configs: [{ targets: ["app:8080"] }]

Grafana에서 Prometheus 데이터소스에 트레이스 백엔드(Tempo 등)를 연결해 두면, 패널의 exemplar 점을 클릭했을 때 자동으로 해당 트레이스가 열린다. 이 연결(Exemplars → internal link)을 안 걸어 두면 점만 찍히고 클릭해도 아무 일이 안 일어난다. 처음에 나도 이걸로 30분을 날렸다.

체크포인트빠지면 생기는 증상
ObserveWithExemplar로 관측exemplar가 아예 안 생김
OpenMetrics 노출 형식exemplar가 스크레이프에서 탈락
스토리지 exemplar 활성화수집돼도 저장 안 됨
Grafana 트레이스 링크점은 보이나 클릭 이동 안 됨

과하게 쓰지 말 것

exemplar는 대표 표본이지 전수 기록이 아니다. 버킷마다 최신 것 위주로 소수만 유지된다. 그러니 "모든 느린 요청을 exemplar로 다 잡겠다"는 접근은 맞지 않다. 그건 로그나 tail 샘플링의 역할이다. exemplar의 진짜 가치는 대시보드에서 트레이스로 넘어가는 한 번의 클릭, 그 마찰을 없애는 데 있다.

자주 묻는 질문

exemplar와 로그의 trace_id 상관관계는 뭐가 다른가요?

둘 다 트레이스로 연결해 주지만 진입점이 다릅니다. 로그는 "이 에러 메시지"에서 트레이스로 가고, exemplar는 "이 메트릭 그래프의 튀는 점"에서 트레이스로 갑니다. 대시보드를 보다 이상을 발견하는 흐름에서는 exemplar가 훨씬 자연스럽습니다.

exemplar를 켜면 카디널리티가 폭발하지 않나요?

아닙니다. exemplar의 trace_id는 시계열 라벨이 아니라 샘플에 붙는 별도 데이터라, 시계열 수를 늘리지 않습니다. 이것이 trace_id를 일반 라벨로 넣는 것 대비 exemplar의 결정적 장점입니다. 일반 라벨로 넣으면 정말로 카디널리티가 폭발합니다.

클래식 히스토그램 말고 네이티브 히스토그램에서도 되나요?

네, 네이티브(스파스) 히스토그램도 exemplar를 지원합니다. 다만 클라이언트 라이브러리와 Prometheus 버전이 모두 대응해야 하니, 도입 전 버전 호환을 확인하는 게 안전합니다.

exemplar가 그래프엔 안 보이는데 왜 그런가요?

대개 노출 형식이 OpenMetrics가 아니거나, 스토리지에서 exemplar 저장이 꺼져 있어서입니다. 관측 코드가 ObserveWithExemplar인지, 스크레이프가 OpenMetrics로 이뤄지는지, 스토리지 옵션이 켜졌는지 순서대로 점검하세요.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록