나의 잡다한 노트 및 메모

request_id, trace_id, span_id 에 대한 구분 본문

DevOps

request_id, trace_id, span_id 에 대한 구분

peanutwalnut 2025. 5. 31. 22:30

request_id

  • 정의: 개별 클라이언트 요청(HTTP API 호출, gRPC 호출, 메시지 큐 메시지 처리 등)에 부여되는 고유 식별자입니다.
  • 주 용도:
    1. 로그 상관관계(Correlation): 특정 클라이언트 요청이 들어왔을 때, 그 요청과 관련된 모든 로그(예: 로드밸런서 단계, 웹서버 단계, 애플리케이션 단계, DB 쿼리 단계 등)에 동일한 request_id를 붙여서 “이 로그들이 모두 같은 요청에 속한다”라는 것을 쉽게 파악할 수 있도록 해 줍니다.
    2. 디버깅 & 트래블스루(Flow-Through) 파악: 여러 레이어(프록시, 리버스 프록시, API 게이트웨이, 어플리케이션 서버) 를 거치더라도, 최상단에서 생성된 request_id 를 헤더(예: X-Request-ID)로 넘겨주면, 모든 레이어가 같은 ID를 로그에 기록하기 때문에 최종 에러가 났을 때 “이 요청이 어디까지 걸려서 실패했는지”를 한눈에 추적할 수 있습니다.
  • 생성 시점:
    • 보통 최초 엔트리 포인트(예: 클라이언트가 API 게이트웨이에 요청을 보내는 순간 또는 웹서버(nginx/Envoy)가 요청을 받자마자) 생성됩니다.**
    • 만약 API Gateway(또는 프록시)가 없을 경우, 각 마이크로서비스 첫 진입 지점(Controller, Handler 등)에서 생성할 수도 있습니다.
  • 형식 예시: UUID, 64비트 난수 문자열 등 (예: f47ac10b-58cc-4372-a567-0e02b2c3d479).

 

  • trace_id
    • 정의: “분산 시스템을 횡단(서비스 A → 서비스 B → 서비스 C …)”하며 하나의 최종 결과를 만들어 내는 하나의 트랜잭션 트리 전체(tree)를 대표하는 ID입니다.
    • 주 용도:
      1. 분산 트레이싱(Distributed Tracing): 분산 시스템(마이크로서비스, 서버리스(Function-as-a-Service, FaaS), 메시지 큐 등)이 여러 개의 컴포넌트로 분리되어 있을 때, “클라이언트가 요청을 보낸 시점부터, 내부적으로 수십 개의 서비스/큐/DB 호출을 거쳐 응답을 반환하기까지” 모든 단계(스팬, span)를 같은 trace_id로 묶어서 하나의 흐름(trace)으로 볼 수 있게 해 줍니다.
      2. 성능 최적화 & 병목 지점 찾기: A 서비스가 B 서비스에 호출을 할 때, 둘 다 같은 trace_id를 쓰므로, 결국 여러 서비스에 걸친 호출 간의 타임라인을 하나의 뷰(View)로 볼 수 있습니다.
    • 생성 시점:
      • 대부분의 트레이싱 라이브러리(OpenTelemetry, OpenTracing, Zipkin, Jaeger 등)는 “최초 스팬(root span)” 을 생성하면서 동시에 trace_id 를 할당합니다.
      • 흔히 이 최초 스팬이 A 서비스의 진입점(예: HTTP 서버 핸들러 진입 시점)에 생성됩니다. 이때 할당된 trace_id 를 이후 호출되는 모든 하위 스팬(child span)에 전파(propagation)합니다.
    • 형식 예시: 128비트(또는 64비트) 난수 기반 문자열. (예: 4bf92f3577b34da6a3ce929d0e0e4736)
  • span_id
    • 정의: “하나의 트레이스(trace) 안에서, 각각의 구간(segment)에 해당하는 고유 식별자”입니다. 트레이스가 하나의 트리를 구성한다면, 각 노드(node)가 바로 ‘스팬(span)’이 됩니다.
    • 주 용도:
      1. 세부 작업 단위 구분: “A 서비스가 B 서비스를 호출해서 데이터를 가공하고, 그 결과를 다시 C 서비스에 호출해서 DB에 저장하고 …” 식의 복잡한 시나리오에서, 각 호출/작업 단위를 고유한 span_id로 구분하여 타임라인을 상세히 기록할 수 있습니다.
      2. 부모-자식 관계 추적: 각 스팬(span)에는 parent_span_id 필드가 붙을 수 있는데, 이를 통해 “이 스팬은 어떤 부모 스팬 아래에서 시작된 작업인가”를 트리 구조로 연결할 수 있습니다.
    • 생성 시점:
      • 최초 “루트 스팬(root span)”도 하나의 span_id를 가집니다.
      • 이후 내부 로직(서브모듈 호출, DB 쿼리, 외부 API 호출 등)마다 새로운 하위 스팬을 만들면서 **새로운 span_id**를 발급하고, 부모 스팬의 span_id를 parent_span_id로 지정합니다.
    • 형식 예시: 64비트(또는 128비트) 난수 기반 문자열(예: a9c8e58df3b049ef)

 

 

구분                     request_id                                          trace_id                                      span_id

