LabHub

분산 트레이싱이 끊기는 자리 · 전파 · 이론

traceparent 한 줄의 구조

LabHub 에서 이어서 보기

한 줄 요약

분산 트레이싱의 전부는 traceparent 헤더를 다음 호출로 넘기는 것이다. 자동 계측이 그걸 대신해 주지만, 코드가 요청 객체를 새로 만드는 순간 끊긴다.

왜 이게 필요했나

"Istio 를 깔았으니 트레이싱이 된다" 는 말이 흔한데 틀렸다. 사이드카는 자기가 본 요청에 대해 스팬을 만들 수 있지만, A 서비스가 받은 요청과 A 가 B 로 보낸 요청이 같은 트레이스라는 것을 사이드카는 모른다. 그걸 잇는 것은 애플리케이션이다.

W3C 표준 헤더는 이렇게 생겼다.

traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01             ^^ ^------------ trace-id ---------^ ^-- span-id --^ ^^             버전            32자리(트레이스 전체)   16자리(이 구간)  플래그

마지막 01샘플링 플래그다. 1이면 "이 트레이스는 기록한다", 0이면 "기록하지 않는다". 이 결정은 맨 앞에서 한 번 내리고 뒤로 전파된다 — 그래야 트레이스가 반쪽만 남는 일이 없다.

어떻게 동작하나

서비스 A 가 B 를 부를 때 해야 하는 일은 딱 하나다.

1. 들어온 요청의 traceparent 를 읽는다
2. 새 span-id 를 만들고, trace-id 는 그대로 두고, 나가는 요청에 붙인다

자동 계측(auto-instrumentation)이 이걸 대신한다. 그런데 다음 경우에 끊긴다.

| 끊기는 자리 | 왜 |
|---|---|
| 새 HTTP 클라이언트를 코드에서 직접 만듦 | 계측 라이브러리가 감싸지 않은 경로 |
| 큐·메시지로 넘김 | HTTP 헤더가 없다 — 메시지 속성에 직접 넣어야 한다 |
| 스레드/코루틴을 새로 띄움 | 컨텍스트가 스레드 로컬이라 안 따라간다 |
| 배치 처리 | 요청 하나 : 트레이스 하나가 아니다 |

특히 가 흔하다. Kafka 로 넘길 때는 메시지 헤더에 traceparent 를 담고, 컨슈머가 그걸 꺼내 컨텍스트를 복원해야 한다. 안 하면 프로듀서 쪽 트레이스와 컨슈머 쪽 트레이스가 서로 남남이 된다.

샘플링

전부 기록하면 비싸다. 그래서 샘플링한다. 방식이 둘이다.

헤드 샘플링 — 맨 앞에서 "이건 기록" 을 정한다. 싸고 단순하지만, 느린 요청만 골라 볼 수 없다. 시작할 때는 그게 느릴지 모르기 때문이다.

테일 샘플링 — 트레이스가 끝난 뒤 판단한다. "오류가 있거나 1초 넘은 것만 저장". 원하는 것을 정확히 고르지만, 컬렉터가 트레이스 전체를 메모리에 모아 두었다가 판단해야 해서 비싸고, 같은 트레이스의 스팬이 같은 컬렉터로 가도록 라우팅해야 한다.

실무 조합은 대개 이렇다 — 헤드에서 10~20% 로 줄이고, 오류는 100% 강제하고, 중요한 엔드포인트만 테일 샘플링을 건다.

흔한 착각

"스팬이 많을수록 좋다" — 함수마다 스팬을 만들면 트레이스 하나가 수천 스팬이 되고, UI 에서 읽을 수 없고, 저장 비용이 폭발한다. 스팬은 네트워크 경계와 느린 작업에 둔다.

속성에 개인정보를 넣는 것. 스팬 속성은 그대로 저장되고 검색된다. 사용자 이메일이나 토큰을 넣으면 그게 트레이싱 백엔드에 평문으로 쌓인다.

실무에서 진짜 중요한 것

트레이스와 로그를 잇는 것이 실제 효용의 대부분이다. 로그 한 줄에 trace_id 를 넣어 두면, 느린 트레이스를 발견했을 때 그 요청이 남긴 로그를 한 번에 찾을 수 있다. 이 연결이 없으면 트레이싱은 예쁜 그림에 그친다.