전체 비유: 택배 추적 시스템#

분산 추적을 택배 배송 추적에 비유하면 이해하기 쉽습니다:

택배 추적 비유분산 추적역할
운송장 번호Trace ID전체 요청의 고유 식별자
배송 구간별 스캔Span개별 작업 단위 기록
“물류센터 → 허브”Parent-Child Span구간 간 연결 관계
각 구간 소요 시간Duration작업 처리 시간
배송 상태 “집하/배송중/완료”Status성공/실패 상태
물류 센터 간 운송장 전달Context Propagation서비스 간 추적 정보 전달
지연 구간 표시Latency 분석병목 구간 식별
배송 사고 이력Error Span실패 지점 추적

이처럼 택배를 추적하듯, 분산 추적은 “요청이 어떤 서비스를 거쳐 어디서 지연됐는지"를 추적합니다.


대상 독자: 마이크로서비스를 운영하는 개발자, SRE 선수 지식: 관측성 3요소 소요 시간: 약 25-30분 이 문서를 읽으면: 분산 추적을 이해하고 서비스 간 요청 흐름을 분석할 수 있습니다

TL;DR#

핵심 요약:

  • Trace: 하나의 요청 전체 경로 (여러 Span으로 구성)
  • Span: 단일 작업 단위 (시작/종료 시간, 메타데이터)
  • Context Propagation: 서비스 간 Trace ID 전달
  • 샘플링: 전체 트레이스 중 일부만 저장 (비용 최적화)

왜 분산 추적이 필요한가?#

마이크로서비스에서는 하나의 요청이 여러 서비스를 거칩니다. 어디서 지연이 발생했는지 파악하기 어렵습니다.

graph LR
    USER["사용자"] --> GW["API Gateway"]
    GW --> ORDER["주문 서비스"]
    ORDER --> PAYMENT["결제 서비스"]
    ORDER --> INVENTORY["재고 서비스"]
    PAYMENT --> DB1["결제 DB"]
    INVENTORY --> DB2["재고 DB"]

문제: 응답이 느린데 어디가 문제인지 모름

해결: 분산 추적으로 각 구간 소요 시간 확인


핵심 개념#

Trace와 Span#

graph TB
    subgraph "Trace (전체 요청)"
        S1["Span: API Gateway<br>0-250ms"]
        S2["Span: Order Service<br>10-200ms"]
        S3["Span: Payment Service<br>20-180ms"]
        S4["Span: Payment DB<br>30-150ms"]
    end

    S1 --> S2
    S2 --> S3
    S3 --> S4
용어설명
Trace전체 요청 경로 (고유 Trace ID)
Span개별 작업 단위 (고유 Span ID)
Parent Span현재 Span을 호출한 상위 Span
Root Span첫 번째 Span (Parent 없음)

Span 구조#

{
  "traceId": "abc123def456",
  "spanId": "span001",
  "parentSpanId": null,
  "operationName": "HTTP GET /orders",
  "serviceName": "order-service",
  "startTime": 1704700800000,
  "duration": 245,
  "tags": {
    "http.method": "GET",
    "http.status_code": 200,
    "http.url": "/orders/123"
  },
  "logs": [
    {
      "timestamp": 1704700800100,
      "message": "Fetching order from database"
    }
  ]
}

Context Propagation#

서비스 간 Trace ID를 전달하는 방법입니다.

sequenceDiagram
    participant A as Service A
    participant B as Service B
    participant C as Service C

    A->>B: HTTP Request<br>traceparent: 00-abc123-span1-01
    Note over B: Extract context<br>Create child span
    B->>C: HTTP Request<br>traceparent: 00-abc123-span2-01
    Note over C: Extract context<br>Create child span

W3C Trace Context 형식:

traceparent: 00-{trace-id}-{span-id}-{flags}
traceparent: 00-abc123def456789-fedcba987654321-01

도구 비교#

도구특징적합한 경우
JaegerCNCF 프로젝트, UI 우수Kubernetes 환경
Zipkin가벼움, 쉬운 설치빠른 시작
TempoGrafana 통합, 저비용Grafana 사용 시
AWS X-RayAWS 통합AWS 환경

아키텍처 (Jaeger)#

graph TB
    APP["Application"] --> |"spans"| AGENT["Jaeger Agent"]
    AGENT --> COLLECTOR["Jaeger Collector"]
    COLLECTOR --> STORAGE["Storage<br>(Elasticsearch/Cassandra)"]
    STORAGE --> QUERY["Jaeger Query"]
    QUERY --> UI["Jaeger UI"]

Spring Boot 설정#

의존성 추가#

