반복 실패한 메시지를 따로 모아 두는 큐(Dead Letter Queue, 죽은 편지함).
왜 필요한가 — poison message
처리할 수 없는 메시지 하나가 큐 맨 앞에 있으면 어떻게 되나.
큐: [깨진 메시지] [정상] [정상] [정상] …
↑ 계속 실패 → 재시도 → 실패 → 재시도 …
그동안 뒤의 정상 메시지들은 전부 대기
하나가 전체를 막는다. 앞서 본 HOL Blocking과 같은 구조다. 일정 횟수를 넘기면 옆으로 빼내야 흐름이 산다.
흐름
다이어그램 로딩 중…
실패를 두 종류로 나눠야 한다
| 종류 | 예 | 대응 |
|---|---|---|
| 일시적 | DB 커넥션 끊김, 타임아웃, 5xx | 재시도하면 성공 → 백오프 재시도 |
| 영구적 | 스키마 불일치, 필수 필드 없음, 4xx | 몇 번을 해도 실패 → 즉시 DLQ |
이 구분 없이 전부 재시도하면 영구 실패 메시지가 재시도 예산을 다 써 버린다. 반대로 전부 DLQ로 보내면 일시적 장애 때 대량이 DLQ로 쏟아진다.
DLQ에 무엇을 함께 남기나
메시지 본문만 남기면 나중에 원인을 알 수 없다.
원본 메시지
- 실패 예외 메시지·스택
- 시도 횟수
- 원본 토픽·파티션·오프셋
- 최초 실패 시각
운영에서 가장 흔한 실수 — 방치
DLQ를 만들어 두고 아무도 안 본다. 그러면 그냥 조용히 데이터를 버리는 것과 같다.
- 적재 건수를 지표로 잡고 임계치 알림을 건다
- 재처리 도구를 미리 만들어 둔다(원본 큐로 되돌려 넣기)
- 실패가 특정 시각에 몰렸다면 메시지가 아니라 하위 시스템 장애다 — 개별 재처리 전에 원인부터 본다
Kafka에는 DLQ가 내장돼 있지 않다
RabbitMQ는 큐 속성으로 DLX(Dead Letter Exchange)를 지원하지만, Kafka는
애플리케이션이 직접 별도 토픽(orders.DLT)에 넣어야 한다.
Spring Kafka의 DeadLetterPublishingRecoverer가 이 역할을 해 준다.