범위(Scope) 하나의 클라이언트 요청 단위 하나의 분산 트랜잭션(여러 서비스/큐/DB 호출 포함) 전체 트레이스(trace) 안의 “개별 작업 단위(span)”
생성 시점 API 게이트웨이(또는 웹서버) 진입 시점, 또는 애플리케이션 진입점 최초 스팬(root span)이 시작되는 시점 해당 작업(스팬)이 시작될 때마다 새로운 하위 스팬으로 생성
전파(propagation) 모든 레이어(프록시, 웹서버, 애플리케이션, 하위 서비스)에 헤더 형태로 전파 최초 루트 스팬→하위 스팬→하위 스팬으로 계속 전파 해당 스팬을 생성한 코드 내에서만 유효. 하위 스팬에게 부모 ID로 전달
관심 대상 “이 요청(클라이언트→내부 인프라)이 어떤 여정을 거쳤는지 주로 로그 관점에서 묶어 보기” “전체 분산 시스템 차원의 호출 흐름을 한 트리로 묶어서 성능/병목을 분석” “각각의 작은 처리 단위가 얼마만큼의 시간을 썼고, 어디서 에러가 났는지 상세 조회”
사용 예시      

 

예시는 클라이언트가 브라우저에서 “사용자 A가 주문을 생성(POST /orders)”하는 요청을 보낼 때부터, 여러 마이크로서비스를 거쳐 최종 응답(에러 혹은 성공)을 반환하기까지 request_id, trace_id, span_id가 어떻게 생성·전파되고 로그에 남는지를 단계별로 정리한 것입니다.

  1. 엔트리 포인트: API 게이트웨이 (예: Envoy, Kong, NGINX)
    • 행동: 클라이언트(브라우저)가 POST /api/v1/orders 요청을 보냄.
    • 처리:
      1. Envoy가 요청을 받자마자 "X-Request-ID":"f47ac10b-58cc-4372-a567-0e02b2c3d479" 형태로 새로운 request_id를 생성해서 요청 헤더에 붙인다.
      2. Envoy(혹은 API Gateway 레벨)에서 최상위 “루트 스팬(root span)”을 생성 → 이때 trace_id (4bf92f3577b34da6a3ce929d0e0e4736)와 span_id (00f067aa0ba902b7)를 발급.

결론:

  • request_id = f47ac10b-58cc-4372-a567-0e02b2c3d479
  • trace_id = 4bf92f3577b34da6a3ce929d0e0e4736
  • span_id = 00f067aa0ba902b7 (Root Span)

2. 첫 번째 마이크로서비스: order-service

  • 행동: Envoy로부터 요청이 전달됨.
  • 처리:
    1. HTTP 헤더 X-Request-ID 와 traceparent (혹은 OpenTelemetry B3 헤더 등)로부터 **기존 request_id, trace_id, span_id**를 수신.
    2. **새로운 하위 스팬(child span)**을 생성 → span_id="b9c9d615188c4c3b" (예시)
    3. 새 하위 스팬의 부모(span)의 ID는 Envoy가 보낸 부모 스팬 ID (00f067aa0ba902b7)가 된다.
    4. order-service 내부에서 “주문 생성 비즈니스 로직”을 실행
    5. 이때 order-service도 로그를 남기는데, 반드시 **모든 로그에 동일한 request_id**와 **동일한 trace_id**를 넣는다. 다만, span ID는 이 스팬이 **“order-service 내부 로직”**에 해당하므로, 새로 생성된 **span_id="b9c9d615188c4c3b"**를 사용한다.
    6. 결론:
      • request_id: 변함없이 f47ac10b-58cc-4372-a567-0e02b2c3d479
      • trace_id: 변함없이 4bf92f3577b34da6a3ce929d0e0e4736
      • span_id: 새로운 ID b9c9d615188c4c3b
      • parent_span_id: Envoy 루트 00f067aa0ba902b7

두 번째 마이크로서비스: payment-service

  • 행동: order-service 내부에서 “결제 요청”을 위해 payment-service에 호출(TCP/gRPC/HTTP)
  • 처리:
    1. 호출 시 HTTP 헤더 X-Request-ID 와 traceparent 헤더에 **동일한 request_id와 trace_id**를 포함하여 보냄.
    2. payment-service는 “새로운 하위 스팬(child span)”을 생성 → span_id="d3e5f829a5fb4cf0" (예시)
  • 결론:
    • request_id: f47ac10b-58cc-4372-a567-0e02b2c3d479 (변함없음)
    • trace_id: 4bf92f3577b34da6a3ce929d0e0e4736 (변함없음)
    • span_id: d3e5f829a5fb4cf0 (새로 생성된 payment-service 로직 스팬)
    • parent_span_id: b9c9d615188c4c3b (order-service 쪽 스팬 ID)

최종 응답 반환

  • payment-service에서 타임아웃이 발생해서 에러를 리턴하면, order-service 입장에서도 더 이상 주문을 생성할 수 없기 때문에, order-service 쪽에서 “에러 헨들링” 하면서 자신의 스팬 종료(End Span)
  • 그 뒤 Envoy(또는 API Gateway)가 응답을 클라이언트에게 보내면서 루트 스팬 종료
  • 최종적으로 아래와 같이 로그 히스토리가 남음:
    1. Envoy(루트 스팬) → 로그
    2. order-service(하위 스팬 1) → 로그
    3. payment-service(하위 스팬 2) → 에러 로그
    4. order-service(하위 스팬 1) 종료 로그
    5. Envoy(루트 스팬) 종료 로그

'DevOps' 카테고리의 다른 글

API Gateway란  (0) 2025.05.31
정형화된 로그 스키마 예시  (0) 2025.05.31
운영 서비스 이관 전략 중 일부 설명  (0) 2025.03.30
Locale 이란?  (0) 2025.02.12
직렬화와 역직렬화  (2) 2024.12.25