OpenTelemetry 분산 추적: Trace·Span·context 전파와 Collector
개요
마이크로서비스나 LLM 에이전트에서 하나의 사용자 요청은 여러 서비스·모델 호출을 거친다. “왜 이 요청이 느렸나”에 답하려면, 흩어진 로그를 사람이 눈으로 잇는 대신 요청 하나를 처음부터 끝까지 하나의 흐름으로 묶어 봐야 한다. **분산 추적(distributed tracing)**이 그 일을 하고, **OpenTelemetry(OTel)**는 이를 위한 벤더 중립 표준이다. CNCF 프로젝트로 traces·metrics·logs 세 신호를 하나의 SDK·프로토콜(OTLP)로 다루며, 특정 관측성 벤더에 종속되지 않게 계측 코드를 표준화한다. Prometheus와 Grafana가 “얼마나(집계 숫자)“라면, OTel 추적은 “어디서·왜(개별 요청 경로)“를 답한다.
원리와 핵심 개념
Trace와 Span
- Trace: 한 요청의 전체 여정. 고유한
trace_id로 식별된다. - Span: trace를 구성하는 단위 작업(HTTP 핸들러, DB 쿼리, LLM 호출 등). 각 span은
span_id, 시작·종료 시각, 부모 span(parent_id), 그리고 attributes(키-값 메타데이터), events(시점 로그), status(성공/오류)를 갖는다.
Span들은 부모-자식 관계로 트리를 이루고, 이를 시간축에 펼치면 어느 구간이 병목인지 한눈에 보이는 워터폴 뷰가 된다.
Trace (trace_id=abc123)
└─ span: POST /chat [1200ms]
├─ span: retrieve_context [ 300ms] ← 벡터 검색
│ └─ span: pgvector.query [ 250ms]
└─ span: llm.generate [ 850ms] ← 병목
Context propagation
핵심 난제는 span이 프로세스·서비스 경계를 넘어도 같은 trace로 묶여야 한다는 것이다. 이를 위해 OTel은 W3C Trace Context 표준의 traceparent HTTP 헤더로 trace_id와 부모 span_id를 전파한다.
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ └ trace_id (32 hex) ────────────┘ └ span_id ────┘ └ flags
version
서비스 A가 B를 호출할 때 이 헤더를 실어 보내면, B는 헤더에서 trace_id를 이어받아 자신의 span을 같은 trace의 자식으로 만든다. 메시지 큐·gRPC 등도 동일한 원리로 전파한다. 이 전파 사슬이 끊기면 trace가 조각나므로, 자동 계측(auto-instrumentation) 라이브러리가 프레임워크 수준에서 헤더 주입/추출을 자동 처리하는 것이 중요하다.
OTel Collector
Collector는 계측된 앱과 백엔드 사이에 놓이는 벤더 중립 파이프라인이다. 세 단계 구조를 갖는다.
| 단계 | 역할 | 예 |
|---|---|---|
| Receiver | 데이터 수집 | OTLP(gRPC/HTTP), Prometheus, Jaeger |
| Processor | 가공 | batch, 샘플링, 속성 편집, PII 삭제 |
| Exporter | 라우팅 | Jaeger, Tempo, Prometheus, Langfuse |
Collector를 두면 (1) 앱은 OTLP로만 내보내고 백엔드 교체는 Collector 설정만 바꾸면 되며(벤더 중립), (2) 샘플링·PII 마스킹·배칭 같은 공통 처리를 중앙집중하고, (3) 여러 백엔드로 fan-out 할 수 있다.
실전: 구성과 계측
[앱 + OTel SDK/자동계측]
│ OTLP (traceparent 전파)
▼
[OTel Collector] receiver → processor(batch·sampling·PII) → exporter
│ │ │
▼ ▼ ▼
[Tempo/Jaeger] [Prometheus] [Langfuse] ← 벤더 중립 fan-out
(트레이스) (메트릭) (LLM 특화)
# 애플리케이션 계측 (Python)
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="collector:4317"))
)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("chat-service")
def handle_chat(query):
with tracer.start_as_current_span("chat.handle") as span:
span.set_attribute("chat.query_len", len(query))
ctx = retrieve(query) # 자식 span 자동 생성(자동계측 시)
with tracer.start_as_current_span("llm.generate") as s:
s.set_attribute("gen_ai.request.model", "claude")
return generate(ctx, query)# otel-collector.yaml
receivers: { otlp: { protocols: { grpc: {}, http: {} } } }
processors:
batch: {}
attributes: # PII 제거 예시
actions: [ { key: user.email, action: delete } ]
exporters:
otlp/tempo: { endpoint: tempo:4317 }
service:
pipelines:
traces: { receivers: [otlp], processors: [batch, attributes], exporters: [otlp/tempo] }LLM·에이전트 호출 체인 추적
RAG·멀티에이전트 앱에서는 “검색 → 리랭킹 → 프롬프트 구성 → LLM 호출 → 툴 실행”이 하나의 trace로 이어진다. 각 단계를 span으로 남기면 지연·실패의 원인(느린 벡터 검색인지, 긴 생성인지, 툴 타임아웃인지)이 워터폴에 그대로 드러난다. OTel은 이를 위한 GenAI semantic conventions(gen_ai.* 속성: model, token 수, temperature 등)를 정의한다. LLM 특화 관측(프롬프트/응답 원문, 토큰 비용, 평가 점수)은 Langfuse 같은 백엔드를 Collector exporter로 붙여 결합한다. 즉 인프라 추적은 Tempo/Jaeger로, LLM 도메인 추적은 Langfuse로 fan-out 하는 구성이 흔하다(RAG 파이프라인 - 청킹부터 생성까지).
함정과 베스트프랙티스
| 함정 | 문제 | 대응 |
|---|---|---|
| 100% 샘플링 | trace 폭증·비용·스토리지 | tail/head 샘플링(예: 1~10%) |
| 전파 사슬 끊김 | trace가 조각남 | 자동계측 + 큐/gRPC 전파 확인 |
| 프롬프트 원문 무필터 | PII·비밀 유출 | Collector processor로 마스킹 |
| 고카디널리티 attribute | 저장·질의 폭발 | id는 이벤트로, 라벨은 유한값 |
| span 과다 생성 | 노이즈로 병목이 묻힘 | 의미 있는 경계만 span |
- 에러 trace는 우선 샘플링: 정상은 낮게, 오류·고지연 trace는 tail-sampling으로 100% 보존한다.
- 메트릭과 exemplar 연동: p95 스파이크에서 대표 trace로 바로 점프하도록 exemplar를 건다.
- 자동계측 먼저, 수동 span은 도메인 경계에만: 프레임워크는 자동, 비즈니스 의미가 있는 구간만 손으로 계측한다.
정리
OpenTelemetry는 Trace(요청 전체)를 Span(단위 작업)의 트리로 표현하고, traceparent 헤더로 서비스 경계를 넘어 context를 전파해 흩어진 작업을 하나로 잇는다. Collector(receiver→processor→exporter)가 수집·가공·라우팅을 중앙집중하며 벤더 중립성과 PII 마스킹·샘플링을 제공한다. RAG·에이전트에서는 호출 체인을 span으로 남기고 Langfuse를 결합해 LLM 도메인 추적까지 확장한다. 집계 지표는 Prometheus와 Grafana, LLM 파이프라인 맥락은 RAG 파이프라인 - 청킹부터 생성까지와 함께 본다.
키워드: #opentelemetry #observability #tracing span traceparent collector langfuse