186. 분산 추적 (Distributed Tracing) 인프라

⚠️ 이 문서는 마이크로서비스(MSA) 환경에서 사용자가 "결제 버튼"을 한 번 눌렀을 때, 그 뒤로 얽히고설킨 10개의 서비스(주문, 재고, 쿠폰, 결제 등)를 건너뛰며 흐르는 API 호출의 전체 여정을 꼬리표(Trace ID)를 달아 하나의 흐름으로 묶고, 어느 서비스에서 속도가 지연(Bottleneck)되거나 에러가 났는지 시각적으로 추적해 내는 인프라 시스템을 다룹니다.

핵심 인사이트 (3줄 요약)

  1. 본질: 모놀리식 시절에는 에러 로그 파일 하나만 보면 원인을 알 수 있었지만, MSA에서는 A 서비스가 B를 부르고 B가 C를 부르다 터졌을 때 "이 로그들이 하나의 사용자가 일으킨 묶음이 맞나?"를 증명할 길이 없다. 이를 이어주는 실(Thread)이다.
  2. 가치: 개발자들이 "우리 서비스는 빠르고 정상인데? 옆 팀 서비스가 느린 거 아니야?"라며 핑퐁 게임을 할 때, 10개의 서비스 호출 구간(Span)에 걸린 밀리초(ms) 단위의 시간 막대그래프(Gantt Chart)를 제시하여 범인(병목 지점)을 1초 만에 찾아낸다.
  3. 기술 체계: 트랜잭션 전체를 묶는 Trace ID와 각 서비스 방문 내역인 Span ID를 HTTP 헤더(Header)에 실어 전달하며, 대표적인 수집/시각화 도구로 트위터가 만든 **Zipkin(집킨)**과 우버가 만든 **Jaeger(예거)**가 사용된다.

Ⅰ. MSA의 저주: "도대체 어디서 에러가 난 거야?"

서버가 수십 개로 쪼개지면, 로그를 보는 것 자체가 불가능한 추리 게임이 된다.

  1. 로그 파편화 (Shattered Logs):
    • 사용자가 "결제 실패" 에러를 겪었다.
    • 주문팀 로그에는 "정상 호출", 재고팀 로그에는 "타임아웃", 결제팀 로그에는 "DB 락(Lock)" 에러가 섞여 있다. 수백만 건의 로그가 동시에 쏟아지는 상황에서, 이 세 로그가 '방금 에러를 겪은 동일한 고객 1명'의 발자취임을 엮어낼 방법이 없다.
  2. 분산 추적(Distributed Tracing)의 아이디어:
    • 사용자가 처음 API 게이트웨이를 뚫고 들어올 때, 랜덤한 일련번호(예: Trace-ID: 12345)를 발급하여 꼬리표를 붙인다.
    • 이 꼬리표는 주문 -> 재고 -> 결제 서비스로 HTTP 요청을 보낼 때마다 헤더에 담겨 전달된다.
    • 모든 서비스가 로그를 찍을 때 항상 [Trace-ID: 12345]를 같이 찍어두면, 나중에 로그 서버(ELK 등)에서 '12345'로 검색하는 순간 이 고객이 거쳐 간 10개 서비스의 로그가 한 번에 정렬되어 튀어나온다.

📢 섹션 요약 비유: 공항에서 수화물(사용자 요청)을 부칠 때 캐리어 손잡이에 긴 바코드 띠(Trace ID)를 하나 묶어두는 것입니다. 비행기를 갈아타고(서비스 A $\rightarrow$ B), 수많은 컨베이어 벨트를 거칠 때마다 기계가 이 바코드를 띡 띡 찍어 중앙 센터에 보내주므로, 내 가방이 파리 공항(병목 지점)에 걸려있다는 것을 즉시 추적할 수 있습니다.


Ⅱ. 핵심 구조: Trace와 Span의 계층

추적의 해상도를 높이기 위해 전체 여정과 세부 여정을 나눈다.

  1. 트레이스 (Trace: 전체 여정):
    • 클라이언트의 최초 요청부터 마지막 응답까지의 전체 생명주기. 1개의 트랜잭션은 1개의 고유한 Trace ID를 갖는다.
  2. 스팬 (Span: 세부 구간):
    • 트레이스를 구성하는 하나하나의 징검다리 작업(Operation) 단위다.
    • 주문 서비스 도착(Span A) -> 재고 서버로 API 호출(Span B) -> DB 쿼리 실행(Span C). 각 스팬은 시작 시간과 종료 시간을 기록해 자신만의 Span ID를 가지며, 부모 스팬의 ID를 기억해 촌수(Tree) 구조를 형성한다.
  3. 분산 컨텍스트 전파 (Context Propagation):
    • 스프링 부트(Spring Boot) 개발자라면 Spring Cloud Sleuth (최근엔 Micrometer Tracing) 같은 라이브러리를 넣기만 하면, 내가 짠 코드를 수정하지 않아도 라이브러리가 알아서 HTTP 헤더(X-B3-TraceId)에 이 값들을 몰래 끼워 넣어 뒷단 서버로 넘겨준다.

📢 섹션 요약 비유: 택배 운송장 번호 전체가 'Trace'라면, 택배가 '옥천 허브 도착', '옥천 허브 출발', '대전 허브 도착' 할 때마다 찍히는 하나하나의 배송 스캔 기록들이 바로 'Span'입니다. 여러 개의 Span이 모여 하나의 온전한 Trace(배송 조회 화면)를 완성합니다.


Ⅲ. 시각화 도구: Zipkin과 Jaeger의 간트 차트(Gantt Chart)

수백만 개의 꼬리표 데이터를 모아 인간이 볼 수 있는 지도로 그린다.

  1. 에이전트를 통한 비동기 수집:
    • 각 마이크로서비스는 남긴 Trace/Span 정보를 사용자 응답을 방해하지 않기 위해 카프카(Kafka)나 백그라운드 스레드를 통해 비동기적으로 Zipkin/Jaeger 서버에 밀어 넣는다.
  2. 타임라인 시각화:
    • 관리자가 Zipkin 대시보드에 들어가면, 폭포수처럼 떨어지는 막대그래프(간트 차트)를 볼 수 있다.
    • ┌──────────────────────────────────────────────┐ │ 주문 API (Total 1000ms) │ │ ├── 재고 API 호출 ▒▒▒▒ (200ms) │ │ ├── 결제 API 호출 ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ (700ms) ❗ │ │ │ └── DB 트랜잭션 ▒▒▒▒▒▒▒▒▒▒ (600ms) ❗ │ │ └── 고객 DB 쿼리 ▒ (100ms) │ └──────────────────────────────────────────────┘
  3. 병목(Bottleneck)의 원터치 색출:
    • 그래프를 보는 순간, 전체 1초 중 결제 API가 0.7초를 잡아먹었고, 그 안에서도 결제팀의 DB가 느려서 발생한 병목임을 개발자 회의 없이 즉각 증명할 수 있다. (블레임 리스, Blameless 문화의 과학적 근거 제공)

📢 섹션 요약 비유: 병원 응급실(MSA)에 환자(요청)가 들어왔을 때 각 진료과가 "우린 빨리 넘겼는데 쟤네가 늦게 한 거야"라고 싸우면 cctv를 다 돌려봐야 합니다. 하지만 환자 이마에 초시계를 붙여놓고(분산 추적), 중앙 모니터(Zipkin)에 엑스레이실 20분, 수술실 4시간, 마취과 대기 2시간(막대그래프)이라는 영수증을 딱 띄워버리면 한 방에 누가 지연을 일으켰는지 명백히 밝혀지는 원리입니다.