// build.gradle.kts
dependencies {
    implementation("io.micrometer:micrometer-tracing-bridge-otel")
    implementation("io.opentelemetry:opentelemetry-exporter-otlp")
}

application.yml#

management:
  tracing:
    sampling:
      probability: 1.0  # 개발: 100%, 운영: 0.1 (10%)
  otlp:
    tracing:
      endpoint: http://jaeger:4318/v1/traces

logging:
  pattern:
    level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"

수동 Span 생성#

@Service
@RequiredArgsConstructor
public class OrderService {
    private final Tracer tracer;

    public Order processOrder(OrderRequest request) {
        Span span = tracer.nextSpan().name("processOrder").start();
        try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
            span.tag("order.type", request.getType());
            span.event("Processing started");

            Order order = createOrder(request);

            span.event("Order created");
            return order;
        } finally {
            span.end();
        }
    }
}

샘플링 전략#

모든 트레이스를 저장하면 비용이 급증합니다. 샘플링으로 비용을 최적화합니다.

샘플링 방식#

방식설명적합한 경우
확률 샘플링일정 비율만 수집일반적
Rate Limiting초당 N개만 수집트래픽 급증 시
Tail-based에러/느린 요청 우선문제 분석 중심

권장 샘플링률#

환경샘플링률이유
개발100%모든 요청 추적
스테이징50%충분한 데이터
운영1-10%비용 최적화

에러 시 100% 수집#

# OpenTelemetry Collector 설정
processors:
  tail_sampling:
    policies:
      - name: errors
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: slow
        type: latency
        latency:
          threshold_ms: 1000
      - name: probabilistic
        type: probabilistic
        probabilistic:
          sampling_percentage: 10

로그/메트릭 연결#

Trace ID로 연결#

graph LR
    METRIC["Metric Alert<br>에러율 급증"]
    LOG["Logs<br>trace_id로 검색"]
    TRACE["Trace<br>상세 흐름"]

    METRIC --> |"시간대 확인"| LOG
    LOG --> |"trace_id 추출"| TRACE

로그에 Trace ID 포함#

// Spring Boot 자동 포함
// 로그 패턴: %X{traceId:-}

// 로그 출력 예시
2026-01-12 10:30:00 INFO [order-service,abc123def456,span001] Order created: 12345

Grafana에서 연결#

1. 대시보드에서 에러율 급증 확인
2. Explore → Loki로 이동
3. {service="order-service"} |= "ERROR" 검색
4. 로그에서 trace_id 클릭
5. Tempo/Jaeger로 전체 트레이스 확인

분석 패턴#

병목 구간 찾기#

Trace 분석:
├─ API Gateway (10ms) ✓
├─ Order Service (50ms) ✓
│   ├─ Validation (5ms) ✓
│   └─ Payment Call (2000ms) ← 병목!
│       └─ Payment DB (1800ms) ← 근본 원인
└─ Response (5ms) ✓

에러 추적#

Trace 분석:
├─ API Gateway (10ms) ✓
├─ Order Service (50ms) ✗ Error
│   ├─ Inventory Check (200ms)
│   └─ Error: "Insufficient stock"

서비스 의존성 맵#

Jaeger/Tempo에서 서비스 간 연결 시각화:

graph LR
    GW["API Gateway"] --> ORDER["Order"]
    GW --> USER["User"]
    ORDER --> PAYMENT["Payment"]
    ORDER --> INVENTORY["Inventory"]
    ORDER --> NOTIFICATION["Notification"]
    PAYMENT --> PAYMENT_DB["Payment DB"]
    INVENTORY --> INVENTORY_DB["Inventory DB"]

알림 규칙#

트레이스 기반 알림#

# Prometheus alerting rule (Tempo 연동)
groups:
  - name: tracing
    rules:
      - alert: HighTraceErrorRate
        expr: |
          sum(rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[5m]))
          / sum(rate(traces_spanmetrics_calls_total[5m]))
          > 0.05
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High trace error rate"

      - alert: SlowSpans
        expr: |
          histogram_quantile(0.99, sum(rate(traces_spanmetrics_latency_bucket[5m])) by (le, service))
          > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "P99 span latency > 2s"

핵심 정리#

개념설명
Trace전체 요청 경로
Span개별 작업 단위
Context서비스 간 전달되는 추적 정보
Sampling비용 최적화를 위한 선별 수집

구현 체크리스트:

  • OpenTelemetry SDK 추가
  • 샘플링률 설정
  • 로그에 trace_id 포함
  • Jaeger/Tempo 배포
  • Grafana 연동

관련 문서#

다음 단계#

추천 순서문서배우는 것
1OpenTelemetry표준화된 계측
2풀스택 예제통합 실습