본문 바로가기
클라우드/opentelemetry

OpenTelemetry Exemplar: 메트릭 스파이크의 원인을 찾아 떠나는 여행 🚀

by gasbugs 2025. 10. 14.

안녕하세요! 오늘은 시스템 모니터링과 디버깅의 효율을 한 단계 끌어올려 줄 OpenTelemetry의 강력한 기능, Exemplar에 대해 이야기해보려고 합니다.

📊 "왜?"를 알 수 없었던 메트릭의 한계

시스템을 운영하다 보면 이런 경험, 다들 한 번쯤은 있으실 거예요.

"어? 갑자기 API 응답 시간이 왜 이렇게 치솟았지?" 📈 "에러율이 급증했는데, 도대체 어떤 요청들에서 문제가 생긴 거지?" 🤔

 

우리는 보통 대시보드에서 위와 같은 메트릭(Metric) 그래프를 보며 문제를 인지합니다. 레이턴시, 에러율, CPU 사용량 같은 집계된 데이터는 시스템의 전반적인 상태를 파악하는 데 아주 유용하죠.

 

하지만 메트릭은 '무엇(What)'이 일어났는지는 알려주지만, '왜(Why)' 일어났는지는 알려주지 못하는 경우가 많습니다. 급증한 레이턴시 스파이크를 보고도, 그 원인이 된 특정 요청을 찾기 위해 수많은 로그와 트레이스를 뒤져야 하는 막막한 상황에 부딪히곤 합니다. 😵

 

 

🔗 메트릭과 트레이스를 이어주는 다리, Exemplar!

바로 이럴 때, Exemplar가 구원투수처럼 등장합니다!

Exemplar의 핵심 기능은 바로 이것입니다:

특정 메트릭 값을 그것을 유발한 구체적인 트레이스(Trace) 샘플과 직접 연결해주는 것!

 

쉽게 말해, '문제를 일으킨 바로 그 녀석'의 정보를 메트릭 데이터에 콕 집어 함께 기록해주는 기능입니다.

예를 들어볼까요?

  1. 레이턴시 스파이크 발생: P99 레이턴시가 200ms에서 갑자기 5000ms로 치솟는 것을 메트릭 그래프에서 발견합니다.
  2. Exemplar 확인: 이 5000ms라는 메트릭 데이터 포인트를 확인해보니, Exemplar가 함께 기록되어 있습니다. 이 Exemplar 안에는 당시 가장 느렸던 요청의 trace_id와 span_id가 담겨 있습니다.
  3. 원인 추적: 우리는 이 trace_id를 이용해 옵저버빌리티 플랫폼(Jaeger, Grafana 등)에서 해당 요청의 전체 분산 추적(Distributed Trace) 정보를 단 한 번의 클릭으로 찾아볼 수 있습니다. 🖱️
  4. 문제 해결: 트레이스를 분석해보니, 특정 외부 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를 통해 데이터 기반의 빠르고 정확한 디버깅을 경험해보세요! 💪