Skip to content

[TRACE-6] Outbox Dispatcher runtime 계측 및 오류 정제 #257

Closed

Description

@geonusp

목적 (Why)

무엇을 추가/변경하나?

  • outbox process 하위에 Dispatcher child span을 추가한다.
  • Notification 조회·FCM 전송·상태 저장 결과를 계측한다.
  • 성공/스킵/재시도/영구 실패 결과 속성을 추가한다.
  • 상위 Outbox span의 예외 메시지를 정제하고, 원본 예외는 업무 호출자에게 재전파한다.
  • SQL parameter, FCM token, notificationId 등 민감·고카디널리티 값은 기록하지 않는다.
  • JDBC 오류 정제 핸들러와 Outbox 오류 정제 책임을 분리한다.

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

  • TRACE-5로 처리 시도별 outbox process root span은 생기지만, Dispatcher 내부의 조회·FCM·상태 저장 구간과 처리 결과는 Trace에서 구분되지 않는다.
  • Dispatcher 예외가 OutboxTracing까지 전파되면 span.error(exception)가 원본 예외를 기록하므로, JDBC child span이 정제되어도 notificationId가 상위 span에 남을 수 있다.

비즈니스/기술적 배경

  • 재시도 가능 실패와 영구 실패를 Trace에서 구분해야 Outbox 처리 장애 원인을 좁힐 수 있다.
  • 현재 SanitizingQueryTracingObservationHandler는 JDBC query span만 정제하므로, 이름과 책임이 공통 Trace 예외 정제로 오해될 수 있다.

전체 설계 (How)

핵심 아이디어/접근법

  • OutboxEventDispatcher 실행 구간을 child span으로 나누고 결과 속성을 기록한다.
  • 기존 catch 범위를 벗어난 예외는 상위 span에 일반화된 메시지와 error.type만 기록하고 원본 예외를 재전파한다.
  • 기존 FirebaseMessagingException 분류, 재시도 횟수, backoff, 트랜잭션 전파 정책은 변경하지 않는다.
  • SanitizingQueryTracingObservationHandler를 JdbcQueryErrorSanitizingObservationHandler로 이름 변경하고 JDBC 범위를 유지한다.

주요 컴포넌트/모듈

  • OutboxEventDispatcher: child span 경계와 결과 속성
  • OutboxTracing: 상위 span 오류 정제와 원본 예외 재전파
  • OutboxFirebaseConfig: tracing 컴포넌트 주입
  • JdbcQueryErrorSanitizingObservationHandler: JDBC query span 오류 정제 (이름 변경)

데이터 흐름/상태 변화

outbox process [CONSUMER root, TRACE-5, Link -> producer]
├── outbox dispatch [INTERNAL]
│   ├── outbox notification lookup [INTERNAL]
│   ├── outbox fcm send [CLIENT]
│   └── outbox state update [INTERNAL]
└── 오류/결과 attributes
속성 허용 값
outbox.item.outcome success, skipped, retryable_failure, permanent_failure, error
outbox.fcm.outcome success, retryable_failure, permanent_failure
outbox.item.skip.reason already_sent, missing_notification
outbox.retry.count 음이 아닌 정수

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

  • Single PR (production 80140, 테스트 160260, 문서·계약 30~60 LOC 예상)
  • Stacked PR (의존성 체인)
  • Parallel Change (Expand-Migrate-Contract)
  • Branch by Abstraction

Single PR 근거

  • child span, 결과 속성, 상위 span 정제가 같은 처리 경로에 있어 함께 검증해야 한다.
  • 예상 범위를 넘거나 Collector/agent 도입, DB 정책 변경이 필요해지면 별도 작업으로 분리한다.

부분 머지 안전성

  • 전체 머지: ✅ 업무 정책 변경 없이 계측과 오류 정제 활성화

전체 Acceptance Criteria (AC)

  • AC1: outbox dispatch, outbox notification lookup, outbox fcm send, outbox state update span을 확인한다.
  • AC2: 정상/스킵/재시도/영구 실패 결과가 구분된다.
  • AC3: 상위 span에 원본 예외·식별자·SQL 값이 노출되지 않는다.
  • AC4: ./gradlew test --tests 'com.beachcheck.outbox.*' --tests 'com.beachcheck.global.tracing.*'가 통과한다.
  • AC5: ./gradlew check가 통과한다.
  • AC6: dev Tempo에서 정상·실패 처리 시도를 확인한다.

영향 범위

  • API 스펙 변경
  • DB 스키마 변경
  • 설정/환경변수 변경
  • 라이브러리 추가/업그레이드
  • 캐시/큐/외부 연동 영향: Outbox 처리·FCM 전송 구간 계측 추가, 업무 처리 정책은 유지
  • UX/UI 변경

Breaking Changes / 호환성

  • Breaking change 없음

  • Breaking change 있음

  • TRACE-5의 outbox process root span과 Span Link 계약은 변경하지 않는다.

  • 원본 예외는 기존과 동일하게 업무 호출자에게 전파된다.


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

Feature Flag / 단계적 배포

  • 사용 안함
  • 사용함

성공 지표/메트릭

  • 한 번의 처리 시도에서 Dispatcher 실행 구간과 최종 결과를 Tempo waterfall로 구분할 수 있다.

