NACK된 메시지가 큐 앞에 다시 쌓여서 같은 메시지만 무한 반복 처리되는 상황, 기본 큐만으로 어떻게 해결할 수 있을까?

고급 큐 기능 — DLX, Delay Queue, Priority Queue

왜 고급 큐 기능이 필요한가

RabbitMQ의 기본 큐는 메시지의 단순 전달과 소비를 지원합니다. 그러나 실제 서비스 환경에서는 기본 큐만으로 해결하기 어려운 문제들이 있습니다.

문제기본 큐의 한계해결하는 고급 기능
실패 메시지 무한 반복NACK된 메시지가 큐 최상단에 재적재되어 동일 메시지만 계속 재처리DLX (별도 큐로 분리)
** 지연 처리 불가**일정 시간 후 처리해야 하는 메시지를 기본 큐에서 제어 불가Delay Queue (지연 시간 설정)
** 우선순위 제어 불가**FIFO 순서로만 소비되어 긴급 메시지 우선 처리 불가Priority Queue (우선순위 정렬)

1. DLX (Dead Letter Exchange)

DLX란

DLX(Dead Letter Exchange)는 ** 정상적으로 처리되지 않은 메시지를 별도의 큐에서 관리할 수 있게 해주는 기능 **입니다. "Dead Letter"란 더 이상 원래 큐에서 소비될 수 없는 메시지를 의미합니다.

예제: RabbitMQ - Dead Letter Exchange Handbook

DLX로 전달되는 3가지 조건

조건설명실무 예시
** 메시지 TTL 만료**유효 기간이 지난 메시지24시간 내 미처리 주문 자동 취소
** 메시지 거절**basicReject 또는 basicNack에서 requeue=false유효성 검증 실패 메시지
** 큐 최대 길이 초과**x-max-length 설정을 초과하는 메시지 인입큐 오버플로우 방지

** 주의 **: 소비자의 처리 속도가 느린 것 자체는 DLX 전달 조건에 해당하지 않습니다.

DLX 동작 흐름

PLAINTEXT
1. 메시지가 원래 큐에 인입
2. TTL 만료 / Reject / 큐 길이 초과 중 하나 발생
3. RabbitMQ가 해당 메시지를 DLX(Dead Letter Exchange)로 전달
4. DLX가 미리 지정된 DLQ(Dead Letter Queue)로 라우팅
5. DLQ의 전용 소비자가 메시지를 재처리, 로깅, 또는 알림 발송

DLX의 장단점

장점단점
실패 메시지를 일반 큐와 분리하여 별도 관리 가능재처리 메시지 종류가 많아지면 DLQ와 소비자를 각각 만들어야 함
메시지 성격에 따라 맞춤형 재처리 로직 적용 가능시스템 구조가 복잡해질 수 있음
무한 재처리 루프 방지DLX 큐 관리 방안을 사전에 설계해야 함

DLX 정책 설정

rabbitmqctl을 통해 DLX 정책을 추가할 수 있습니다.

SHELL
rabbitmqctl set_policy ${정책이름} ${큐이름정규식} \
  '{"dead-letter-exchange":"${DLX_익스체인지_이름}"}' \
  --apply-to ${queues|exchanges|all}
파라미터설명
정책이름DLX 정책의 식별자
큐이름정규식DLX를 적용할 큐 이름 패턴 (정규식)
DLX_익스체인지_이름Dead Letter를 수신할 Exchange 이름
--apply-to적용 범위 (queues: 큐에만, exchanges: 익스체인지에만, all: 모두)

** 참고 **: DLX 정책은 ** 큐에 적용할 때만 실질적인 효과 **가 있습니다. 익스체인지에 적용해도 DLX 동작이 활성화되지 않습니다.

DLX 실무 활용 사례

  • ** 결제 실패 재처리 **: 결제 처리 실패 메시지를 DLQ에 적재 후 일정 주기로 재시도
  • ** 데이터 유효성 검증 **: 유효하지 않은 데이터를 DLQ로 분리하여 관리자에게 알림
  • ** 오류 분석 **: DLQ에 적재된 메시지를 분석하여 시스템 오류 패턴 파악

2. Delay Queue

Delay Queue란

Delay Queue는 ** 메시지를 즉시 처리하지 않고, 지정한 지연 시간 이후에 소비자에게 전달 **하는 큐입니다.

예제: RabbitMQ - Delay-Queue Handbook

Delay Queue가 필요한 이유

일반 큐에서 지연 처리를 구현하려면, 메시지에 시간 정보를 기록하고 소비자가 해당 시간까지 메시지를 NACK으로 재적재하는 방식을 사용해야 합니다. 이 방법은 비효율적이고 구현이 복잡합니다.

Delay Queue는 메시지에 지연 시간을 설정하면 ** 지정한 시간이 지난 후 자동으로 소비자에게 전달 **되므로 구현이 간결합니다.

플러그인 없이 Delay Queue 구현하기 (TTL + DLX 조합)

플러그인을 설치할 수 없는 환경에서는 ** 메시지 TTL과 DLX를 조합 **하여 지연 처리를 구현할 수 있습니다.

