4.7 KiB
4.7 KiB
title, category, error_codes, severity, owner, last_updated, status
| title | category | error_codes | severity | owner | last_updated | status | |
|---|---|---|---|---|---|---|---|
| Runbook — outbox publish 일시 실패 | TRANSIENT_DEPENDENCY |
|
P2 | oncall | 2026-06-11 | stub |
Runbook: outbox publish 일시 실패 (runbook://outbox/publish-failed)
1. Trigger
이 runbook은 다음 alert에서 발동됩니다.
- alert name:
outbox_publish_failed_rate또는outbox_publisher_lag - alert payload 필수 field:
error.code=OUTBOX_PUBLISH_FAILED,event_type,correlation_id,runbook_link - 임계 (metrics.yaml verbatim):
- P2:
outbox.publisher.published.total{outcome=FAILED}rate > 1% for 10m - P2:
outbox.publisher.lag> 60s for 10m / P1: > 300s for 5m - P2:
outbox.pending.size{status=PENDING}growing for 10m
- P2:
2. First Response (5분 이내)
Step 1 — 확인
- ERROR log에서
OUTBOX_PUBLISH_FAILED라인 확인:event_type,event_id,correlation_id,attempt_count추출 - broker 상태 확인:
APP_MESSAGING_BROKER값(공백이면 messaging 비활성)과 broker endpoint 가용성APP_MESSAGING_BROKER가 공백인 채로 relay가 켜져 있으면 애플리케이션이 기동하지 않는다 (OutboxRelayBrokerRequirementValidator, MSG-024). 이 조합에서는 publish가 전부AdapterDisabledException으로 실패하며 PENDING row가 DEAD까지 소진되기 때문이다. 기동 실패를 보고 있다면 broker를 설정하거나ca-skeleton.outbox.relay-enabled=false로 둔다.- 기동은 했는데 실패가 쌓인다면 broker는 설정돼 있고 도달이 안 되는 것이다 — endpoint부터 본다.
outbox.pending.sizestatus 분포 확인 (FAILED 누적 vs PENDING 누적)
Step 2 — 임시 격리
- 일시 실패는 자동 backoff 재시도(30s × 2^(attempt-1) + jitter, max attempts 3)가 동작 — 즉시 수동 개입 불필요
- broker 장기 다운이면 DEAD 전이 누적 전에 broker 회복을 우선 (max attempts 소진 시
runbook://outbox/dead-letter로 이관) - relay 자체를 멈춰야 하면
ca-skeleton.outbox.relay-enabled=false로 스케줄러 비활성 (이벤트는 outbox 테이블에 안전하게 보존됨 — 유실 없음)
3. Diagnosis
- log query:
{service="app"} | error.code="OUTBOX_PUBLISH_FAILED" | stats count by event_type - metric panel:
outbox.publisher.published.total{outcome}— FAILED 비율outbox.publisher.lag{event_type}— 최고령 미발행 이벤트 ageoutbox.pending.size{status}— 상태별 분포
- DB 확인:
SELECT status, count(*) FROM outbox_event GROUP BY status; - 가능한 원인:
- broker outage/네트워크 → broker 측 회복 대기
- Kafka adapter 미구성(enabled인데 brokers 누락은 기동 시 차단됨) / disabled 상태에서 producer 활성화
- poison event (직렬화 불가/payload 계약 위반) → 재시도 무의미, attempts 소진 후 DEAD로 흘러감 (의도된 동작)
- 동일 aggregate head 실패로 후행 이벤트가 FIFO 게이트에 차단되어 lag 증가 (strict per-aggregate FIFO — 설계 의도)
4. Mitigation
- 단기: broker 회복 후 backoff 만료 시 자동 재발행 —
outcome=PUBLISHED회복 확인 - IN_FLIGHT orphan(claim 후 crash)은 in-flight-timeout(기본 PT5M) 경과 후 자동 재claim — at-least-once이므로 중복 발행 가능, consumer dedupe(idempotencyKey)가 흡수
- 장기:
ca-skeleton.outbox.poll-interval/batch-size조정, broker 가용성 SLA 점검, 빈발 event_type의 payload 계약 검토
5. Escalation
- P1 lag(>300s 5m) 지속 + broker 회복 불가면 broker/infra 팀에 page
- DEAD 전이가 발생하기 시작하면
runbook://outbox/dead-letter절차로 이관
6. Recovery / Verification
- 회복 확인 metric:
outcome=FAILEDrate < 1% 10분 지속,outbox.publisher.lag< 60s,outbox.pending.size{status=FAILED}감소 추세 - post-incident: 실패 구간의 DEAD row 유무 확인, consumer 측 중복 처리량 확인(dedupe 동작 검증), backoff/attempts 상수 재평가
7. Related
- error-codes.yaml rows:
OUTBOX_PUBLISH_FAILED(TRANSIENT_DEPENDENCY, retryable=true, retry_after 30s) - metrics.yaml:
outbox.publisher.published.total,outbox.publisher.lag,outbox.pending.size - 코드:
application-corePublishPendingOutboxEventsUseCase(상태머신),adapter-persistenceoutbox/OutboxEventJpaRepository(SKIP LOCKED claim + FIFO 게이트),adapter-outboundmessaging/outbox/KafkaOutboxMessagePublishAdapter(fail-closed) - 관련 runbook: outbox-dead-letter
- 관련 branch: feature-domain-event-outbox-contract, feature-background-job-async-contract (retry/DLQ vocabulary SSOT)
Stub 상태 안내: 이 runbook은 skeleton 단계의 stub. 실제 broker 채택·alert 라우팅·대시보드 링크 확정 시 보강 필요.