Skip to content

[feat] TRACE-5 Outbox Span Link 구축 #251

Closed

Description

@geonusp

목적 (Why)

무엇을 추가/변경하나?

  • Outbox 적재 시점의 W3C traceparent를 DB에 저장한다.
  • 각 Outbox 처리 시도를 별도 SpanKind.CONSUMER root span으로 만들고 producer span을 Span Link로 연결한다.
  • legacy/null/invalid context row는 Link 없이 기존과 동일하게 처리한다.

왜 필요한가? (User value / Problem)

  • HTTP 요청과 비동기 Outbox 처리 사이의 인과관계가 현재 Trace에서 끊긴다.
  • 지연·재시도되는 Outbox를 하나의 장기 trace로 만들지 않고도 producer에서 각 처리 시도를 추적할 수 있어야 한다.

비즈니스/기술적 배경

  • Outbox는 처리 지연, 독립 실패, 재시도가 가능하므로 producer context를 parent로 복원하면 trace 수명과 retry 표현이 왜곡된다.
  • 각 처리 시도를 독립 trace로 유지하고 OpenTelemetry Span Link로 인과관계를 표현한다.
  • TRACE-1의 Micrometer Tracing/OpenTelemetry 기반을 사용하며 native OpenTelemetry API 직접 의존은 추가하지 않는다.

전체 설계 (How)

핵심 아이디어/접근법

  • V14에서 outbox_events.producer_traceparent VARCHAR(64) NULL 컬럼을 추가한다.
  • NotificationService가 active span을 W3C traceparent로 직렬화해 Outbox row에 저장한다.
  • OutboxPublisher가 이벤트별 outbox process consumer root span을 생성한다.
  • 저장된 context가 유효하면 span 생성 전에 Link 1개를 추가한다.
  • 처리 예외와 기존 Outbox 상태 전이 정책은 바꾸지 않는다.

주요 컴포넌트/모듈

  • OutboxEvent: nullable producer context 저장
  • OutboxTracing: active context 캡처, 안전한 파싱, linked root span scope 관리
  • NotificationService: producer context 전달
  • OutboxPublisher: 이벤트별 consumer root 생성
  • OutboxFirebaseConfig: tracing 컴포넌트 주입

데이터 흐름/상태 변화

producer HTTP/Scheduler trace
└─ Notification + OutboxEvent 저장
   └─ producer_traceparent 저장

consumer attempt trace #1
└─ outbox process [root, CONSUMER, Link -> producer span]
   └─ 기존 dispatcher 실행

consumer attempt trace #2
└─ outbox process [별도 root, 같은 producer Link]
   └─ 기존 retry 처리

PR 분할 전략 (300라인 초과 시 필수)

  • Single PR (약 300~440 LOC 예상)
  • Stacked PR (의존성 체인)
  • Parallel Change (Expand-Migrate-Contract)
  • Branch by Abstraction

Single PR 근거

  • nullable migration, producer 저장, consumer Link가 함께 있어야 end-to-end 인과관계를 검증할 수 있다.
  • migration은 구버전 insert와 호환되고 신규 코드는 legacy/null row를 처리하므로 단계별 배포 장점이 작다.
  • TRACE-6의 poll/dispatch/FCM/retry 결과 계측은 이 이슈에서 제외한다.

부분 머지 안전성

  • 전체 머지: ✅ nullable schema와 legacy 호환을 포함해 기능 활성화

전체 Acceptance Criteria (AC)

  • AC1: V14 migration이 producer_traceparent VARCHAR(64) NULL을 추가한다.
  • AC2: active producer span이 있으면 trace ID, span ID, sampled flags가 Outbox row에 저장된다.
  • AC3: active span이 없으면 null이 저장되고 기존 Notification/Outbox 원자성이 유지된다.
  • AC4: 각 처리 시도는 parent 없는 outbox process CONSUMER root span으로 생성된다.
  • AC5: 유효한 context의 consumer span은 producer trace/span을 가리키는 Link 1개를 갖고 producer와 다른 Trace ID를 사용한다.
  • AC6: retry 시도별 Trace ID는 서로 다르며 동일 producer span을 Link한다.
  • AC7: null, blank, malformed, all-zero context row는 Link 없이 정상 처리된다.
  • AC8: context 파싱 실패가 업무 예외, 재시도 증가, 로그 경고의 원인이 되지 않는다.
  • AC9: trace context 원문과 업무 식별자를 새 로그 또는 span attribute에 기록하지 않는다.
  • AC10: Outbox 상태 전이, FCM 실패 분류, backoff, transaction 정책이 변경되지 않는다.
  • AC11: 관련 자동 테스트와 ./gradlew check가 통과한다.
  • AC12: dev Tempo에서 { link:traceID = "<producer-trace-id>" }로 consumer attempt trace를 조회할 수 있다.

