DLQ 메시지에 기록되는 에러 사유(Reason) 속성 확인법은?
엔터프라이즈 환경에서 메시지 브로커를 운영할 때, 데드 레터 큐(DLQ, Dead Letter Queue)는 정상적으로 처리되지 못한 메시지들이 모이는 최후의 종착지입니다. 하지만 DLQ에 메시지가 쌓였다는 사실만으로는 장애를 해결할 수 없습니다. 개발자와 인프라 엔지니어에게 가장 필요한 것은 "이 메시지가 왜 원래의 큐에서 처리되지 못하고 여기까지 밀려났는가?"에 대한 정확한 단서입니다.
단순한 네트워크 지연 때문인지, 애플리케이션의 NullPointerException 때문인지, 아니면 TTL(수명)이 만료되었기 때문인지 그 사유를 파악해야만 올바른 재처리(Re-queue)나 폐기 결정을 내릴 수 있습니다. 이 가이드에서는 ActiveMQ(Classic 및 Artemis)가 메시지를 DLQ로 이동시킬 때 내부에 주입하는 특수한 에러 사유 속성들을 확인하고, 이를 트러블슈팅에 활용하는 방법을 상세히 해부합니다.

1. DLQ 메시지에 숨겨진 '블랙박스' 메타데이터
메시지가 최대 재시도 횟수(Max Delivery Attempts)를 초과하거나 기타 치명적인 라우팅 오류를 겪게 되면, 브로커는 해당 메시지를 삭제하는 대신 DLQ로 강제 이동시킵니다.
이때 브로커는 단순히 메시지의 본문(Payload)만 옮기는 것이 아닙니다. 비행기의 블랙박스처럼, 메시지가 추락(처리 실패)한 정확한 원인과 원래 목적지에 대한 메타데이터를 메시지의 헤더(Header) 또는 속성(Property) 영역에 몰래 추가하여 저장합니다. 우리가 이 특수한 속성 키(Key) 값들을 알고 있다면, 장애의 원인을 100% 역추적할 수 있습니다.
2. 반드시 추적해야 할 DLQ 핵심 속성(Property) 키값
사용 중인 브로커의 버전에 따라 주입되는 메타데이터의 키값이 약간 다릅니다. 다음은 DLQ 트러블슈팅을 위해 반드시 확인해야 하는 핵심 속성들입니다.
A. ActiveMQ Classic 환경의 에러 사유 속성
dlqDeliveryFailureCause: 가장 직접적이고 핵심적인 속성입니다. 브로커가 메시지를 DLQ로 보낸 결정적인 자바 예외(Exception) 스택 트레이스나 텍스트 사유가 문자열 형태로 낱낱이 기록됩니다.- (예시 값: "java.lang.Throwable: Exceeded redelivery policy limit: redeliveryCounter=6...")
originalDestination: 메시지가 DLQ로 오기 전, 원래 머물렀던 목적지(큐나 토픽)의 물리적 이름을 담고 있습니다. 단일 DLQ를 여러 큐가 공유하는 아키텍처에서 메시지의 출처를 분류할 때 필수적입니다.
B. ActiveMQ Artemis 환경의 추적 속성
Artemis는 코어 레벨에서 좀 더 세분화된 메타데이터를 주입합니다.
_AMQ_ORIG_ADDRESS: 메시지가 최초로 라우팅되었던 원본 주소(Address)입니다._AMQ_ORIG_QUEUE: 메시지가 최종적으로 소비에 실패했던 원본 큐(Queue)의 이름입니다._AMQ_ORIG_MESSAGE_ID: 원본 메시지가 가지고 있던 브로커 내부의 고유 식별자입니다.
C. 공통 상태 추적 속성
JMSXDeliveryCount: 이 메시지가 컨슈머에게 전달 시도되었던 총 횟수입니다. 만약 브로커에 설정된 최대 재시도 횟수(예: 5회)와 이 값이 동일하다면, 애플리케이션 로직의 반복적인 예외 발생으로 인한 DLQ 인입임을 확신할 수 있습니다.
3. 관리자 웹 콘솔(Web Console)을 통한 직관적인 확인 방법
장애 발생 직후 즉각적인 상황 파악이 필요할 때는 브로커가 제공하는 웹 관리자 콘솔을 활용하는 것이 가장 빠릅니다.
- ActiveMQ Web Console (기본 포트 8161) 또는 Artemis Hawtio 콘솔에 관리자 권한으로 접속합니다.
- 상단 메뉴에서 Queues 탭으로 이동한 뒤, DLQ 목적지(일반적으로
ActiveMQ.DLQ또는DLQ)를 찾습니다. - 해당 큐의 메시지 목록을 조회(Browse)합니다.
- 조회된 메시지 리스트 중 분석할 메시지의 Message ID를 클릭하여 상세 페이지로 진입합니다.
- 'Message Details' 또는 'Properties' 테이블 영역을 스크롤하여 내려보면, 본문 영역(Body) 위에 표기된
dlqDeliveryFailureCause나_AMQ_ORIG_QUEUE키와 그에 매핑된 에러 텍스트를 육안으로 확인할 수 있습니다.
4. Spring Boot 애플리케이션에서 코드로 에러 사유 추출하기
수백 개의 메시지가 DLQ에 쌓이는 대규모 환경에서는 관리자가 일일이 콘솔을 클릭하여 확인할 수 없습니다. DLQ 전용 컨슈머 애플리케이션을 구축하여 에러 사유를 자동으로 파싱하고 분류하는 코드를 작성해야 합니다.
Spring Boot의 JmsListener를 활용하면 Message 인터페이스의 getStringProperty 메서드를 통해 브로커가 주입한 이 숨겨진 속성들을 쉽게 꺼낼 수 있습니다.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.TextMessage;
@Component
public class DlqMonitoringListener {
private static final Logger log = LoggerFactory.getLogger(DlqMonitoringListener.class);
// DLQ를 목적지로 하는 전용 리스너 구성
@JmsListener(destination = "ActiveMQ.DLQ")
public void processDlqMessage(Message message) {
try {
// 1. 에러 사유 추출 (ActiveMQ Classic 기준)
String failureCause = message.getStringProperty("dlqDeliveryFailureCause");
// 2. 원본 큐 이름 추출 (Artemis의 경우 _AMQ_ORIG_QUEUE 사용)
String origQueue = message.getStringProperty("originalDestination");
// 3. 재시도 횟수 추출
int deliveryCount = message.getIntProperty("JMSXDeliveryCount");
// 메시지 본문 추출
String payload = "";
if (message instanceof TextMessage) {
payload = ((TextMessage) message).getText();
}
// 로그 기록 및 슬랙(Slack) 등의 알림 파이프라인으로 전송
log.error("🚨 [DLQ 경보] 원본 큐: {}, 재시도 횟수: {}", origQueue, deliveryCount);
log.error("📝 실패 사유: {}", failureCause);
log.error("📦 메시지 본문: {}", payload);
// TODO: failureCause 텍스트를 분석하여 단순 DB 락(Lock) 타임아웃이면
// 원본 큐로 다시 재전송(Re-queue)하는 자동화 로직 구현 가능
} catch (JMSException e) {
log.error("DLQ 메시지 속성 파싱 중 치명적 오류 발생", e);
}
}
}
5. 에러 사유 기반의 DLQ 처리 모범 사례 (Best Practice)
추출한 DLQ 에러 사유를 바탕으로 다음과 같은 시스템 자동화 파이프라인을 구축할 수 있습니다.
- 포이즌 필(Poison Pill) 영구 격리: 에러 사유에
NullPointerException이나JsonParseException과 같은 치명적인 애플리케이션 코드 버그나 데이터 포맷 오류가 기록되어 있다면, 이 메시지는 천 번을 다시 큐에 넣어도 실패합니다. 즉시 격리 보관소(DB 등)로 옮기고 개발팀에 슬랙 알림을 발송하여 수동 처리를 유도합니다. - 일시적 장애(Transient Failure) 자동 재처리: 에러 사유가
java.sql.SQLTimeoutException이나 외부 API 연동 시 발생한HTTP 503 Service Unavailable이라면, 이는 네트워크나 인프라의 일시적 장애일 확률이 높습니다. DLQ 컨슈머가 이 사유를 정규식으로 판별해 내면, 메시지를 즉시 폐기하지 않고 일정 시간(예: 30분) 대기 후 원본 큐로 다시 밀어 넣는(Re-queue) 자동 복구 파이프라인을 태울 수 있습니다.
요약하자면 DLQ는 실패한 메시지를 버리는 쓰레기통이 아니라, 시스템의 결함을 진단할 수 있는 가장 풍부한 단서가 담긴 블랙박스입니다. 브로커가 제공하는 에러 사유 속성(dlqDeliveryFailureCause 등)을 철저히 모니터링하고 분석하여, 장애 복구 시간을 획기적으로 단축하는 견고한 아키텍처를 완성하시기 바랍니다.
'1. 개발 > 1.8. ActiveMQ' 카테고리의 다른 글
| DLQ로 가기 전 지수 백오프(Exponential Backoff)의 최대 지연 시간 설정? (0) | 2026.03.27 |
|---|---|
| 'Individual Dead Letter Strategy'를 통한 서비스별 DLQ 분리법은? (0) | 2026.03.27 |
| Paging 모드에서 메시지를 다시 메모리로 읽어오는 'Page Size' 최적화는? (0) | 2026.03.25 |
| 'Message Paging'이 시작되는 메모리 계산 공식은? (0) | 2026.03.25 |
| Large Message 전송 중 네트워크 단절 시 부분 데이터 처리 방식은? (0) | 2026.03.25 |