안녕하세요! 오늘은 시스템 모니터링과 디버깅의 효율을 한 단계 끌어올려 줄 OpenTelemetry의 강력한 기능, Exemplar에 대해 이야기해보려고 합니다.
📊 "왜?"를 알 수 없었던 메트릭의 한계
시스템을 운영하다 보면 이런 경험, 다들 한 번쯤은 있으실 거예요.
"어? 갑자기 API 응답 시간이 왜 이렇게 치솟았지?" 📈 "에러율이 급증했는데, 도대체 어떤 요청들에서 문제가 생긴 거지?" 🤔
우리는 보통 대시보드에서 위와 같은 메트릭(Metric) 그래프를 보며 문제를 인지합니다. 레이턴시, 에러율, CPU 사용량 같은 집계된 데이터는 시스템의 전반적인 상태를 파악하는 데 아주 유용하죠.
하지만 메트릭은 '무엇(What)'이 일어났는지는 알려주지만, '왜(Why)' 일어났는지는 알려주지 못하는 경우가 많습니다. 급증한 레이턴시 스파이크를 보고도, 그 원인이 된 특정 요청을 찾기 위해 수많은 로그와 트레이스를 뒤져야 하는 막막한 상황에 부딪히곤 합니다. 😵

🔗 메트릭과 트레이스를 이어주는 다리, Exemplar!
바로 이럴 때, Exemplar가 구원투수처럼 등장합니다!
Exemplar의 핵심 기능은 바로 이것입니다:
특정 메트릭 값을 그것을 유발한 구체적인 트레이스(Trace) 샘플과 직접 연결해주는 것!
쉽게 말해, '문제를 일으킨 바로 그 녀석'의 정보를 메트릭 데이터에 콕 집어 함께 기록해주는 기능입니다.
예를 들어볼까요?
- 레이턴시 스파이크 발생: P99 레이턴시가 200ms에서 갑자기 5000ms로 치솟는 것을 메트릭 그래프에서 발견합니다.
- Exemplar 확인: 이 5000ms라는 메트릭 데이터 포인트를 확인해보니, Exemplar가 함께 기록되어 있습니다. 이 Exemplar 안에는 당시 가장 느렸던 요청의 trace_id와 span_id가 담겨 있습니다.
- 원인 추적: 우리는 이 trace_id를 이용해 옵저버빌리티 플랫폼(Jaeger, Grafana 등)에서 해당 요청의 전체 분산 추적(Distributed Trace) 정보를 단 한 번의 클릭으로 찾아볼 수 있습니다. 🖱️
- 문제 해결: 트레이스를 분석해보니, 특정 외부 API 호출에서 타임아웃이 발생했거나, 특정 데이터베이스 쿼리가 비정상적으로 느렸다는 사실을 바로 확인할 수 있습니다.
Exemplar가 없었다면 우리는 스파이크가 발생한 시간대를 기준으로 수많은 로그와 트레이스 더미 속에서 원인이 될 만한 요청을 '추측'하며 찾아 헤맸을 것입니다. 하지만 Exemplar 덕분에 추측의 영역이 확인의 영역으로 바뀌게 됩니다.
✨ Exemplar가 제공하는 놀라운 가치
Exemplar를 사용하면 다음과 같은 이점을 얻을 수 있습니다.
- 풍부한 컨텍스트 제공: 단순한 숫자(메트릭)에 '이야기(트레이스)'를 더해줍니다. "레이턴시가 5초"라는 사실을 넘어, "A 사용자의 주문 요청이 B 데이터베이스의 느린 쿼리 때문에 5초 걸렸다"는 구체적인 컨텍스트를 얻게 됩니다. 📜
- 획기적으로 빠른 디버깅: 문제의 원인이 된 요청 예시를 직접 확인할 수 있으므로, 근본 원인을 파악하고 해결하는 데 걸리는 시간이 극적으로 단축됩니다. 🎯
- 집계 데이터의 함정 탈피: 평균값이나 백분위수 같은 집계 데이터는 개별적인 이상치(outlier)를 가릴 수 있습니다. Exemplar는 이런 숨겨진 이상치를 수면 위로 드러내 줍니다.
📊 프로메테우스 Exemplar 파이썬 예제
가장 일반적인 사용 사례인 프로메테우스의 파이썬 클라이언트 라이브러리(prometheus_client)를 사용한 예제입니다.
1. 카운터(Counter) 메트릭에 Exemplar 추가하기
요청이 들어올 때마다 숫자를 세는 카운터에 어떤 요청이었는지 Trace ID를 함께 기록하는 예제입니다.
from prometheus_client import Counter
import time
import random
import string
# 'my_requests_total'이라는 이름의 카운터 메트릭을 생성합니다.
c = Counter('my_requests_total', 'Total number of HTTP requests')
def generate_trace_id(length=10):
"""간단한 랜덤 Trace ID 생성 함수"""
return ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length))
# 요청을 시뮬레이션하며 Exemplar 추가
for _ in range(5):
# 각 요청마다 고유한 Trace ID를 생성합니다.
trace_id = generate_trace_id()
# .inc() 메소드를 호출할 때 exemplar 딕셔너리를 전달합니다.
# 이 Trace ID가 메트릭 데이터에 꼬리표처럼 붙게 됩니다.
c.inc(exemplar={'trace_id': trace_id})
print(f"Request handled with trace_id: {trace_id}")
time.sleep(1)
코드 설명:
- Counter를 사용하여 my_requests_total이라는 이름의 메트릭을 만듭니다.
- 요청이 처리될 때마다 c.inc()를 호출하여 카운터를 1씩 증가시킵니다.
- 이때 exemplar 인자로 {'trace_id': '...'}와 같은 딕셔너리를 전달하면, 해당 시점의 메트릭 데이터에 이 정보가 연결됩니다.
2. 히스토그램(Histogram) 메트릭에 Exemplar 추가하기
요청의 지연 시간(latency) 분포를 추적하는 히스토그램에 특정 요청의 Trace ID를 기록하는 예제입니다.
from prometheus_client import Histogram
import time
import random
import string
# 'request_latency_seconds'라는 이름의 히스토그램 메트릭을 생성합니다.
h = Histogram('request_latency_seconds', 'Request latency')
def generate_trace_id(length=10):
"""간단한 랜덤 Trace ID 생성 함수"""
return ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length))
# 요청 지연 시간을 시뮬레이션하며 Exemplar 추가
for _ in range(5):
latency = random.uniform(0.1, 1.5) # 0.1초에서 1.5초 사이의 랜덤 지연 시간
trace_id = generate_trace_id()
# .observe() 메소드로 지연 시간을 기록할 때 exemplar 딕셔너리를 전달합니다.
h.observe(latency, exemplar={'trace_id': trace_id})
print(f"Observed latency {latency:.2f}s with trace_id: {trace_id}")
time.sleep(1)
코드 설명:
- Histogram을 사용하여 request_latency_seconds라는 이름의 메트릭을 만듭니다.
- 요청이 완료되면 h.observe(latency)를 호출하여 측정된 지연 시간을 기록합니다.
- 마찬가지로 exemplar 인자에 trace_id를 담아 전달하면, 특정 지연 시간 값과 해당 요청의 Trace가 연결됩니다.
🧐 Exemplar는 어떻게 보이나요?
위 코드를 실행하고 프로메테우스가 수집하는 데이터를 살펴보면 (OpenMetrics 형식), 다음과 같이 메트릭 끝에 # {trace_id="..."} 값 시간 형태의 Exemplar 정보가 추가된 것을 볼 수 있습니다.
# TYPE my_requests_total counter
my_requests_total_total 5.0 # {trace_id="a1b2c3d4e5"} 5.0 1665789605.123
이 정보를 그라파나(Grafana)와 같은 시각화 도구에서 보면, 그래프의 특정 데이터 포인트 위에 점으로 표시되며, 클릭 시 연결된 Trace ID를 통해 해당 시점의 상세 로그나 트레이스 데이터로 바로 이동할 수 있습니다.
마치며
OpenTelemetry의 Exemplar는 단순히 또 하나의 기능이 아닙니다. 메트릭과 트레이스라는 두 중요한 데이터를 유기적으로 연결하여, 우리가 시스템을 이해하고 문제를 해결하는 방식을 근본적으로 바꾸는 강력한 도구입니다. 🛠️
여러분의 모니터링 시스템에서 "왜?"라는 질문에 답을 찾기 어려웠다면, 이제 Exemplar를 통해 데이터 기반의 빠르고 정확한 디버깅을 경험해보세요! 💪
'클라우드 > opentelemetry' 카테고리의 다른 글
| OpenTelemetry Collector의 숨겨진 비밀 🤫: service::telemetry::metrics 완전 정복 (0) | 2025.10.14 |
|---|---|
| 🧭 OpenTelemetry SDK의 심장을 파헤치다: 4가지 핵심 컴포넌트 완벽 가이드 (0) | 2025.10.14 |
| OpenTelemetry Collector, 여러 백엔드로 데이터를 똑똑하게 분산하는 방법 ⚖️ (0) | 2025.10.14 |
| OpenTelemetry 로그와 트레이스의 완벽한 연결고리: Log Bridge API 톺아보기 🌉 (0) | 2025.10.13 |
| 🚀 OpenTelemetry Collector의 숨겨진 능력: gRPC와 HTTP 동시 사용의 비밀! (0) | 2025.10.13 |