영향 범위

  • API 스펙 변경
  • DB 스키마 변경: V14__add_producer_traceparent_to_outbox_events.sql
  • 설정/환경변수 변경
  • 라이브러리 추가/업그레이드
  • 캐시/큐/외부 연동 영향: Outbox row에 trace context 저장, 업무 처리 정책은 유지
  • UX/UI 변경

Breaking Changes / 호환성

  • Breaking change 없음

  • Breaking change 있음

  • 신규 컬럼은 nullable이며 default와 기존 row backfill을 두지 않는다.

  • 구버전 애플리케이션 insert와 신규 애플리케이션의 legacy row 처리를 모두 허용한다.

  • invalid context는 Link 없이 처리한다.


롤아웃/관측 (배포 전략)

Feature Flag / 단계적 배포

  • 사용 안함
  • 사용함

성공 지표/메트릭

  • producer Trace ID로 consumer attempt trace가 조회된다.
  • retry 시도들이 서로 다른 trace로 기록되고 동일 producer Link를 가진다.

모니터링/알림

  • 확인 위치: Grafana Explore > Tempo
  • TraceQL: { link:traceID = "<producer-trace-id>" }
  • span 계약: outbox process, root, kind=consumer

리스크 & 롤백

리스크

  • 잘못된 context 파싱이 Outbox 업무 처리를 막을 수 있음
  • 기존 Scheduler span을 parent로 상속해 처리 시도가 독립 trace가 되지 않을 수 있음
  • scope 종료 누락으로 다음 이벤트에 trace context가 누수될 수 있음

완화 및 롤백 전략

  • 애플리케이션 변경은 단일 PR로 revert 가능

  • Feature flag로 비활성화 가능

  • 데이터 롤백 필요

  • null/invalid context 통합 테스트와 scope 복원 테스트를 추가한다.

  • 애플리케이션 rollback 시 nullable 컬럼은 남기며 down migration이나 컬럼 삭제를 수행하지 않는다.


의존성


증빙/참고 자료

설계/기획

구현 완료 후

  • Tempo producer/consumer Link 화면:
  • 테스트 결과:
  • dev 배포 결과:

AC12 검증 결과 (dev Tempo)

Image Image Image
  • producer trace ID: f1ea84cd6cb349fe98bddad81307d12c
  • consumer attempt trace ID: 15ca3102ceb6443c45bb0fdca8616d0d
  • TraceQL link 조회: { link:traceID = "f1ea84cd6cb349fe98bddad81307d12c" }
  • consumer span 조회: { span:name = "outbox process" && span:kind = consumer }
  • 확인 결과: outbox process root span의 kind=consumer 및 하위 JDBC SELECT notifications span 확인
  • 두 조회 결과가 동일한 consumer trace ID로 일치하여 AC12 검증 완료

Activity

  1. self-assigned this
    on Sep 5, 2026
  2. geonusp commented on Sep 14, 2026

    @geonusp
    CollaboratorAuthor

    AC12 검증 결과

    dev Tempo에서 producer trace link로 consumer attempt trace를 조회하고 consumer span kind를 확인했습니다.

    • TraceQL: { span:name = "outbox process" && span:kind = consumer }
    • consumer attempt trace ID: 15ca3102ceb6443c45bb0fdca8616d0d
    • consumer root span: outbox process
    • span kind: consumer
    • 하위 JDBC span: SELECT notifications
    • producer trace link 조회 결과에서 동일한 consumer trace ID 확인

    AC12 검증을 통과했습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions