본문 바로가기
1. 개발/1.8. ActiveMQ

'Individual Dead Letter Strategy'를 통한 서비스별 DLQ 분리법은?

by 엉짱 2026. 3. 27.
반응형

'Individual Dead Letter Strategy'를 통한 서비스별 DLQ 분리법은?

메시지 브로커를 엔터프라이즈 마이크로서비스(MSA) 환경에서 운영할 때 가장 골칫거리 중 하나는 바로 데드 레터 큐(DLQ, Dead Letter Queue)의 관리입니다. 수많은 서비스가 각자의 큐를 통해 통신하다 보면, 하루에도 수백 건의 처리 실패 메시지가 발생할 수 있습니다.

ActiveMQ의 기본 설정을 그대로 사용하면, 모든 큐에서 발생한 에러 메시지가 ActiveMQ.DLQ라는 단 하나의 거대한 공용 큐로 쏟아져 들어오게 됩니다. 이는 장애 트러블슈팅과 복구 작업을 마비시키는 최악의 안티 패턴(Anti-Pattern)입니다.

이 가이드에서는 수십, 수백 개의 큐에서 발생하는 데드 레터들을 각 서비스별, 목적지별로 깔끔하게 격리하여 라우팅해 주는 'Individual Dead Letter Strategy (개별 데드 레터 전략)'의 동작 원리와 설정법, 그리고 이를 통해 얻을 수 있는 아키텍처적 이점을 상세히 해부합니다.


1. 기본 설정(Shared DLQ)의 치명적인 한계

ActiveMQ를 처음 설치하고 에러 메시지를 발생시켜 보면, 목적지가 어디였든 상관없이 모든 메시지가 ActiveMQ.DLQ라는 단일 큐에 쌓이는 것을 볼 수 있습니다.

  • 모니터링의 붕괴: 결제 큐(queue.payment)에서 실패한 메시지와 배송 큐(queue.shipping)에서 실패한 메시지가 한 곳에 뒤섞입니다. 결제팀과 배송팀은 자신들의 에러를 찾기 위해 거대한 공용 쓰레기통을 매번 뒤져야 합니다.
  • 재처리(Re-queue)의 위험성: DLQ에 쌓인 메시지를 일괄적으로 원래 큐로 밀어 넣는 스크립트를 잘못 실행하면, 결제 에러 메시지가 배송 큐로 들어가는 등 치명적인 데이터 오염이 발생할 수 있습니다. 이를 막으려면 메시지 헤더의 originalDestination 속성을 하나하나 검사하여 라우팅해야 하므로 복구 파이프라인이 기하급수적으로 복잡해집니다.

2. 'Individual Dead Letter Strategy'의 핵심 메커니즘

이러한 문제를 원천적으로 해결하는 기능이 바로 individualDeadLetterStrategy입니다.

이 정책을 브로커에 적용하면, 메시지가 처리 한계를 초과하여 DLQ로 이동해야 할 때 브로커가 원래 목적지의 이름을 기반으로 새로운 DLQ를 동적으로 자동 생성하고 그곳으로 메시지를 라우팅합니다.

예를 들어, 정책의 접두어(Prefix)를 DLQ.으로 설정해 두었다면:

  • queue.payment에서 실패한 메시지는 DLQ.queue.payment라는 큐로 이동합니다.
  • queue.shipping에서 실패한 메시지는 DLQ.queue.shipping이라는 큐로 이동합니다.

결과적으로 에러 메시지들이 원래의 도메인(비즈니스 경계)을 유지한 채 완벽하게 격리되는 마법이 일어납니다.


3. activemq.xml 핵심 설정 가이드

이 전략은 activemq.xml 파일의 <destinationPolicy> 블록 내에서 큐(Queue)나 토픽(Topic) 패턴별로 적용할 수 있습니다.

