Post

RabbitMQ DLQ 톺아 보기

RabbitMQ DLQ가 무엇이고 어떻게 사용하는지에 대해 설명합니다.

들어가며

RabbitMQ 서버와 컨슈머서버는 메시지를 처리에 대한 확인(Ack)을 메소드를 통해 통신하고 있습니다. 컨슈머가 메시지 처리에 대한 확인 메서드를 보내기전에 에러가 나면 어떻게 될까요? RabbitMQ에서는 메시지를 보냈지만 소비되지 않았으므로 다시 보내는 작업을 수행합니다. 이렇게 되면 실패된 메시지에 대해 반복 처리 되면서 장애 발생 원인이 될 수 있습니다.

DLQ란?

RabbitMQ에서는 DLQ(Dead Letter Qeque)를 사용하여 실패처리된 메시지를 따로 큐에 저장하는것을 말하며 저장된 실패된 메시지에 대해 재처리하거나 원인을 분석할 수 있습니다. DLQ을 선언할때는 정책 또는 x-arguments 사용하고 있습니다.

정책

RabbitMQ에서는 정책(Policy)으로 사용을 권장하고 있습니다. 정책은 큐, 익스체인지 선택적 속성들을 동적으로 설정하는 선언적 메커니즘입니다.

  1. 핵심 구성요소 Policy = 이름 + 패턴 + 정의 + 우선순위 + 적용 대상
    • 이름: 정책 고유 이름
    • 패턴: 정규식표현으로 큐/익스체인지 이름 매칭
    • 정의: 설정할 key-value 쌍
    • 우선순위: 여러 정책이 매칭될 때 우선순위 결정
    • 적용 대상: 큐, 익스체인지, 스트림 등
    1
    2
    3
    4
    5
    
     rabbitmqctl set_policy my-policy \
         "^orders\." \
         '{"message-ttl":60000, "max-length":10000}' \
         --priority 10 \
         --apply-to queues
    
  2. 주요 정책 종류

    keyvalue설명
    message-ttlint메시지 만료 시간
    max-lengthint메시지 최대 개수
    dead-letter-exchange
    dead-letter-routing-key
    stringDead Letter Exchange
    delivery-limitint최대 재전달 횟수(쿼럼큐 전용)

x-arguments 를 사용을 권장하지 않는 이유

x-arguments는 익스체인지/큐를 생성할 때 속성을 추가로 설정할 수 있는 값입니다. 만약 orders.queue를 생성할 때 큐의 ttl를 60초, 최대 개수를 10000설정한다고 가정해봅시다.

1
2
3
rabbitmqadmin declare queue name=orders.queue \
    durable=true \
    arguments='{"x-message-ttl": 60000, "x-max-length": 10000}'

시스템에서 잘사용하고 있다가 필요로 인해 ttl을 60초에서 120초 변경해야 한다거나 속성을 추가로 해야한다면 기존 큐를 삭제했다가 다시 생성해야지만 설정이 가능합니다. 이러한 문제는 메시지 손실이 발생과 애플리케이션 장애로 이어질 수 있기 때문에 RabbitMQ에서는 x-arguments 사용을 권장하지 않고 있습니다.

프로젝트에 DLQ 적용

현재 담당하고있는 알림시스템에 DLQ를 적용과정을 상세하게 알아보겠습니다.

적용 이유

애플리케이션에서 메시지를 받아 처리할 때 에러가 발생 했을 때 로그를 남기고 있지만 서버를 들어가 로그난 기록을 찾아야하는 시간적 비용이 많이 든다는걸 알았습니다. 이러한 문제점을 해결하기 위해 DLQ를 적용해서 에러난 메시지를 따로 격리하여 모니터링하도록 적용하였습니다.

적용 플로우

  1. DLQ, DLX 생성

    사용하고있는 큐에 DLQ를 적용하기 위해, DLQ와 DLX를 먼저 생성해야 합니다. 큐, 익스체인지 생성과 동일합니다.

    Queue Queue

    스크린샷 2025-12-22 오후 3.43.58.png

    Exchange Exchange

  2. 정책 설정

    daemonNotiQ에서 실패 메시지를 DLQ로 보내기 위해 아래와 같이 설정했습니다. 애플리케이션에서 에러가 나면 재시도해도 에러가 나기 때문에 재시도 횟수(delivery-limit: 1)는 없이 설정했습니다. 다른 추가 속성이 없기 때문에 컨슈머가 requeue=false 옵션으로 메시지를 거부할 때 DLQ로 메시지가 전송됩니다.

    1
    2
    3
    
     rabbitmqctl set_policy notification-quorum-dlx-policy "^daemonNotiQ$" \  
     '{ "dead-letter-exchange": "notification.quorum.dlx", "dead-letter-routing-key": "failed", "delivery-limit": 1 }' \
      --apply-to quorum_queues --priority 10
    
  3. Spring 설정

    에러 발생시 흐름은 아래와 같습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
       1. Listener에서 예외 발생
          ↓
       2. ConditionalRejectingErrorHandler.handleError() 호출
          ↓
       3. ExceptionStrategy.isFatal(exception) 확인
          ↓
          YES (Fatal) → AmqpRejectAndDontRequeueException 던짐
          NO (Retryable) → 일반 Exception 던짐
          ↓
       4. Spring AMQP가 예외 타입에 따라 처리
          - AmqpRejectAndDontRequeueException → reject (requeue=false)
          - 일반 Exception → factory.setDefaultRequeueRejected() 설정 따름
          ↓
       5. RabbitMQ 브로커가 DLX 설정 확인
          ↓
       6. DLQ로 메시지 이동
    

    이러한 흐름대로 가기 위해서 Spring 기존 로직을 변경해주었습니다.

    • queueListener.process() 예외 처리

      스크린샷 2025-12-22 오후 4.25.42.png

    • 에러 발생 시 isFatal변경 하는 클래스 생성

      스크린샷 2025-12-22 오후 4.31.26.png

    • rabbitMQConfig 파일에 에러 핸들러 추가

      스크린샷 2025-12-22 오후 4.32.10.png

마치며

DLQ 상세 개념과 실무에서 어떻게 적용했는지 알아보았습니다. DLQ을 적용한지 얼마 안되었기에 정제되지 않는 에러 메시지도 들어 올 수 있지만 필터링 할수 있는 방안과 지속적인 데이터를 모니터링을 통해 다른 정책들도 필요하면 추가할 예정입니다.

This post is licensed under CC BY 4.0 by the author.