본문 바로가기
Backend2026년 8월 2일5분 읽기

데드레터 큐(DLQ) — 독약 메시지가 큐를 막지 않게

YS
김영삼
조회 8
데드레터 큐(DLQ) — 독약 메시지가 큐를 막지 않게

데드레터 큐(Dead Letter Queue, DLQ)는 정상적으로 처리하지 못한 메시지를 버리지 않고 따로 모아두는 별도의 큐입니다. 큐 시스템에서 어떤 메시지가 몇 번을 재시도해도 계속 실패하면, 그 메시지를 원래 큐에서 빼내 DLQ로 옮겨 격리합니다. 실패한 메시지의 "임시 보관소"이자 "격리 병동"인 셈이죠.

DLQ가 없는 큐를 운영해 본 사람은 그 고통을 압니다. 처리 안 되는 메시지 하나가 큐 맨 앞에 박혀 무한 재시도를 돌면, 뒤의 정상 메시지들이 전부 막힙니다. 이걸 독약 메시지(poison message)라고 부릅니다. 한 알이 큐 전체를 마비시키죠.

왜 필요한가 — 독약 메시지 격리

메시지가 실패하는 이유는 다양합니다. 잘못된 형식, 참조하는 데이터가 삭제됨, 처리 코드의 버그, 다운스트림 서비스 장애 등. 이 중 상당수는 몇 번을 다시 시도해도 결과가 같은 영구적 실패입니다.

이런 메시지를 계속 재시도하면 두 가지 문제가 생깁니다. 첫째, 그 메시지가 처리될 때까지 뒤가 막히거나(순서 보장 큐), 워커 자원이 헛되이 소모됩니다. 둘째, 재시도 로그가 폭발해 진짜 문제를 가립니다. DLQ는 "N번 시도했는데 안 되면 옆으로 빼자"는 규칙으로 이 흐름을 끊습니다.

동작 흐름

대부분의 브로커는 이 로직을 내장하고 있습니다. 핵심 설정은 최대 수신 횟수(max receive count)입니다.

단계일어나는 일
1워커가 메시지를 받아 처리 시도
2실패 → ack 안 함 → 메시지가 큐로 되돌아옴(재시도)
3수신 횟수가 임계값(예: 5) 도달
4브로커가 메시지를 DLQ로 이동
5원래 큐는 다음 메시지로 계속 진행

SQS는 리드라이브 정책(redrive policy)으로 maxReceiveCount와 DLQ를 지정하고, RabbitMQ는 큐에 x-dead-letter-exchange를 걸며, Kafka는 처리 실패 시 애플리케이션이 직접 별도 토픽(보통 <topic>.DLT)으로 보냅니다. 이름과 방식은 달라도 개념은 같습니다.

DLQ를 그냥 두면 안 된다

여기서 제일 흔한 실수를 말하고 싶습니다. DLQ를 만들어놓고 아무도 안 본다는 것. DLQ는 쓰레기통이 아니라 미결 트레이입니다. 메시지가 여기 쌓인다는 건 처리 못 한 일이 그만큼 있다는 뜻이에요. 나는 항상 DLQ의 깊이(depth)에 알람을 겁니다. "DLQ에 메시지가 1개라도 들어오면 알림"이 기본이고, 트래픽이 큰 곳은 임계값을 조금 둡니다.

솔직히 처음 맡은 시스템에서 DLQ에 3만 건이 쌓여 있는 걸 몇 달 뒤에 발견한 적이 있습니다. 전부 결제 후처리 메시지였어요. 그날 이후로 "DLQ 깊이 = 0"을 대시보드 맨 위에 올려둡니다.

재처리(redrive) 전략

원인을 고쳤다면 DLQ의 메시지를 원래 큐로 돌려보내 다시 처리합니다. 이걸 재구동(redrive)이라고 합니다. 다만 무작정 되돌리기 전에 확인할 게 있습니다.

  • 실패 원인을 먼저 고쳤는가 — 안 고치고 되돌리면 다시 DLQ로 돌아옵니다. 무한 왕복이죠.
  • 소비자가 멱등한가 — 재처리는 곧 중복 처리 가능성입니다. 같은 메시지를 두 번 처리해도 안전해야 합니다.
  • 메시지가 아직 유효한가 — 며칠 묵은 "재고 알림" 메시지를 지금 재처리하면 오히려 이상할 수 있습니다. 시효를 판단하세요.
// DLQ 메시지에 실패 맥락을 남겨두면 디버깅이 쉬워진다
{
  "originalMessage": { "orderId": "A-1029", ... },
  "failureReason": "PaymentService timeout",
  "receiveCount": 5,
  "firstFailedAt": "2026-07-30T11:20:03Z",
  "lastError": "ETIMEDOUT"
}

자주 묻는 질문

재시도와 DLQ는 어떻게 나눠 쓰나요?

둘은 단계입니다. 일시적 오류(네트워크 순단, 다운스트림 순간 과부하)는 백오프 재시도로 대개 해결됩니다. 재시도를 다 소진하고도 실패하면 영구적 문제일 확률이 높으니 그때 DLQ로 보냅니다. 재시도는 "곧 나아질 것"에, DLQ는 "사람이 봐야 할 것"에 씁니다.

DLQ 메시지는 언제까지 보관하나요?

브로커 설정에 따라 다르지만, 조사할 시간을 벌 만큼 넉넉히 잡는 게 좋습니다. SQS는 최대 14일까지 보관되죠. 너무 짧으면 주말 사이 쌓인 메시지가 조사도 못 하고 사라집니다. 다만 무한 보관은 비용이니 정책과 알람을 함께 두세요.

DLQ에도 계속 실패하는 메시지는요?

DLQ 자체에 또 DLQ를 두는 경우는 드물고, 보통은 그 지점부터 사람이 개입합니다. 페이로드를 뜯어보고, 데이터를 수정하거나, 처리 코드를 고친 뒤 재구동합니다. DLQ는 "자동화의 끝, 사람 판단의 시작"이라고 보면 됩니다.

순서가 중요한 큐에서 DLQ를 쓰면 순서가 깨지지 않나요?

깨질 수 있습니다. 실패한 메시지만 빼서 DLQ로 보내면 그 뒤 메시지가 먼저 처리되니까요. 엄격한 순서가 필요하면 파티션/그룹 단위로 처리를 멈추는 전략과 DLQ 사이에서 트레이드오프를 따져야 합니다. 대개는 "막힘 방지"가 "완벽한 순서"보다 우선입니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록