messaging-superpowers-package 설계서/계획서 기반 구현. registry를 19 → 43 leaf로 확장하고 src/messaging 아래 24개 leaf를 등록. - core-api: M1 publish/consume + M2 batch·delayed·pause-resume - policy/transport-spi: 재시도 결정, DLQ orchestration, admission control, lifecycle - kafka·rabbit(Stable): contiguous commit, confirm/return 상관, 배치, 보안 설정 - pulsar·nats(Experimental): 기본 비활성, live 인증 없음을 코드로 기록 - outbox/inbox/claim-check: 트랜잭션 결합, lease, 무결성 검증 - admin: plan → approve → execute를 타입으로 강제 - 문서 9종, infra compose 7종, JMH 벤치마크 3종 검증: 아키텍처 게이트 3종 통과, 24개 leaf 전부 check 통과, messaging 테스트 604개 통과/0 실패. 미완: 계획서가 요구한 실 브로커 IT 40개 중 7개만 작성. Rabbit 13 / Outbox 6 / Inbox 4 / NATS·Pulsar·Share 5 / testkit 2 / starter·admin 3, 그리고 TLS·ACL 2개가 남음.
114 lines
4.0 KiB
Markdown
114 lines
4.0 KiB
Markdown
# 운영 Runbook
|
|
|
|
## 배포 전 체크
|
|
|
|
```bash
|
|
./gradlew verifyCleanArchitectureDependencies --console=plain
|
|
./gradlew verifyRuntimeModuleMembership --console=plain
|
|
./gradlew verifyOneTypePerFile --console=plain
|
|
```
|
|
|
|
destination profile은 startup에서 검증된다. 아래는 **부팅 실패**다.
|
|
|
|
- ordered destination + reorder 가능 retry
|
|
- `ordering=KEY` + key resolver 없음
|
|
- payload 상한 > 8,388,608 bytes
|
|
- DLQ 자기 참조 / retry 자기 참조
|
|
- retry·DLQ 그래프 cycle
|
|
- 미등록 retry·DLQ destination
|
|
- M1 destination + manual settlement
|
|
- `AT_LEAST_ONCE` + confirmation `NONE`
|
|
- production profile + topology auto-create
|
|
- broker topology가 manifest와 불일치
|
|
|
|
## 증상별 대응
|
|
|
|
### publish가 AMBIGUOUS로 쏟아진다
|
|
|
|
broker confirm 경로 문제다. 실패가 아니다.
|
|
|
|
1. `PublishEvidence.transmission`이 `MAY_HAVE_BEEN_TRANSMITTED`인지 확인
|
|
2. Kafka: `delivery.timeout.ms`, ISR 상태, leader election 확인
|
|
3. Rabbit: confirm timeout, channel 상태 확인
|
|
4. Outbox를 쓰고 있다면 `status='AMBIGUOUS'` row가 같은 messageId로 재시도 중이다. **정상이다.**
|
|
5. consumer 쪽 Inbox가 중복을 흡수하는지 확인
|
|
|
|
`AMBIGUOUS`를 실패로 취급해 새 messageId로 재발행하지 말 것. 중복이 복구 불가능해진다.
|
|
|
|
### DLQ가 비어 있는데 메시지가 사라졌다
|
|
|
|
DLQ publish 실패 시 source는 settlement되지 않는다. 메시지는 source에 남아 재전달된다.
|
|
|
|
1. `msg.failure-code`가 `DEAD_LETTER_*`인 로그 확인
|
|
2. DLQ destination이 실제로 존재하는지 (topology validation)
|
|
3. DLQ credential에 publish 권한이 있는지
|
|
|
|
### consumer lag이 한 partition에서만 증가한다
|
|
|
|
`ContiguousPartitionOffsetTracker`가 gap에서 멈춘 것이다. 설계된 동작이다.
|
|
|
|
commit은 **연속** 완료 offset까지만 전진한다. offset 11이 아직 실행 중이면
|
|
10과 12가 끝나도 watermark는 10에 머문다. 12를 commit하면 consumer가 죽었을 때 11을 잃는다.
|
|
|
|
1. 해당 partition의 in-flight를 확인
|
|
2. 느린 handler를 찾는다 (`handlerTimeout` 초과 여부)
|
|
3. 필요하면 `PAUSE_PARTITION` retry가 걸려 있는지 확인
|
|
|
|
### 재시도 폭풍
|
|
|
|
`RetryPolicy.jitter=false`인지 확인한다. jitter 없이는 같은 초에 실패한 모든 consumer가
|
|
같은 초에 재시도한다.
|
|
|
|
### shutdown이 오래 걸린다
|
|
|
|
`GracefulShutdownCoordinator`가 in-flight를 기다리는 중이다.
|
|
|
|
- `inFlight()`가 0이 되면 즉시 종료
|
|
- drain deadline(기본 30초) 초과 시 남은 작업을 **unsettled로 포기**한다 → broker가 재전달
|
|
- draining 시작 후 새 retry attempt는 만들지 않는다
|
|
|
|
## Destructive 작업
|
|
|
|
전부 `DestructiveOperationGuard`를 통과해야 한다.
|
|
|
|
| 조건 | 요구 |
|
|
|---|---|
|
|
| admin credential | application runtime은 보유하지 않음 |
|
|
| `AdminApproval` | 유효기간 내 |
|
|
| dry-run | 항상 허용 |
|
|
|
|
### Replay
|
|
|
|
```text
|
|
기본: 격리된 consumer group (replay-<requestId>)
|
|
기존 group 대상: 승인 티켓 필수
|
|
```
|
|
|
|
기존 production group으로 replay하는 것은 "다시 읽기"가 아니라 **live consumer를 되감는 것**이다.
|
|
그 사이의 모든 것이 재처리된다.
|
|
|
|
### Redrive
|
|
|
|
```text
|
|
dry-run으로 후보 수 확인
|
|
→ 승인 획득
|
|
→ batch 100건 이하로 실행
|
|
→ republish CONFIRMED 인 것만 DLQ에서 settlement
|
|
```
|
|
|
|
`redriveId`로 재구동 루프를 추적한다. 같은 메시지가 반복해서 redrive되면
|
|
근본 원인이 해결되지 않은 것이다.
|
|
|
|
### Offset reset
|
|
|
|
`KafkaOffsetResetExecutor`는 승인 predicate를 **생성자 인자**로 받는다.
|
|
승인 소스 없이 조립된 runtime은 물리적으로 reset을 수행할 수 없다.
|
|
|
|
## Topology
|
|
|
|
production topology는 IaC가 만들고 애플리케이션은 **검증만** 한다.
|
|
|
|
`TopologyValidationRuntime`은 모든 불일치를 한 번에 보고하고 startup을 실패시킨다.
|
|
partition 수가 다르면 destination이 광고하는 ordering 보장이 달라지고,
|
|
`min.insync.replicas`가 없으면 `acks=all`의 의미가 달라진다.
|