개념/배경

마이크로서비스와 비동기 작업이 섞인 환경에서 요청의 전 흐름을 좇기 위한 상관 식별자 필요 일반적으로 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

참고자료