OpenTelemetry 분산 추적: Trace·Span·context 전파와 Collector

상위: 00.Observability MOC

개요

마이크로서비스나 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