Files
DongHyeonka d646c2f12f feat(messaging): 브로커 중립 메시징 플랫폼 24개 leaf 추가
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개가 남음.
2026-08-14 14:55:38 +09:00

4.3 KiB

Retry · DLQ · Redrive

자동 retry는 opt-in이다

일반 destination의 기본값은 retry 없음이다. 순서를 깨거나, 멱등하지 않은 side effect를 증폭시키거나, 이미 throttle된 downstream을 더 때리는 retry는 보이는 실패보다 나쁘다.

결정 순서

DefaultRetryDecisionEngine은 아래 순서를 위에서 아래로 평가한다.

1. non-retryable category  → parking(DeadLetter) 또는 Reject
2. attempt >= maxAttempts  → DeadLetter
3. PRESERVE + ordered + orderedStream capability → PauseAndRetry
4. mode=PAUSE_PARTITION    → PauseAndRetry
5. mode=RETRY_DESTINATION + ALLOW_REORDER → PublishToRetryDestination
6. mode=INLINE|BLOCKING    → RetryInline
7. mode=BROKER_DELAYED + delayedDelivery capability → PublishToRetryDestination
8. 그 외                   → DeadLetter

retryability를 attempt 예산보다 먼저 검사한다. deserialization 실패는 payload가 바뀌지 않으므로 재시도가 3번 더 실패할 뿐이다. 첫 delivery에서 바로 park한다.

순서 보존 전략을 재발행 전략보다 먼저 검사한다. 둘 다 설정되어 있어도 ordered destination이 reorder 경로로 흘러내리지 않는다.

기본 non-retryable

DESERIALIZATION, AUTHENTICATION, AUTHORIZATION, CONFIGURATION은 자동 retry하지 않는다. 매 redelivery마다 동일하게 실패하므로 부하만 늘어난다. destination profile의 retryableCategories로 명시적으로 뒤집을 수는 있다.

Backoff

min(maxDelay, initialDelay * multiplier^(attempt-1)), 이후 full jitter.

full jitter는 [0, delay] 균등 분포다. jitter가 없으면 같은 초에 실패한 모든 consumer가 같은 초에 재시도하고, downstream의 회복이 재시도 폭풍으로 즉시 무효화된다.

Kafka: pause-and-seek vs retry topic

전략 순서 언제
PAUSE_PARTITION 유지 ordered destination
RETRY_DESTINATION 깨짐 work queue, ALLOW_REORDER 명시

pause-and-seek는 메시지가 로그의 자기 자리를 떠나지 않는다. partition을 멈추고, 기다리고, 같은 offset으로 seek해 재전달한다. 뒤의 메시지도 함께 기다리며 이것이 의도된 동작이다.

RabbitMQ: delayed retry queue

core broker에 per-message delay가 없으므로 TTL + DLX로 구현한다. retry queue의 x-message-ttl이 만료되면 x-dead-letter-exchange를 통해 work queue로 되돌아간다.

주의: TTL 만료는 큐 head에서 평가된다. 하나의 retry queue에 서로 다른 delay를 섞으면 독립적으로 만료되지 않는다.

basic.nack(requeue=true)는 사용하지 않는다. delay 없이 큐 head로 되돌리므로 hot loop가 된다.

DLQ: publish 확인 후 settlement

이것이 dead lettering이 데이터 손실이 되지 않게 하는 유일한 불변식이다.

DLQ envelope 생성 (원래 messageId 유지)
→ DLQ publish
→ CONFIRMED 이면 source settlement
→ REJECTED / AMBIGUOUS 이면 source를 settlement하지 않음

source를 먼저 ACK하면, DLQ publish가 실패했을 때 메시지의 사본이 어디에도 남지 않는다. broker는 이미 해제했고 DLQ는 받지 못했다.

AMBIGUOUS DLQ publish는 중복을 만든다. 이것이 의도된 trade다. DLQ는 사람이 읽는 곳이고 중복은 알아볼 수 있지만, 손실은 복구할 수 없다.

DLQ envelope 내용

reserved header에만 기록한다. payload에 넣지 않는다.

msg.failure-category, msg.failure-code, msg.origin-destination,
msg.retry-attempt, msg.first-failure-at, msg.last-failure-at

stack trace, exception message, secret header, 실제 key는 넣지 않는다. DLQ는 원본 topic보다 오래 보관되고 더 많은 사람이 읽는다.

Redrive

M4 Admin 전용이다. DestructiveOperationGuard를 통과해야 한다.

  • admin credential 필요 (application runtime은 보유하지 않는다)
  • 유효기간 내 AdminApproval 필요
  • dry-run은 항상 허용 (계획이 공짜여야 사람이 계획한다)
  • batch 상한 100건
  • source == target 금지
  • redriveIdmessageId와 별개다. 재구동 루프를 식별하기 위해서다.

redrive도 publish → settlement 순서다. republish가 confirm되지 않은 메시지는 DLQ에 남는다.