RabbitMQ Dead Letter Exchange: route보다 trigger부터 확인하자
Dev
마지막 업데이트

RabbitMQ Dead Letter Exchange: route보다 trigger부터 확인하자


팀에서는 dead letter exchange를 “실패 메시지가 무조건 모이는 안전한 통”처럼 말하는 경우가 많지만, 실제 DLX 장애는 한 단계 더 앞에서 시작됩니다. 첫 질문은 “메시지가 어디로 갔지?”가 아니라 “왜 원래 queue를 떠났지?”여야 합니다. 실제로 운영 중이던 알림 시스템에서 “메시지 유실” 장애가 터졌는데, 팀 전원이 DLX 바인딩만 들여다보고 있었습니다. 알고 보니 원인은 consumer가 파싱 실패 시 requeue=false로 nack하고 있었고, DLX target exchange는 이름만 같고 바인딩이 없는 빈 exchange였습니다. x-death 헤더를 보니 reason이 rejected로 찍혀 있어서 5분 만에 원인을 좁힐 수 있었습니다. 이 분기를 건너뛰면 TTL 문제를 nack 문제처럼 보고, policy 문제를 binding 문제처럼 디버깅하게 됩니다.

짧게 말하면 먼저 dead-letter trigger를 확인하고, 그다음 어떤 config source가 queue를 지배하는지 확인한 뒤, 마지막으로 DLX 라우팅 경로를 봐야 합니다.


이 글이 맞는 상황

아래 중 하나면 여기서 시작하면 됩니다.

  • reject되거나 expire된 메시지가 기대한 queue로 안 간다
  • source queue에서는 메시지가 빠지는데 dead-letter queue는 비어 있다
  • 환경마다 DLX 동작이 다르다
  • policy가 적용된 건지 x-arguments가 적용된 건지 헷갈린다
  • dead-letter 이후 메시지가 사라진 것처럼 보인다

처음 10분에 볼 것

1차 확인은 이 정도면 충분합니다.

rabbitmqctl list_queues name arguments policy messages_ready messages_unacknowledged
rabbitmqctl list_bindings source_name destination_name routing_key
rabbitmqctl list_exchanges name type
rabbitmqctl list_policies

이 단계에서 답해야 할 질문은 네 개입니다.

  • 정확히 어떤 이벤트 때문에 dead-letter가 발생했는가
  • queue를 policy가 제어하는가, x-arguments가 제어하는가, 둘 다인가
  • republish될 때 어떤 routing key가 쓰이는가
  • DLX target exchange와 binding이 실제로 존재하는가

dead lettering은 하나의 이벤트가 아니라 네 가지 trigger입니다

RabbitMQ dead-lettering 문서는 queue 수준에서 네 가지 trigger를 설명합니다.

  • consumer가 requeue=false로 reject 또는 nack했다
  • per-message TTL이 만료됐다
  • queue length limit 때문에 메시지가 밀려났다
  • quorum queue에서 delivery limit를 초과했다

즉 “DLX로 갔다”만으로는 정보가 부족합니다. trigger를 알아야 다음에 볼 층위가 결정됩니다.

한 가지 edge case도 중요합니다. queue 전체가 expire되는 경우 RabbitMQ는 그 안의 메시지를 dead-letter하지 않습니다.

trigger와 route를 먼저 분리해야 합니다

이 분리만 해도 시간 낭비가 많이 줄어듭니다.

먼저 일어난 일보통 의미다음 확인 포인트
basic.reject 또는 basic.nack with requeue=falseconsumer 쪽 판단handler logic과 ack path 확인
message TTL 만료timing과 retention 문제per-message TTL과 queue flow 확인
queue length limit 초과backlog 또는 limit 정책 문제limits, overflow, drain rate 확인
quorum delivery limit 초과반복 requeue 또는 poison message 패턴x-delivery-count, delivery-limit 확인

어느 갈래인지 모르면 DLX binding을 만지는 건 대개 너무 이릅니다.

policy를 우선해야 하지만 x-arguments가 override합니다

RabbitMQ 문서는 hardcoded queue x-arguments보다 policy를 권장합니다. policy는 애플리케이션 재배포 없이 바꿀 수 있기 때문입니다. 하지만 운영상 중요한 함정이 하나 있습니다. 둘 다 있으면 queue arguments가 policy를 override합니다.

그래서 DLX 장애는 이런 식으로 헷갈립니다.

  • 운영자는 policy를 바꿨다고 생각한다
  • 애플리케이션은 x-dead-letter-exchange를 직접 선언했다
  • queue는 계속 hardcoded argument를 따른다

팀이 policy를 보고 있는데 실제 queue는 arguments에 묶여 있다면, 조사 방향이 시작부터 틀어집니다.

routing key 가정이 틀려서 route가 어긋나는 경우가 많습니다

RabbitMQ는 dead-lettered message를 두 방식 중 하나로 다시 publish합니다.

  • queue에 dead-letter routing key가 설정되어 있으면 그 key 사용
  • 없으면 원래 publish 경로의 routing key 사용

즉 DLX 자체는 살아 있어도 팀이 상상한 routing key와 실제 key가 달라서 원하는 dead-letter queue로 못 가는 일이 충분히 생깁니다.

target exchange가 없으면 특히 위험합니다

RabbitMQ 문서는 DLX target exchange가 queue declaration 시점에 존재할 필요는 없지만, 실제 dead-letter 시점에는 반드시 있어야 한다고 설명합니다. 그 순간 exchange가 없으면 메시지는 조용히 drop됩니다.

실제 장애에서는 이걸 제일 먼저 확인하는 편이 좋습니다.

권한도 DLX 설정의 일부입니다

