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

JMS 2.0의 'Delivery Delay'와 Classic의 'Scheduled Message' 차이는?

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

JMS 2.0의 'Delivery Delay'와 Classic의 'Scheduled Message' 차이는?

엔터프라이즈 환경에서는 메시지를 생산(Produce)하는 즉시 소비(Consume)하는 것뿐만 아니라, "특정 시간 이후에 메시지가 처리되도록 지연시키는 기능"이 필수적으로 요구됩니다. 결제 실패 후 5분 뒤 재시도, 혹은 24시간 뒤 발송되는 리마인더 알림 등이 대표적인 사례입니다.

메시지 브로커에서 이를 구현하는 방식은 크게 ActiveMQ 5.x (Classic) 시절의 독자적인 기능인 Scheduled Message와, 최신 표준인 JMS 2.0의 Delivery Delay로 나뉩니다. 두 방식은 겉보기엔 유사하게 동작하지만, 표준화 여부와 브로커 내부 아키텍처 측면에서 완전히 다른 메커니즘을 가집니다.


1. ActiveMQ Classic의 'Scheduled Message' (벤더 종속적 확장)

ActiveMQ Classic은 JMS 2.0 표준이 제정되기 훨씬 이전부터 지연 발송에 대한 실무적인 니즈를 해결하기 위해 Job Scheduler라는 독자적인 내장 기능을 제공했습니다.

  • 동작 방식:
    JMS 표준 API가 아닌, 메시지의 특정 사용자 정의 속성(Property)에 예약 정보를 주입하는 방식입니다. 생산자는 AMQ_SCHEDULED_DELAY (지연 시간), AMQ_SCHEDULED_PERIOD (반복 주기), AMQ_SCHEDULED_CRON (크론 표현식) 등의 특수 헤더를 메시지에 추가하여 전송합니다.
  • 브로커 내부 아키텍처:
    이 기능을 사용하려면 브로커 설정(activemq.xml)에서 반드시 schedulerSupport="true" 플래그를 활성화해야 합니다. 브로커는 일반 메시지를 저장하는 KahaDB 외에, 예약된 메시지만을 별도로 관리하는 Job Scheduler Store (B-Tree 기반)를 추가로 생성하여 운용합니다.
  • 강점과 한계:
  • 강점: 리눅스 Cron 표현식(* * * * *)을 지원하여 "매주 월요일 오전 9시 발송" 같은 복잡한 배치성 스케줄링이 브로커 단에서 가능합니다.
  • 한계: ActiveMQ에만 종속된 하드코딩된 속성명을 사용하므로 브로커 교체 시 클라이언트 코드를 전면 수정해야 합니다. 또한, 수백만 건의 지연 메시지가 쌓일 경우 Job Scheduler DB의 병목 현상으로 인해 브로커 전체 성능이 저하되는 구조적 약점이 있습니다.

2. JMS 2.0 표준의 'Delivery Delay'

JMS 2.0 스펙이 발표되면서, 지연 전송 기능이 드디어 공식 자바 표준 API로 편입되었습니다. ActiveMQ Artemis를 비롯한 최신 모던 브로커들은 이 표준을 네이티브로 지원합니다.

  • 동작 방식:
    메시지 속성을 임의로 조작할 필요 없이, JMS 2.0에서 새로 도입된 JMSProducer 인터페이스의 표준 메서드를 호출합니다.
    producer.setDeliveryDelay(300000); // 5분 지연
    이후 메시지를 전송하면, 브로커는 해당 메시지를 수신하되 지정된 지연 시간이 경과할 때까지 컨슈머에게 전달하지 않고 대기시킵니다.
  • 브로커 내부 아키텍처 (Artemis 기준):
    Artemis는 Classic처럼 별도의 무거운 스케줄러 DB를 띄우지 않습니다. 메시지는 일반적인 큐의 저장소 구조 내에 통합되어 저장되며, 메시지의 메타데이터에 '전달 예정 시간'만 마킹됩니다. 브로커의 내부 페이징(Paging) 및 라우팅 엔진이 이 타임스탬프를 네이티브로 인식하여 시간이 된 메시지만 큐의 활성 뷰(Active View)로 노출시킵니다.
  • 강점과 한계:
  • 강점: 완벽한 벤더 중립성(Vendor Neutrality)을 보장합니다. 코드를 수정하지 않고도 JMS 2.0을 지원하는 어떤 브로커(Artemis, IBM MQ 등)로든 마이그레이션이 가능합니다. 내부 라우팅 엔진과 결합되어 성능 오버헤드도 훨씬 적습니다.
  • 한계: 단순히 특정 밀리초 뒤에 보내라는 상대적 지연(Offset) 기능만 표준화되었으므로, Classic이 제공하던 Cron 표현식과 같은 복잡한 반복 스케줄링 기능은 자체적으로 제공하지 않습니다.

3. 핵심 아키텍처 차이점 비교

비교 항목 ActiveMQ Classic (Scheduled Message) JMS 2.0 표준 (Delivery Delay)
표준 여부 독자적 확장 (Proprietary) JMS 2.0 공식 표준 스펙
설정 방식 특수 문자열 프로퍼티 삽입 (AMQ_...) JMSProducer.setDeliveryDelay() API 호출
브로커 종속성 강함 (코드를 수정하지 않으면 이식 불가) 없음 (표준 호환 브로커 간 자유로운 이식)
크론(Cron) 지원 완벽 지원 (AMQ_SCHEDULED_CRON) 미지원 (단순 지연 시간만 설정 가능)
내장 스토리지 구조 별도의 Job Scheduler Store 구동 필수 메인 메시지 스토어의 메타데이터로 통합 처리

4. 아키텍처 설계 시 주의사항 (Anti-Patterns)

지연 전송 기능은 매우 유용하지만, 메시지 브로커를 '장기 배치 스케줄러(Batch Scheduler)'로 오용하는 순간 시스템의 시한폭탄이 됩니다.

  1. 장기 보관의 위험성: "3개월 뒤에 보낼 이메일"을 브로커의 지연 메시지로 넣는 것은 최악의 안티 패턴입니다. 메시지 브로커는 단기적인 이벤트 스트림 처리에 최적화되어 있습니다. 수개월간 큐에 데이터가 쌓여 있으면 디스크 공간을 영구적으로 점유하고 마이그레이션이나 백업 시 거대한 짐이 됩니다. 며칠 이상 지연되어야 하는 작업은 RDBMS와 Quartz, Spring Batch 같은 전용 스케줄러 시스템에서 관리하고, 실행 시점에만 브로커로 메시지를 쏘는 구조가 맞습니다.
  2. 메모리 및 타이머 오버헤드: 수십만 개의 지연 메시지가 큐에 적재되면, 브로커는 어떤 메시지의 타이머가 만료되었는지 지속적으로 계산하고 폴링(Polling)해야 합니다. 이는 브로커의 CPU 자원을 은밀하게 갉아먹으므로, 재시도(Retry Backoff) 로직이나 수 분 이내의 단기 지연 목적으로만 제한적으로 사용해야 합니다.
반응형