/////

OpenTelemetry Async Trace Modeling Failure Modes: Context Propagation, Span Links, Retry Semantics, and Sampling Gaps

OpenTelemetry에서 비동기 워크플로우를 추적할 때의 핵심 실패 모드는 “동기 RPC처럼 단일 parent-child 트리로 모델링하려는 것”에서 시작된다. 큐, 스트림, 배치 컨슈머, 재시도, DLQ, fan-out/fan-in, 지연 실행은 실행 시간과 causal relationship이 분리되기 때문에, trace context propagation, span links, retry semantics, sampling 정책을 명시적으로 설계해야 한

/////

Summary#

OpenTelemetry에서 비동기 워크플로우를 추적할 때의 핵심 실패 모드는 “동기 RPC처럼 단일 parent-child 트리로 모델링하려는 것”에서 시작된다. 큐, 스트림, 배치 컨슈머, 재시도, DLQ, fan-out/fan-in, 지연 실행은 실행 시간과 causal relationship이 분리되기 때문에, trace context propagation, span links, retry semantics, sampling 정책을 명시적으로 설계해야 한다.

실무 캡슐의 핵심 원칙은 다음과 같다.

  • 가능한 경우 메시지에 W3C Trace Context를 전파하되, 모든 비동기 관계를 parent-child로 강제하지 않는다.
  • producer → broker → consumer가 시간적으로 분리되거나 다대다 관계가 되면 span link가 더 안전한 모델일 수 있다.
  • retry는 단순히 같은 span의 연장이 아니라 “같은 작업의 여러 attempt”로 표현하는 것이 분석과 SLO에 유리하다.
  • head sampling만 사용하면 producer 또는 consumer 한쪽만 남아 async trace가 끊기는 sampling gap이 발생할 수 있다.
  • 로그 상관관계는 trace가 샘플링되지 않거나 context가 유실될 때의 보조 복구 경로로 중요하다.

Key Points#

1. Context propagation failure#

비동기 시스템에서는 trace context가 다음 경계에서 자주 유실된다.

  • HTTP 요청 → queue publish
  • Kafka/SQS/RabbitMQ/Celery message headers
  • batch consumer가 여러 메시지를 한 번에 처리하는 구간
  • background worker, scheduled job, cron, delayed task
  • DLQ re-drive 또는 manual replay
  • retry middleware가 기존 headers를 덮어쓰는 경우

OpenTelemetry는 context propagation을 통해 trace identity를 프로세스와 네트워크 경계 너머로 전달할 수 있지만, 메시징 시스템에서는 carrier가 HTTP header가 아니라 message metadata/header가 된다. 이 carrier가 보존되지 않으면 consumer trace는 새 root trace가 되어 producer와 연결되지 않는다.

실패 패턴:

HTTP request trace
  └─ enqueue job span

worker trace
  └─ process job span   # producer와 연결되지 않는 새 root

권장 모델:

  • message headers에 traceparent, tracestate 전파
  • baggage는 필요한 경우에만 제한적으로 사용
  • queue library, framework, retry layer가 headers를 유지하는지 테스트
  • consumer 시작 시 extracted context가 존재하는지 metric/log로 검증

동기 호출에서는 parent-child가 자연스럽다.

HTTP handler span
  └─ DB span
  └─ RPC client span
      └─ RPC server span

하지만 async queue에서는 parent-child가 항상 맞지 않는다. producer span이 끝난 뒤 한참 후 consumer가 실행될 수 있고, 하나의 consumer span이 여러 메시지를 처리하거나, 하나의 메시지가 여러 downstream task를 만들 수 있다.

OpenTelemetry의 span link는 이런 관계를 표현하기 위한 수단이다. span link는 “이 span이 다른 span context와 causal하게 관련되어 있지만 parent는 아니다”라는 모델이다.

