Repository navigation
[TRACE-6] Outbox Dispatcher runtime 계측 및 오류 정제 #257
Copy link
Copy link
Closed
Labels
Description
목적 (Why)
무엇을 추가/변경하나?
outbox process하위에 Dispatcher child span을 추가한다.- Notification 조회·FCM 전송·상태 저장 결과를 계측한다.
- 성공/스킵/재시도/영구 실패 결과 속성을 추가한다.
- 상위 Outbox span의 예외 메시지를 정제하고, 원본 예외는 업무 호출자에게 재전파한다.
- SQL parameter, FCM token,
notificationId등 민감·고카디널리티 값은 기록하지 않는다. - JDBC 오류 정제 핸들러와 Outbox 오류 정제 책임을 분리한다.
왜 필요한가? (User value / Problem)
- TRACE-5로 처리 시도별
outbox processroot 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 80
140, 테스트 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 updatespan을 확인한다. - 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 processroot 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 종료, 민감값 미기록을 테스트로 검증한다.
의존성
- 선행 이슈/PR: [feat] TRACE-5 Outbox Span Link 구축 #251 TRACE-5 Outbox Span Link
- 블로킹 이슈: [bug] Outbox 조건부 Bean 등록 순서로 알림 폴링 미실행 #253 dev 서버 Outbox 폴링 미실행 (AC6 dev Tempo 검증 전 해결 필요)
증빙/참고 자료
설계/기획
- Trace 계약:
docs/TRACING_CONTRACT.md - OpenTelemetry Span Status: https://opentelemetry.io/docs/specs/otel/trace/api/#span-status
- OpenTelemetry Exception Semantic Conventions: https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/
- Micrometer Tracing API: https://docs.micrometer.io/tracing/reference/api.html
- Grafana Tempo TraceQL: https://grafana.com/docs/tempo/latest/traceql/
구현 완료 후
- Tempo Dispatcher span 화면:
- 테스트 결과:
- dev 배포 결과:
Reactions are currently unavailable
Activity
PHJ2000 commentedon Sep 15, 2026
이슈 내용에서 아래 세 가지는 한 번 정리하면 좋을 것 같습니다.
missing_notification을skipped로 분류하는 부분은 현재 예외 전파·롤백 동작과 맞지 않습니다. 기존 정책을 유지한다면error로 분류하는 게 자연스러워 보입니다.geonusp commentedon Sep 17, 2026
missing_notification라는 부분이 없습니다...PHJ2000 commentedon Sep 17, 2026
상위 span 오류 정제가 추가할 작업에 들어가 있어서 이번에 새로 구현하신다는 뜻으로 읽었는데, 기존 정제를 유지하고 검증한다는 말씀이었군요. 그럼 이 부분은 이해했습니다.
표에 missing_notification이 스킵 사유로 들어가 있어서 ‘알림이 없으면 처리를 건너뛴다’고 이해했습니다. 말씀 듣고 다시 보니 실제 동작까지 그렇게 적으신 건 아니었네요. 제가 속성값을 보고 예외 전파·롤백 정책까지 바뀐다고 확대해서 해석한 뒤 충돌한다고 단정했습니다. 이 부분은 죄송합니다.
블로킹 이슈를 현재 진행을 막는 문제로 읽어서 빼도 될 것 같다고 말씀드렸는데, 먼저 해결돼야 했던 문제를 이력으로 남기신 거였군요. 그 취지라면 그대로 두어도 될 거 같습니다.