queue를 dead-letter exchange와 함께 선언할 때 RabbitMQ는 그 시점에 권한도 확인합니다.

  • queue에 대한 configure 권한
  • source queue에 대한 read 권한
  • target DLX에 대한 write 권한

즉 topology는 맞아 보여도, 선언한 사용자의 권한이 부족해서 훨씬 앞단에서 문제가 생길 수 있습니다.

dead-letter cycle은 실제로 생기고, RabbitMQ는 메시지를 drop합니다

RabbitMQ 문서는 dead-letter된 메시지가 결국 같은 queue로 다시 돌아오는 cycle을 설명합니다. 무한 루프를 막기 위해 RabbitMQ는 cycle을 감지하고, 그 cycle 안에 rejection이 한 번도 없었다면 메시지를 drop합니다.

보통 이런 조건에서 잘 보입니다.

  • default exchange를 가볍게 쓴다
  • explicit dead-letter routing key가 없다
  • 경로가 실수로 원래 queue로 되돌아간다

기본 DLX republishing은 강한 안전 보장이 아닙니다

RabbitMQ에서 dead-lettering도 결국 publish의 한 형태이고, publish는 실패할 수 있습니다. 공식 문서는 기본 dead-letter republishing이 내부적으로 publisher confirms 없이 이뤄진다고 설명합니다. 그래서 clustered DLX 경로가 항상 안전하다고 보면 안 됩니다.

즉 메시지는 source queue를 떠났지만 target 쪽이 못 받으면 그대로 잃을 수도 있습니다.

quorum queue에는 at-least-once dead-lettering이라는 더 안전한 선택지가 있지만, 그건 quorum 전용 기능이고 별도의 tradeoff가 있습니다.

x-death는 추측을 멈추게 해 주는 가장 빠른 단서입니다

RabbitMQ는 dead-letter history를 header에 남깁니다.

  • AMQP 0-9-1에서는 x-death
  • RabbitMQ는 x-first-death-*, x-last-death-*도 추가

dead-letter reason 값에는 아래가 포함됩니다.

  • rejected
  • expired
  • maxlen
  • delivery_limit

즉 메시지가 왜 이동했는지 굳이 추측할 필요가 없을 때가 많습니다. broker가 이미 기록해 둡니다.

dead-lettering은 message metadata도 바꿉니다

RabbitMQ 문서는 dead-lettering 과정에서 일부 metadata가 바뀐다고 설명합니다.

  • exchange 이름은 최신 dead-letter exchange로 바뀐다
  • routing key는 configured dead-letter routing key로 교체될 수 있다
  • original TTL은 다음 queue에서 즉시 다시 만료되지 않도록 제거된다

그래서 dead-lettered message만 보고 원래 publish path를 복원할 때는, broker가 이미 일부 값을 다시 썼다는 점을 감안해야 합니다.

흔한 원인

1. trigger를 잘못 가정함

실제 원인은 TTL, queue length, quorum delivery limit인데 팀은 routing만 보고 있습니다.

2. policy 대 x-arguments drift

운영자는 policy를 바꿨지만 queue는 여전히 hardcoded argument를 따릅니다.

3. routing key 가정이 틀림

DLX는 있는데 팀이 예상한 key와 다르게 republish됩니다.

4. DLX target exchange 또는 binding 부재

메시지는 source queue를 떠나지만 그다음에 사라집니다.

5. dead-letter cycle

메시지가 같은 queue 경로로 되돌아오고 결국 drop됩니다.

흔한 잘못된 시작

  • trigger를 확인하기 전에 DLX binding부터 보기
  • queue arguments를 안 보고 policy가 먹고 있다고 가정하기
  • DLX를 모든 cluster 상황에서 안전한 sink로 생각하기
  • x-death를 안 보고 기억에 의존해 원인을 복원하기
  • target exchange 존재 여부를 안 본 채 consumer부터 바꾸기

실전 점검 순서

1. dead-letter trigger를 먼저 식별합니다

메시지가 왜 source queue를 떠났는지 모르면 나머지는 거의 추측입니다.

2. policy가 지배하는지 x-arguments가 지배하는지 확인합니다

진짜 source of truth가 어디 있는지 먼저 알아야 합니다.

3. DLX exchange, binding, routing-key 경로를 확인합니다

trigger와 control plane이 명확해진 다음에야 route를 봐야 합니다.

4. x-death와 first/last death header를 확인합니다

RabbitMQ가 이미 reason과 경로 히스토리를 남겨 두었습니다.

5. cycle, missing target, cluster safety 가정을 확인합니다

메시지가 사라지는 사건은 여기서 설명되는 경우가 많습니다.

체크리스트

  • 정확한 dead-letter trigger를 확인했다
  • policy와 x-arguments 중 누가 queue를 지배하는지 확인했다
  • dead-letter routing key 가정을 검증했다
  • DLX target exchange와 binding 존재를 확인했다
  • topology를 바꾸기 전에 x-death를 확인했다

FAQ

Q. DLX를 붙이면 실패 메시지는 항상 보존되나요?

아니요. 기본 dead-letter republishing은 end-to-end 안전 보장과 다릅니다.

Q. policy를 바꿨는데 왜 queue 동작이 안 바뀌죠?

queue x-arguments가 DLX policy를 override하고 있을 수 있습니다.

Q. source queue에서는 메시지가 빠지는데 dead-letter queue는 왜 비어 있나요?

target exchange가 없거나, binding이 틀렸거나, routing key 가정이 다를 수 있습니다.

Q. 메시지가 왜 dead-letter됐는지 가장 빨리 확인하는 방법은 뭔가요?

x-death reason과 first/last death header를 보는 겁니다.

Sources:

먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.

광고