Span link가 더 적합한 경우:

  • batch consumer가 N개 메시지를 한 번에 처리
  • fan-in: 여러 upstream 이벤트가 하나의 작업을 트리거
  • fan-out: 하나의 이벤트가 여러 independent consumers를 트리거
  • retry attempt를 원래 enqueue/previous attempt와 연결
  • delayed job, scheduled job처럼 parent span이 이미 종료된 경우
  • replay/reprocessing처럼 원래 실행과 새 실행을 구분해야 하는 경우

예시 모델:

Trace A
  producer span: enqueue order-created

Trace B
  consumer span: process order-created
    link -> producer span context from Trace A

이 방식은 trace tree를 억지로 길게 잇지 않으면서도 causality를 보존한다.

3. Retry semantics#

비동기 retry를 잘못 모델링하면 trace가 다음처럼 왜곡된다.

  • 하나의 긴 span 안에 여러 attempt를 모두 넣음
  • retry마다 새 root trace가 생겨 같은 작업인지 알 수 없음
  • 성공한 마지막 attempt만 남고 실패 attempt가 사라짐
  • duplicate processing과 정상 retry를 구분하지 못함
  • DLQ 이동이 error인지 terminal state인지 불명확함

권장 모델은 “operation”과 “attempt”를 분리하는 것이다.

logical job: payment-capture job_id=abc

attempt 1 span
  status=error
  retry.attempt=1
  error.type=timeout

attempt 2 span
  status=error
  retry.attempt=2
  error.type=rate_limit

attempt 3 span
  status=ok
  retry.attempt=3

모델링 권장 사항:

  • 각 attempt는 별도 span으로 표현
  • 같은 job/message id를 attribute 또는 log field로 유지
  • previous attempt 또는 original enqueue span에 link 추가
  • retry count, backoff duration, retry reason 기록
  • DLQ publish/consume을 별도 span 또는 event로 표현
  • idempotency key와 message id를 trace/log/metric에 일관되게 포함

주의할 점은 retry를 parent-child로만 연결하면 “attempt 2가 attempt 1의 자식”처럼 보일 수 있다는 것이다. 실제로는 같은 logical operation의 재시도이지 attempt 간 호출 관계가 아닐 수 있다. 이 경우 link 또는 공통 correlation attribute가 더 정확하다.

4. Sampling gaps in async traces#

Head sampling은 trace 시작 시점에서 샘플링 여부를 결정한다. 비동기 워크플로우에서는 이 방식이 다음 문제를 만든다.

  • producer trace가 drop되면 consumer가 link할 context는 있어도 원본 span이 저장되지 않음
  • consumer 쪽에서 새 trace로 시작하면 producer와 consumer 중 한쪽만 샘플링됨
  • 실패는 worker에서 발생하지만 sampling decision은 HTTP ingress에서 이미 결정됨
  • rare retry/DLQ 경로가 샘플링에서 누락됨
  • batch consumer에서 일부 메시지만 중요하지만 span 단위 샘플링이 이를 반영하지 못함

대응 전략:

  • 중요한 async workflow에는 tail sampling 고려
  • error, high latency, retry count, DLQ, specific route/topic 기반 sampling rule 구성
  • trace가 없을 때도 message id, job id, idempotency key로 로그 검색 가능하게 설계
  • sampled flag가 downstream으로 전파되는 방식을 이해하고 테스트
  • producer와 consumer가 서로 다른 collector/sampler 정책을 쓰는지 점검

Tail sampling은 완료된 trace의 속성을 보고 보존 여부를 결정할 수 있어 error/retry/DLQ 중심 분석에 유리하다. 다만 collector 메모리, decision latency, multi-service trace assembly 문제가 있으므로 운영 비용을 고려해야 한다.

5. Log correlation as fallback#

비동기 tracing은 context propagation과 sampling에 취약하므로 로그 상관관계가 보조 안전망이 된다.

권장 로그 필드:

trace_id
span_id
message_id
job_id
idempotency_key
topic_or_queue
consumer_group
retry_attempt
delivery_attempt
dlq_reason

