이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
공부 › 아키텍처와 운영 › 관측
분산 추적
요청 하나가 느린데 어느 서비스 탓인가
주문 요청 하나가 게이트웨이, 주문 서비스, 결제 서비스, 외부 결제사를 차례로 거칩니다. 로그는 서비스마다 따로 쌓이므로, 같은 요청의 로그를 묶을 공통 번호가 없으면 어디서 시간이 걸렸는지 찾기 어렵습니다.
문서·표준 대조 · W3C Trace Context 표준과 OpenTelemetry 문서
이 실습에 나오는 개념
- 분산 추적(Trace ID, Span)
- 하나의 요청이 여러 서비스를 거치며 처리되는 전체 경로를 추적하는 기술이다. 요청이 처음 들어올 때 고유한 추적 ID를 부여하고 서비스 사이의 호출마다 이를 함께 전달한다. 각 서비스의 처리 구간을 스팬이라 하며, 같은 추적 ID의 스팬을 모으면 어느 구간에서 시간이 걸렸는지 알 수 있다.
- 관측성(로그, 지표, 추적)
- 시스템의 외부 출력만으로 내부 상태를 파악할 수 있는 능력이다. 로그는 개별 사건의 기록, 지표(메트릭)는 요청 수나 응답 시간처럼 수치로 집계한 값, 추적은 하나의 요청이 여러 서비스를 거쳐 가는 경로와 구간별 소요 시간의 기록이다. 세 가지를 함께 갖춰야 문제를 감지하고 원인을 찾을 수 있다.
- MDC
- 로그를 남길 때 현재 요청과 관련된 문맥 정보(요청 ID, 사용자 ID 등)를 자동으로 함께 출력하게 해 주는 로깅 기능이다. 스레드별로 값을 보관하며, 요청이 시작될 때 값을 넣어 두면 그 요청을 처리하는 스레드가 남기는 모든 로그에 같은 값이 찍힌다. 여러 요청의 로그가 섞여 있어도 한 요청의 로그만 골라 볼 수 있다.
- APM
- 애플리케이션의 성능을 모니터링하는 도구다. 요청별 응답 시간, 어느 메서드나 쿼리에서 시간이 걸렸는지, 오류 발생 현황 등을 수집해 보여 준다. 자바에서는 주로 에이전트 방식으로 코드 수정 없이 적용한다. Pinpoint, Scouter, Datadog 등이 있다.
- 모놀리식과 MSA(마이크로서비스)
- 모놀리식은 모든 기능을 하나의 애플리케이션으로 만들어 함께 배포하는 구조다. MSA는 기능별로 작은 서비스로 나눠 각각 독립적으로 개발, 배포, 확장하는 구조다. MSA는 서비스별로 따로 배포하고 확장할 수 있고 장애가 격리되지만, 서비스 간 통신, 데이터 일관성, 운영의 복잡도가 크게 늘어난다.
이렇게 바꿔 보세요
- 처음 설정으로 — 외부 결제사가 느립니다. 추적 화면에서 한눈에 보입니다.
- 추적 번호가 없다면 — 서비스마다 로그는 있지만 같은 요청끼리 이을 수 없습니다.
- DB 조회가 느릴 때 — 느린 구간이 주문 서비스 안쪽으로 옮겨집니다.
- 결제 서비스 내부가 느릴 때 — 결제 서비스의 시간에서 외부 호출 시간을 뺀 부분이 깁니다.
처음 설정으로 실행하면
- 요청이 게이트웨이에 도착합니다. 추적 번호가 없는 요청이므로 새 Trace ID(4bf92f35…)를 만듭니다. 이 요청이 끝날 때까지 이 번호가 따라다닙니다. 게이트웨이의 처리 구간이 첫 번째 스팬입니다.
- 게이트웨이가 주문 서비스를 호출할 때 HTTP 헤더에 Trace ID와 자기 스팬 ID를 실어 보냅니다. 주문 서비스는 같은 Trace ID를 이어받고, 자신의 구간을 새 스팬으로 기록합니다(부모는 게이트웨이의 스팬).
- 주문 서비스가 DB를 조회합니다(30ms). 이 쿼리도 하나의 스팬으로 기록됩니다.
- 주문 서비스가 결제 서비스를 호출합니다. 이번에도 헤더로 Trace ID가 전달됩니다. 로그를 남길 때마다 MDC에 들어 있는 Trace ID가 자동으로 함께 찍힙니다.
- 결제 서비스가 외부 결제사 API를 호출합니다(2000ms). 응답이 돌아오면 거꾸로 결제 서비스, 주문 서비스, 게이트웨이 순으로 스팬이 닫힙니다. 전체 2095ms.
- 추적 시스템(Zipkin, Tempo 등)에서 Trace ID 하나로 조회하면 다섯 구간이 시간 축 위에 계단 모양으로 그려집니다. 안쪽 호출을 뺀 자체 시간이 가장 긴 구간은 "외부 결제사 API"입니다. 어디가 느린지 바로 보입니다.
실행 결과
- Trace ID는 요청 하나 전체에 붙는 번호이고, 스팬은 그 안의 한 구간(서비스 하나의 처리, 쿼리 하나, 외부 호출 하나)입니다. 스팬마다 부모 스팬을 기록하므로 호출 관계가 트리로 복원됩니다.
- 로그는 "무슨 일이 있었나", 지표는 "전체적으로 얼마나", 추적은 "이 요청이 어디서 얼마나"를 알려 줍니다. 로그에도 Trace ID를 찍어 두면 추적 화면에서 느린 구간을 찾은 뒤 그 번호로 로그를 검색할 수 있습니다.
코드
// build.gradle: Micrometer Tracing
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
# 서비스끼리 호출할 때 자동으로 붙는 헤더 (W3C Trace Context)
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
# └─ trace id (요청 전체에서 동일) └─ span id (구간마다 다름)
# 로그 패턴에 추적 번호를 넣는다 (MDC에서 꺼냄)
logging.pattern.level: "%5p [%X{traceId:-},%X{spanId:-}]"
이 모형의 전제
서비스 네 개를 차례로 호출하는 단순한 구조이고 시간은 예시 값입니다. Trace ID와 스팬, 헤더로 전파하는 방식은 W3C Trace Context 표준과 OpenTelemetry, Micrometer Tracing 문서의 설명을 따랐습니다. 실제로는 모든 요청을 기록하지 않고 일부만 표본으로 수집하는 경우가 많습니다.
앞 실습: 평균과 백분위 · 다음 실습: merge와 rebase