모니터링/알림

  • 확인 위치: Grafana Explore -> Tempo
  • span 계약: outbox process 하위 outbox dispatch, outbox notification lookup, outbox fcm send, outbox state update
  • 오류 확인: status description이 일반화된 메시지이고 exception event에 notificationId, SQL parameter, constraint 원문이 없다.

리스크 & 롤백

리스크

  • 예외 경로에서 span 종료나 scope 복구가 누락되면 다음 이벤트로 trace context가 누수될 수 있음
  • 오류 정제 과정에서 원본 예외가 바뀌면 트랜잭션 롤백과 Outbox 상태 전이가 달라질 수 있음
  • 이미 정책으로 처리된 FCM 예외를 상위 span에 ERROR로 중복 기록할 수 있음

완화 및 롤백 전략

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

  • Feature flag로 비활성화 가능

  • 데이터 롤백 필요

  • 원본 예외 재전파, span 종료, 민감값 미기록을 테스트로 검증한다.


의존성


증빙/참고 자료

설계/기획

구현 완료 후

  • Tempo Dispatcher span 화면:
  • 테스트 결과:
  • dev 배포 결과:

Activity

  1. self-assigned this
    on Sep 14, 2026
  2. PHJ2000 commented on Sep 15, 2026

    @PHJ2000
    Collaborator

    이슈 내용에서 아래 세 가지는 한 번 정리하면 좋을 것 같습니다.

    1. 상위 Outbox span 오류 정제는 #252에서 이미 반영된 내용이라, 이번에는 신규 구현보다 기존 동작이 유지되는지 검증하는 범위로 보면 좋을 것 같습니다.
    2. missing_notification을 skipped로 분류하는 부분은 현재 예외 전파·롤백 동작과 맞지 않습니다. 기존 정책을 유지한다면 error로 분류하는 게 자연스러워 보입니다.
    3. #253은 #254에서 해결된 상태라 블로킹 이슈에서는 빼도 될 것 같습니다.
  3. geonusp commented on Sep 17, 2026

    @geonusp
    CollaboratorAuthor

    이슈 내용에서 아래 세 가지는 한 번 정리하면 좋을 것 같습니다.

    1. 상위 Outbox span 오류 정제는 #252에서 이미 반영된 내용이라, 이번에는 신규 구현보다 기존 동작이 유지되는지 검증하는 범위로 보면 좋을 것 같습니다.
    2. missing_notification을 skipped로 분류하는 부분은 현재 예외 전파·롤백 동작과 맞지 않습니다. 기존 정책을 유지한다면 error로 분류하는 게 자연스러워 보입니다.
    3. #253은 #254에서 해결된 상태라 블로킹 이슈에서는 빼도 될 것 같습니다.
    1. 이슈 본문에 이미 그렇게 적혀 있는데 무엇을 본지 모르겠습니다.
    2. 이슈 본문에 missing_notification라는 부분이 없습니다...
    3. 블로킹 이슈는 해결되지 않은 것만 넣는게 아닌 그전에 문제가 생겨 선행되야할 것을 적는 것으로 생각합니다. 그래서 해결된 것을 둬도 상관없다고 생각합니다.
  4. PHJ2000 commented on Sep 17, 2026

    @PHJ2000
    Collaborator

    이슈 내용에서 아래 세 가지는 한 번 정리하면 좋을 것 같습니다.

    1. 상위 Outbox span 오류 정제는 #252에서 이미 반영된 내용이라, 이번에는 신규 구현보다 기존 동작이 유지되는지 검증하는 범위로 보면 좋을 것 같습니다.
    2. missing_notification을 skipped로 분류하는 부분은 현재 예외 전파·롤백 동작과 맞지 않습니다. 기존 정책을 유지한다면 error로 분류하는 게 자연스러워 보입니다.
    3. #253은 #254에서 해결된 상태라 블로킹 이슈에서는 빼도 될 것 같습니다.
    1. 이슈 본문에 이미 그렇게 적혀 있는데 무엇을 본지 모르겠습니다.
    2. 이슈 본문에 missing_notification라는 부분이 없습니다...
    3. 블로킹 이슈는 해결되지 않은 것만 넣는게 아닌 그전에 문제가 생겨 선행되야할 것을 적는 것으로 생각합니다. 그래서 해결된 것을 둬도 상관없다고 생각합니다.
    1. 상위 span 오류 정제가 추가할 작업에 들어가 있어서 이번에 새로 구현하신다는 뜻으로 읽었는데, 기존 정제를 유지하고 검증한다는 말씀이었군요. 그럼 이 부분은 이해했습니다.

    2. 표에 missing_notification이 스킵 사유로 들어가 있어서 ‘알림이 없으면 처리를 건너뛴다’고 이해했습니다. 말씀 듣고 다시 보니 실제 동작까지 그렇게 적으신 건 아니었네요. 제가 속성값을 보고 예외 전파·롤백 정책까지 바뀐다고 확대해서 해석한 뒤 충돌한다고 단정했습니다. 이 부분은 죄송합니다.

    3. 블로킹 이슈를 현재 진행을 막는 문제로 읽어서 빼도 될 것 같다고 말씀드렸는데, 먼저 해결돼야 했던 문제를 이력으로 남기신 거였군요. 그 취지라면 그대로 두어도 될 거 같습니다.

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