<destinationPolicy>
    <policyMap>
        <policyEntries>
            <policyEntry queue=">">
                <deadLetterStrategy>
                    <individualDeadLetterStrategy 
                        queuePrefix="DLQ." 
                        useQueueForQueueMessages="true" />
                </deadLetterStrategy>
            </policyEntry>

            <policyEntry topic=">">
                <deadLetterStrategy>
                    <individualDeadLetterStrategy 
                        queuePrefix="DLQ.TOPIC." 
                        useQueueForTopicMessages="true" />
                </deadLetterStrategy>
            </policyEntry>
        </policyEntries>
    </policyMap>
</destinationPolicy>

핵심 파라미터 분석:

  • queuePrefix: 동적으로 생성될 DLQ의 이름 앞에 붙일 접두어입니다. 기본값은 ActiveMQ.DLQ.Queue. 같이 매우 길기 때문에, 실무에서는 관리의 편의를 위해 DLQ. 또는 ERROR. 처럼 직관적이고 짧은 이름으로 변경하는 것이 좋습니다.
  • useQueueForQueueMessages / useQueueForTopicMessages: 매우 중요한 옵션입니다. 토픽(Pub/Sub) 환경에서 에러가 발생하더라도, 에러 메시지 자체는 휘발되지 않도록 반드시 큐(Queue) 형태로 보관해야 합니다. 이 속성들을 true로 설정하면 원래 목적지가 큐였든 토픽이었든 상관없이 DLQ는 무조건 큐 형태로 생성되어 안전하게 저장됩니다.

4. 아키텍처 및 운영 측면의 3대 이점

Individual Dead Letter Strategy를 도입하면 엔터프라이즈 환경에서 다음과 같은 압도적인 이점을 얻게 됩니다.

A. 완벽한 도메인 격리와 권한 분리
결제팀은 DLQ.queue.payment 큐에 대해서만 모니터링 알림을 설정하고, 해당 큐의 메시지만 읽을 수 있도록 브로커 보안(Security) 권한을 제어할 수 있습니다. 서로 다른 MSA 서비스 팀 간의 간섭이 완전히 사라집니다.

B. 무지성 재처리(Shovel) 파이프라인의 단순화
DLQ가 개별적으로 나뉘어 있으므로, 복구 파이프라인 로직이 극단적으로 단순해집니다. DLQ.queue.payment에 쌓인 메시지 100건을 다시 처리해야 한다면, 메시지 내부 속성을 검사할 필요 없이 그냥 100건을 통째로 꺼내어 queue.payment로 전송(Shovel)하기만 하면 됩니다.

C. 목적지별 차등 정책 적용
주문 큐의 DLQ는 중요도가 높으므로 영구적으로 보관하고, 단순 로그 수집 큐의 DLQ는 3일 뒤에 자동 삭제(TTL)되도록 브로커 정책을 큐 패턴별로 다르게 지정할 수 있습니다. 공용 DLQ 구조에서는 불가능한 유연한 운영이 가능해집니다.


5. 도입 시 주의해야 할 스토리지 관리 포인트

이 전략은 무수히 많은 장점을 제공하지만, 운영 시 반드시 챙겨야 할 트레이드오프가 존재합니다.

서비스 큐가 1,000개라면, DLQ도 1,000개가 동적으로 생성될 수 있습니다. 큐의 개수가 기하급수적으로 늘어나면 KahaDB 인덱스(db.data B-Tree)가 비대해지고, 브로커 JVM의 힙 메모리(Destination 객체 유지 비용)를 더 많이 소모하게 됩니다.

따라서 개별 DLQ 전략을 사용할 때는, DLQ에 메시지가 무한정 쌓이도록 방치해서는 안 됩니다. 브로커의 백그라운드 스레드를 통해 일주일이 지난 DLQ 메시지는 자동으로 삭제되도록 expireMessagesPeriod와 TTL 정책을 강력하게 걸어두거나, 컨슈머에서 처리할 가치가 없는 포이즌 필(Poison Pill) 메시지는 애초에 DLQ로 가지 않도록 버리거나(Discard) 즉시 DB로 빼내는 방어 로직을 애플리케이션 단에 병행 구축해야 합니다.

반응형