개념/배경
마이크로서비스와 비동기 작업이 섞인 환경에서 요청의 전 흐름을 좇기 위한 상관 식별자 필요 일반적으로 trace id를 tid로 표기해 서비스 경계를 넘나들며 전파함 HTTP 같은 동기 호출과 메시지 큐 같은 비동기 처리의 전파 매체가 다르므로 매체별 규칙을 명확히 두는 것이 핵심 산업 표준은 W3C Trace Context와 OpenTelemetry가 사실상 기본값 프로세스 내부 전파에는 비동기 컨텍스트 저장소 사용 권장
핵심 개념과 정의
- TID(trace id)와 span id 구분, tid는 전체 요청 상관을 위한 루트 식별자 역할
- W3C Trace Context의 traceparent와 tracestate 헤더로 표준 전파
- OpenTelemetry는 위 표준을 구현하고 언어별 SDK 제공
- 프로세스 내 컨텍스트 전파는 AsyncLocalStorage 등 런타임 컨텍스트로 처리
표준 패턴
- HTTP 및 gRPC 같은 동기 네트워크 통신은 헤더 사용이 기본 원칙
- 메시지 큐나 비동기 작업은 메시지 Body 또는 시스템이 제공하는 메시지 속성 사용
- 지원되는 경우 메시지 헤더를 우선 사용, 없으면 Payload에 포함
요약
- HTTP, gRPC 전파 수단: 헤더
- Queue, Job 전파 수단: Payload 또는 메시지 속성
- 내부 함수 간 전파 수단: AsyncLocalStorage 같은 컨텍스트 저장소
산업 표준
OpenTelemetry의 대표 포맷 예시
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: vendor-specific data메시지 큐에 싣는 최소 컨텍스트 예시
{"traceId":"0af7651916cd43dd8448eb211c80319c","spanId":"b7ad6b7169203331","data":{}}권장 사항
- 가능하면 W3C traceparent 사용, 불가 시 임시로 x-trace-id 같은 커스텀 헤더 사용
- OTel SDK를 사용하면 생성과 전파, 추적 연계를 표준화 가능
매체별 권장 전파 방식
- HTTP REST: traceparent 또는 x-trace-id 같은 커스텀 헤더 사용
- gRPC: 메타데이터 사용, 키는 traceparent 또는 조직 표준 키
- Kafka: Message Headers 사용 권장, traceId를 헤더 키로 저장
- RabbitMQ: Message Properties의 headers 사용 권장
- AWS SQS: MessageAttributes 사용, 제약 때문에 불가한 경우 Body에 포함
- BullMQ 같은 Redis 기반 Job Queue: 헤더 개념 없음, Job Data에 포함
- WebSocket: 최초 핸드셰이크에 헤더 사용, 이후 애플리케이션 메시지에 tid 필드 포함
간단 예시
// HTTP 클라이언트 인터셉터 예시
client.interceptors.request.use((config) => {
const tid = contextStore.get('tid')
if (tid) config.headers['traceparent'] = toTraceparent(tid)
return config
})// Kafka 예시
producer.send({ topic: 't', messages: [{ headers: { traceId: tid }, value: payload }] })// BullMQ 예시
await queue.add('job', { tid, ...data })// RabbitMQ 예시
channel.publish('ex', 'rk', Buffer.from(body), { headers: { traceId: tid } })동작 원리/흐름
HTTP는 요청과 응답이 한 쌍으로 묶이는 모델이라 헤더가 자연스러운 메타데이터 운송 수단 한 요청 체인의 다음 홉으로 갈 때 기존 헤더를 그대로 전달하거나 필요 시 변환함
메시지 큐는 fire-and-forget 모델이라 메시지 자체가 컨텍스트의 단위가 됨 프로듀서는 메시지에 tid를 포함하고, 컨슈머는 수신 시 컨텍스트 저장소에 복원한 뒤 외부 호출에는 다시 헤더로 전파함
실무 흐름
- 수신 지점에서 tid 추출 또는 생성
- 프로세스 내부 컨텍스트에 주입
- 모든 로그와 외부 호출에 동일 tid를 포함
- 비동기 전환 시 메시지에 tid를 넣어 다음 단계로 전달
최소 예시 모음
HTTP 수신 지점에서 컨텍스트 초기화
app.use((req, res, next) => {
const fromHeader = req.headers['traceparent'] || req.headers['x-trace-id']
const tid = parseOrCreateTid(fromHeader)
contextStore.run({ tid }, () => {
res.setHeader('traceparent', toTraceparent(tid))
next()
})
})비동기 Job 생성과 처리
// enqueue
const tid = contextStore.get('tid')
await queue.add('send-email', { tid, orderId })// worker
worker.process(async (job) => {
const tid = job.data.tid || createTid()
contextStore.enterWith({ tid })
await http.post('/email', body, { headers: { traceparent: toTraceparent(tid) } })
})구조화 로그 예시
{"tid":"abc-123","msg":"order created","orderId":5}
{"tid":"abc-123","msg":"email sent","jobId":99}베스트 프랙티스
- 표준 우선 적용, traceparent와 tracestate 사용
- 가능한 한 동일 키를 계층 전반에 일관되게 사용
- 수신 시 tid 유효성 검증, 형식 불일치 시 새로 생성하고 원본은 별도 필드로 기록
- 모든 응답에 tid를 에코, 클라이언트 디버깅 비용 절감
- 로그는 JSON 구조화와 키 일관성 유지, tid 키를 최우선 컬럼으로 두기
- 메시지 큐는 시스템이 제공하는 헤더나 속성 기능이 있으면 이를 우선 사용
- 컨텍스트 저장소는 프레임워크와 런타임 특성에 맞춰 검증, 비동기 경계에서 누수 테스트 필수
- 샘플링을 사용하는 경우 로거, 트레이서, APM 간 상관 규칙을 문서화
- 게이트웨이와 프록시에서 trace 헤더가 보존되도록 allowlist 설정
- 헤더 크기와 메시지 속성 한도 고려, 최소 필드만 전송
- tid에 개인정보나 의미 있는 비즈니스 값 포함 금지
- 외부 조직 간 연동 시 최소한 traceparent만 합의하고 벤더별 tracestate는 선택 적용
주의와 한계
- 일부 로드밸런서나 보안 프록시가 커스텀 헤더를 제거할 수 있음, traceparent 같은 표준 키 사용과 예외 설정 필요
- SQS와 같은 서비스는 속성 크기 제한이 존재, 길이와 문자셋 검증 필요
- 배치 컨슈머는 단일 배치에 여러 tid가 섞일 수 있음, 레코드 단위로 컨텍스트 복원 필요
- 워커 풀이나 스레드 풀 전환 시 컨텍스트 전달 누락 가능, 프레임워크 수준 미들웨어로 강제 적용 권장
- 클라이언트가 임의로 tid를 주입하는 경우 추적 오염 가능, 화이트리스트 형식 검증과 재생성 정책 필요
간단 검증 체크리스트
- HTTP 수신과 응답 모두에서 traceparent가 존재하는지 확인
- 내부 로그 전체에서 tid 검색 시 하나의 사용자 시나리오가 연결되는지 확인
- 동기 호출 체인과 비동기 메시지 체인 모두에서 tid 손실 구간이 없는지 확인
- 큐 컨슈머가 수신한 tid로 외부 호출 헤더를 세팅하는지 확인
- 프록시, 게이트웨이, 서비스 메시 구간에서 trace 헤더 보존 여부 확인
점검을 위한 최소 E2E 시나리오 권장
- 클라이언트에서 요청 시작 시 tid 지정 또는 서버 생성
- API 서버 로그, 외부 API 호출 로그, 큐에 적재된 메시지, 워커 로그를 단일 tid로 조회 가능해야 함
마무리
전파 매체에 맞춘 일관된 규칙이 관건 HTTP와 gRPC는 헤더, 메시지 큐는 메시지 속성 또는 Payload, 내부 호출은 컨텍스트 저장소라는 단순한 규칙을 지키면 됨 W3C Trace Context와 OpenTelemetry를 기본으로 두고, 지원이 부족한 구간만 최소한의 커스텀을 적용하는 전략이 유지 비용과 도입 비용을 동시에 낮춤 핵심은 유효성 검증, 일관된 로깅, 경계별 책임 분리 세 가지임
참고 자료
- W3C Trace Context
- OpenTelemetry Traces 개념
- gRPC Metadata
- Kafka Message Format과 Headers
- RabbitMQ Message Properties
- AWS SQS Message Attributes
- BullMQ 문서
- Node.js AsyncLocalStorage
참고자료
- https://www.w3.org/TR/trace-context/
- https://opentelemetry.io/docs/concepts/signals/traces/
- https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/
- https://grpc.io/docs/guides/metadata/
- https://kafka.apache.org/documentation/#messageformat
- https://www.rabbitmq.com/publishers.html#message-properties
- https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-attributes.html
- https://docs.bullmq.io/
- https://nodejs.org/api/async_context.html#class-asynclocalstorage