나의 잡다한 노트 및 메모
request_id, trace_id, span_id 에 대한 구분 본문
request_id
- 정의: 개별 클라이언트 요청(HTTP API 호출, gRPC 호출, 메시지 큐 메시지 처리 등)에 부여되는 고유 식별자입니다.
- 주 용도:
- 로그 상관관계(Correlation): 특정 클라이언트 요청이 들어왔을 때, 그 요청과 관련된 모든 로그(예: 로드밸런서 단계, 웹서버 단계, 애플리케이션 단계, DB 쿼리 단계 등)에 동일한 request_id를 붙여서 “이 로그들이 모두 같은 요청에 속한다”라는 것을 쉽게 파악할 수 있도록 해 줍니다.
- 디버깅 & 트래블스루(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입니다.
- 주 용도:
- 분산 트레이싱(Distributed Tracing): 분산 시스템(마이크로서비스, 서버리스(Function-as-a-Service, FaaS), 메시지 큐 등)이 여러 개의 컴포넌트로 분리되어 있을 때, “클라이언트가 요청을 보낸 시점부터, 내부적으로 수십 개의 서비스/큐/DB 호출을 거쳐 응답을 반환하기까지” 모든 단계(스팬, span)를 같은 trace_id로 묶어서 하나의 흐름(trace)으로 볼 수 있게 해 줍니다.
- 성능 최적화 & 병목 지점 찾기: 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)’이 됩니다.
- 주 용도:
- 세부 작업 단위 구분: “A 서비스가 B 서비스를 호출해서 데이터를 가공하고, 그 결과를 다시 C 서비스에 호출해서 DB에 저장하고 …” 식의 복잡한 시나리오에서, 각 호출/작업 단위를 고유한 span_id로 구분하여 타임라인을 상세히 기록할 수 있습니다.
- 부모-자식 관계 추적: 각 스팬(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가 어떻게 생성·전파되고 로그에 남는지를 단계별로 정리한 것입니다.
- 엔트리 포인트: API 게이트웨이 (예: Envoy, Kong, NGINX)
- 행동: 클라이언트(브라우저)가 POST /api/v1/orders 요청을 보냄.
- 처리:
- Envoy가 요청을 받자마자 "X-Request-ID":"f47ac10b-58cc-4372-a567-0e02b2c3d479" 형태로 새로운 request_id를 생성해서 요청 헤더에 붙인다.
- 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로부터 요청이 전달됨.
- 처리:
- HTTP 헤더 X-Request-ID 와 traceparent (혹은 OpenTelemetry B3 헤더 등)로부터 **기존 request_id, trace_id, span_id**를 수신.
- **새로운 하위 스팬(child span)**을 생성 → span_id="b9c9d615188c4c3b" (예시)
- 새 하위 스팬의 부모(span)의 ID는 Envoy가 보낸 부모 스팬 ID (00f067aa0ba902b7)가 된다.
- order-service 내부에서 “주문 생성 비즈니스 로직”을 실행
- 이때 order-service도 로그를 남기는데, 반드시 **모든 로그에 동일한 request_id**와 **동일한 trace_id**를 넣는다. 다만, span ID는 이 스팬이 **“order-service 내부 로직”**에 해당하므로, 새로 생성된 **span_id="b9c9d615188c4c3b"**를 사용한다.
- 결론:
- 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)
- 처리:
- 호출 시 HTTP 헤더 X-Request-ID 와 traceparent 헤더에 **동일한 request_id와 trace_id**를 포함하여 보냄.
- 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)가 응답을 클라이언트에게 보내면서 루트 스팬 종료
- 최종적으로 아래와 같이 로그 히스토리가 남음:
- Envoy(루트 스팬) → 로그
- order-service(하위 스팬 1) → 로그
- payment-service(하위 스팬 2) → 에러 로그
- order-service(하위 스팬 1) 종료 로그
- 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 |