PLAINTEXT
1. 메시지를 "대기 큐"에 발행 (TTL 설정, 소비자 없음)
2. TTL 만료 → DLX로 전달
3. DLX가 "실제 처리 큐"로 라우팅
4. 소비자가 처리 큐에서 메시지를 소비
JAVA
// 대기 큐 선언: TTL 30초 + DLX 연결, 소비자 없음
Map<String, Object> waitArgs = new HashMap<>();
waitArgs.put("x-message-ttl", 30000);           // 30초 후 만료
waitArgs.put("x-dead-letter-exchange", "work.exchange");  // 만료 시 DLX로 전달
waitArgs.put("x-dead-letter-routing-key", "work.key");
channel.queueDeclare("wait.queue", true, false, false, waitArgs);

// 실제 처리 큐 선언: 소비자가 여기서 메시지를 소비
channel.queueDeclare("work.queue", true, false, false, null);
channel.queueBind("work.queue", "work.exchange", "work.key");

이 방식의 한계는 ** 대기 큐의 TTL이 큐 단위로 고정 **된다는 점입니다. 메시지별로 다른 지연 시간이 필요하면 메시지 TTL을 사용할 수 있지만, RabbitMQ는 큐 헤드에서만 만료를 확인하므로 앞 메시지의 TTL이 길면 뒤 메시지가 먼저 만료되어도 전달되지 않습니다. 메시지별 지연이 필요하면 플러그인 사용을 권장합니다.

실무 활용 사례

시나리오지연 시간 예시
회원가입 후 환영 메일 발송10분 후
주문 미결제 자동 취소30분 후
예약 리마인더 알림1시간 전
실패한 작업 재시도5분 후 (점진적 백오프)

플러그인 설치

RabbitMQ에서 Delay Queue를 사용하려면 rabbitmq_delayed_message_exchange 플러그인 을 설치해야 합니다.

SHELL
# 플러그인 디렉토리 확인
rabbitmq-plugins directories -s

# 플러그인 다운로드
wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/v4.1.0/rabbitmq_delayed_message_exchange-4.1.0.ez

# 플러그인 활성화
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

코드 예시 (Spring AMQP)

JAVA
public class DelayQueueMain {

    private static final String DELAYED_EXCHANGE = "delay.exchange";
    private static final String DELAYED_QUEUE = "delay.queue";
    private static final String ROUTING_KEY = "delay.routingkey";

    @Bean
    public CustomExchange delayExchange() {
        Map<String, Object> args = Map.of("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE, "x-delayed-message", true, false, args);
    }

    @Bean
    public Queue delayQueue() {
        return new Queue(DELAYED_QUEUE, true);
    }

    @Bean
    public Binding binding(Queue delayQueue, CustomExchange delayExchange) {
        return BindingBuilder.bind(delayQueue).to(delayExchange).with(ROUTING_KEY).noargs();
    }

    @Bean
    public CommandLineRunner testSend(RabbitTemplate rabbitTemplate) {
        return args -> {
            String message = "테스트 메시지";
            MessageProperties props = new MessageProperties();
            props.setHeader("x-delay", 3000); // 3초 지연 (밀리초 단위)
            Message msg = new Message(message.getBytes(), props);
            rabbitTemplate.send(DELAYED_EXCHANGE, ROUTING_KEY, msg);
            System.out.println("지연 메시지 전송 완료");
        };
    }
}

Delay Queue 설정 요약

항목설정값/설명
Exchange 타입x-delayed-message
Exchange argsx-delayed-type = direct, fanout, topic, headers
바인딩원하는 라우팅 키로 바인딩
메시지 헤더x-delay (밀리초 단위, 예: 3000 = 3초 지연)

3. Priority Queue

Priority Queue란

Priority Queue는 메시지에 우선순위(priority) 속성을 부여하여 높은 우선순위 메시지를 먼저 처리하는 큐 입니다. RabbitMQ 3.5.0 이상에서 기본 제공됩니다.

예제: RabbitMQ - Priority-Queue Handbook

Priority Queue가 없다면

우선순위 큐를 사용하지 않고 우선순위 처리를 구현하려면 다음과 같은 방법을 사용해야 합니다.

대안 방법설명문제점
우선순위별 큐 분리high.queue, mid.queue, low.queue로 분리큐 관리 복잡, 소비자 분배 로직 필요
프로세스 내부 정렬SortedSet, Comparator 등으로 정렬 후 처리메모리 부담, 정렬 성능 이슈

Priority Queue의 장점

장점설명
자동 정렬RabbitMQ가 우선순위에 따라 자동으로 메시지 정렬
** 성능 최적화**내부적으로 효율적인 정렬 알고리즘 사용
** 구현 단순**복잡한 분배 로직이나 정렬 로직 불필요

실무 활용 사례

  • **VIP 고객 주문 우선 처리 **: VIP 고객의 주문 메시지에 높은 우선순위 부여
  • ** 시스템 장애 알림 우선 전송 **: 장애 알림 메시지를 일반 알림보다 먼저 처리
  • ** 긴급 배치 작업 **: 긴급 배치 작업을 일반 배치보다 우선 실행

고급 큐 기능 비교 요약

기능해결하는 문제핵심 동작추가 설정 필요
DLX실패 메시지 관리처리 불가 메시지를 별도 큐로 분리정책 설정
Delay Queue지연 처리지정 시간 후 메시지 전달플러그인 설치
Priority Queue우선순위 처리높은 우선순위 메시지 먼저 소비큐 선언 시 설정

이 세 가지 기능을 조합하면 다양한 실무 요구사항에 대응할 수 있습니다. 예를 들어, DLX와 Delay Queue를 조합하면 "실패한 메시지를 5분 후에 자동 재시도"하는 패턴을 구현할 수 있습니다.

** 공식 문서 참고 **: Dead Lettering | Community Plugins

댓글 로딩 중...