고급 큐 기능(DLX, 딜레이 큐, 우선 순위 큐)
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"란 더 이상 원래 큐에서 소비될 수 없는 메시지를 의미합니다.
DLX로 전달되는 3가지 조건
| 조건 | 설명 | 실무 예시 |
|---|---|---|
| ** 메시지 TTL 만료** | 유효 기간이 지난 메시지 | 24시간 내 미처리 주문 자동 취소 |
| ** 메시지 거절** | basicReject 또는 basicNack에서 requeue=false | 유효성 검증 실패 메시지 |
| ** 큐 최대 길이 초과** | x-max-length 설정을 초과하는 메시지 인입 | 큐 오버플로우 방지 |
** 주의 **: 소비자의 처리 속도가 느린 것 자체는 DLX 전달 조건에 해당하지 않습니다.
DLX 동작 흐름
1. 메시지가 원래 큐에 인입
2. TTL 만료 / Reject / 큐 길이 초과 중 하나 발생
3. RabbitMQ가 해당 메시지를 DLX(Dead Letter Exchange)로 전달
4. DLX가 미리 지정된 DLQ(Dead Letter Queue)로 라우팅
5. DLQ의 전용 소비자가 메시지를 재처리, 로깅, 또는 알림 발송
DLX의 장단점
| 장점 | 단점 |
|---|---|
| 실패 메시지를 일반 큐와 분리하여 별도 관리 가능 | 재처리 메시지 종류가 많아지면 DLQ와 소비자를 각각 만들어야 함 |
| 메시지 성격에 따라 맞춤형 재처리 로직 적용 가능 | 시스템 구조가 복잡해질 수 있음 |
| 무한 재처리 루프 방지 | DLX 큐 관리 방안을 사전에 설계해야 함 |
DLX 정책 설정
rabbitmqctl을 통해 DLX 정책을 추가할 수 있습니다.
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는 ** 메시지를 즉시 처리하지 않고, 지정한 지연 시간 이후에 소비자에게 전달 **하는 큐입니다.
Delay Queue가 필요한 이유
일반 큐에서 지연 처리를 구현하려면, 메시지에 시간 정보를 기록하고 소비자가 해당 시간까지 메시지를 NACK으로 재적재하는 방식을 사용해야 합니다. 이 방법은 비효율적이고 구현이 복잡합니다.
Delay Queue는 메시지에 지연 시간을 설정하면 ** 지정한 시간이 지난 후 자동으로 소비자에게 전달 **되므로 구현이 간결합니다.
플러그인 없이 Delay Queue 구현하기 (TTL + DLX 조합)
플러그인을 설치할 수 없는 환경에서는 ** 메시지 TTL과 DLX를 조합 **하여 지연 처리를 구현할 수 있습니다.
1. 메시지를 "대기 큐"에 발행 (TTL 설정, 소비자 없음)
2. TTL 만료 → DLX로 전달
3. DLX가 "실제 처리 큐"로 라우팅
4. 소비자가 처리 큐에서 메시지를 소비
// 대기 큐 선언: 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 플러그인 을 설치해야 합니다.
# 플러그인 디렉토리 확인
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)
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 args | x-delayed-type = direct, fanout, topic, headers |
| 바인딩 | 원하는 라우팅 키로 바인딩 |
| 메시지 헤더 | x-delay (밀리초 단위, 예: 3000 = 3초 지연) |
3. Priority Queue
Priority Queue란
Priority Queue는 메시지에 우선순위(priority) 속성을 부여하여 높은 우선순위 메시지를 먼저 처리하는 큐 입니다. RabbitMQ 3.5.0 이상에서 기본 제공됩니다.
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