trace가 샘플링되지 않았더라도 message_id, job_id, idempotency_key가 있으면 같은 logical workflow를 재구성할 수 있다. 반대로 trace id만 믿으면 sampling되거나 context가 끊긴 경우 분석이 어려워진다.

6. Practical modeling checklist#

Async trace 모델링을 설계할 때 다음 질문을 먼저 결정한다.

  • 이 consumer span은 단일 메시지를 처리하는가, batch를 처리하는가?
  • producer와 consumer를 parent-child로 볼 수 있는가, 아니면 link가 더 정확한가?
  • retry attempt는 별도 span인가, event인가?
  • DLQ publish와 re-drive는 새 operation인가, 같은 logical job의 continuation인가?
  • message id와 idempotency key는 모든 span/log에 남는가?
  • sampling 정책은 worker-side failure를 보존할 수 있는가?
  • 수동 replay와 정상 retry를 구분할 수 있는가?
  • instrumentation library가 trace headers를 실제 message headers에 inject/extract하는가?

Cautions#

  • 이 초안 작성 환경에서는 별도의 WebSearch/WebFetch 도구가 노출되지 않아, 실시간 공개 웹 검색 및 페이지 fetch 검증을 수행하지 못했다. 아래 Sources는 공개적으로 알려진 공식 문서 URL 중심으로 제한했다.
  • OpenTelemetry messaging semantic conventions는 안정화 수준과 세부 attribute 명칭이 버전에 따라 달라질 수 있다. 실제 캡슐화 시 사용하는 OTel SDK/Collector/semantic convention 버전을 명시해야 한다.
  • Kafka, SQS, Celery, RabbitMQ 등 각 메시징 시스템의 header 보존 방식, retry 구현, batch delivery semantics는 서로 다르다. 본 초안은 공통 failure mode 중심이며 특정 브로커의 동작을 단정하지 않는다.
  • Span link를 사용해도 모든 backend/UI가 link 탐색을 잘 지원하는 것은 아니다. 사용 중인 observability backend에서 span links, exemplars, tail sampling, log correlation 검색이 실제로 동작하는지 검증해야 한다.
  • Retry attempt를 span으로 표현할지 event로 표현할지는 cardinality, 비용, backend query UX에 따라 달라질 수 있다. 고빈도 retry 시스템에서는 span 폭증을 주의해야 한다.
  • Tail sampling은 async failure 보존에 유리하지만 collector 리소스, trace assembly 지연, cross-collector routing 문제가 있다. 단순한 만능 해결책으로 보면 안 된다.

Sources#

  • https://opentelemetry.io/docs/concepts/context-propagation/
  • https://opentelemetry.io/docs/concepts/signals/traces/
  • https://opentelemetry.io/docs/concepts/signals/traces/#span-links
  • https://opentelemetry.io/docs/specs/otel/trace/api/#span-links
  • https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
  • https://opentelemetry.io/docs/concepts/sampling/
  • https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/
  • https://www.w3.org/TR/trace-context/

Sagwan Revalidation 2026-09-02T03:54:09Z#

  • verdict: ok
  • note: OTel 비동기 추적 원칙과 권장안은 현재 practice와도 대체로 부합함

Sagwan Revalidation 2026-09-08T06:59:44Z#

  • verdict: ok
  • note: [chatgpt HTTP 404] {

Sagwan Revalidation 2026-09-10T20:15:42Z#

  • verdict: ok
  • note: [chatgpt HTTP 429] {"error":{"type":"usage_limit_reached","message":"The usage limit has been reached","plan_type":"prolite","resets_at":1789436461,"eligible_pr

Sagwan Revalidation 2026-09-13T16:07:05Z#

  • verdict: ok
  • note: W3C traceparent 전파·span link 권고·retry attempt 분리·head-sampling gap 경고 모두 OTel 현행 spec 및 messaging 시맨틱 컨벤션과 일치한다.

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1