Trace

Trace는 분산 시스템에서 요청의 흐름을 추적하고 시각화하는 데 사용된다.

Trace는 분산 시스템에서 요청이나 트랜잭션이 여러 서비스와 컴포넌트를 통과하는 전체 여정을 기록한 것이다.
각 Trace는 하나 이상의 span으로 구성되며, 첫 번째 span은 root span이라고 한다.

Trace의 목적

  1. 분산 시스템에서의 요청 흐름 이해
  2. 성능 병목 지점 식별
  3. 서비스 간 의존성 파악
  4. 오류 및 지연의 근본 원인 분석

Trace의 구성 요소

트레이스는 다음과 같은 구성 요소들로 이루어진다:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 트레이스 시작
Span rootSpan = tracer.spanBuilder("checkout-process")
    .setSpanKind(SpanKind.SERVER)
    .startSpan();

try (Scope scope = rootSpan.makeCurrent()) {
    // 자식 스팬 생성
    Span paymentSpan = tracer.spanBuilder("process-payment")
        .setParent(Context.current().with(rootSpan))
        .startSpan();
    
    try {
        processPayment();
        paymentSpan.setStatus(StatusCode.OK);
    } catch (Exception e) {
        paymentSpan.setStatus(StatusCode.ERROR, e.getMessage());
        throw e;
    } finally {
        paymentSpan.end();
    }
} finally {
    rootSpan.end();
}

트레이스 구성의 핵심 요소들:

  1. Trace ID: 전체 트랜잭션을 식별하는 고유 식별자
  2. Span: 작업의 단위를 나타내며, 시작/종료 시간과 메타데이터를 포함
  3. Parent-Child 관계: 스팬들 간의 계층 구조를 표현
  4. Tags/Attributes: 추가적인 컨텍스트 정보를 제공
  5. Events: 스팬 내에서 발생한 중요한 시점들을 기록

Trace의 특징

  1. 인과관계 표현: 요청의 전체 흐름과 각 단계 간의 관계를 보여준다.
  2. 시각화: 주로 워터폴 다이어그램 형태로 시각화되어 직관적인 이해를 돕는다.
  3. 분산 시스템 최적화: 마이크로서비스 아키텍처에서 특히 유용하다.

Trace의 활용

  1. 성능 최적화: 지연 시간이 긴 구간을 식별하고 개선할 수 있다.
  2. 오류 디버깅: 오류가 발생한 정확한 위치와 원인을 파악할 수 있다.
  3. 시스템 이해: 복잡한 분산 시스템의 동작을 더 잘 이해할 수 있다.

분산 추적의 구현

여러 서비스에 걸친 추적을 구현하는 방법을 살펴보자:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer(__name__)

@app.route('/order')
def create_order():
    with tracer.start_as_current_span("create_order") as span:
        span.set_attribute("user_id", request.user.id)
        
        # 결제 서비스 호출
        with tracer.start_span("payment_service_call") as payment_span:
            try:
                process_payment()
                payment_span.set_status(Status(StatusCode.OK))
            except Exception as e:
                payment_span.set_status(Status(StatusCode.ERROR))
                payment_span.record_exception(e)
                raise

컨텍스트 전파

서비스 간에 트레이스 컨텍스트를 전달하는 방법:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// HTTP 클라이언트에서 트레이스 컨텍스트 전달
public Response makeHttpCall() {
    Span span = tracer.spanBuilder("http-call").startSpan();
    try {
        HttpHeaders headers = new HttpHeaders();
        TextMapSetter<HttpHeaders> setter =
            (carrier, key, value) -> carrier.set(key, value);
        
        // 현재 컨텍스트를 HTTP 헤더에 주입
        OpenTelemetry.getPropagators().getTextMapPropagator()
            .inject(Context.current(), headers, setter);
        
        return httpClient.send(request, headers);
    } finally {
        span.end();
    }
}

트레이스 데이터 분석

수집된 트레이스 데이터를 분석하는 방법은 다음과 같다:

  1. 성능 분석:

    1
    2
    3
    
    // 성능 메트릭 추출
    span.setAttribute("db.query.duration_ms", queryDuration);
    span.setAttribute("response.size_bytes", responseSize);
    
  2. 오류 분석:

    1
    2
    3
    
    // 오류 정보 기록
    span.recordException(exception);
    span.setStatus(StatusCode.ERROR, "Database connection failed");
    
  3. 병목 지점 식별:

    1
    2
    3
    4
    5
    
    // 임계값 초과 시 경고 이벤트 기록
    if (duration > threshold) {
        span.addEvent("performance_warning", 
            Attributes.of("duration_ms", duration));
    }
    

참고 및 출처