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

독점 컨슈머(Exclusive Consumer)의 우선순위 결정 방식은?

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

독점 컨슈머(Exclusive Consumer)의 우선순위 결정 방식은?

엔터프라이즈 메시징 시스템(ActiveMQ, Amazon MQ 등)을 설계할 때, 분산 환경의 병렬 처리 이점보다 '절대적인 글로벌 순서 보장(Global Ordering)'이 훨씬 중요한 비즈니스 요건이 있습니다. 예를 들어, 증권 거래소의 호가 접수 시스템이나 레거시 시스템의 순차적 상태 업데이트(생성 $\rightarrow$ 수정 $\rightarrow$ 삭제) 등은 메시지가 큐(Queue)에 들어온 순서 그대로 단 1분의 오차도 없이 처리되어야 합니다.

이러한 극단적인 순서 보장을 위해 도입된 기능이 바로 독점 컨슈머(Exclusive Consumer)입니다. 여러 대의 컨슈머 서버가 큐에 접속해 있더라도, 브로커는 오직 단 하나의 컨슈머(Active)에게만 큐의 모든 메시지를 몰아주고 나머지 컨슈머들(Standby)은 유휴 상태로 대기시킵니다.

그렇다면 여러 대의 컨슈머가 동시에 접속을 시도하거나, 활성화된 컨슈머가 다운되었을 때 브로커는 어떤 기준으로 다음 독점 권한을 넘겨줄까요? 본 가이드에서는 독점 컨슈머의 우선순위 결정 방식과 내부 동작 메커니즘을 상세히 해부합니다.


1. 독점 권한 부여의 제1 원칙: 컨슈머 우선순위 (Consumer Priority)

브로커가 독점 컨슈머를 선정하는 가장 강력하고 명시적인 기준은 클라이언트가 큐에 접속할 때 스스로 부여하는 우선순위(Priority) 속성입니다.

  • 설정 방식: 클라이언트가 큐를 구독할 때 연결 URL이나 Destination 설정에 consumer.priority 파라미터를 명시합니다.
  • 값의 범위: 우선순위는 0부터 127까지의 정수로 설정할 수 있습니다. 숫자가 높을수록 우선순위가 높습니다. (기본값은 0입니다.)
  • 동작 매커니즘:
  • 큐에 설정된 옵션(consumer.exclusive=true)을 확인한 브로커는 현재 큐에 연결된 모든 컨슈머들의 priority 값을 비교합니다.
  • 만약 Consumer A가 우선순위 10으로, Consumer B가 우선순위 5로 접속해 있다면, 브로커는 주저 없이 Consumer A에게 큐의 독점적 소비 권한(Lock)을 부여합니다.
  • Consumer B는 연결은 유지한 채 브로커로부터 어떠한 메시지도 받지 않고 대기 상태(Standby)로 머물게 됩니다.

이러한 명시적 우선순위 부여는 고성능 베어메탈 서버(Priority 10)를 주 처리 노드로 두고, 일반 클라우드 인스턴스(Priority 5)를 백업 노드로 구성하는 식의 능동적인 자원 할당을 가능하게 합니다.


2. 제2 원칙: 동일 우선순위 간의 경합 해결 (Tie-Breaker)

실무 환경에서는 대부분 동일한 소스 코드를 가진 컨슈머 애플리케이션 여러 대가 동시에 배포되어 기동됩니다. 즉, 연결된 모든 컨슈머의 priority 값이 기본값(0)이거나 동일한 숫자로 설정되는 경우가 가장 흔합니다. 이럴 때 브로커는 어떻게 승자를 결정할까요?

  • 선착순(First-Come, First-Served) 배정:
    우선순위가 동일한 경우, 브로커는 가장 먼저 TCP/IP 연결을 맺고 세션을 생성한 컨슈머에게 독점 권한을 부여합니다.
  • 네트워크 지연의 변수:
    서버 3대를 동시에 기동하더라도 네트워크 스위치의 라우팅 지연, JVM 웜업 시간, 커넥션 풀 초기화 속도의 미세한 차이에 의해 브로커에 0.001초라도 먼저 도착한 커넥션이 승리합니다. 따라서 모든 조건이 동일하다면 개발자나 운영자는 어떤 서버가 Active 상태가 될지 정확히 예측하기 어렵습니다.

3. 장애 발생 시의 페일오버(Failover) 및 재선출 과정

독점 컨슈머 아키텍처의 핵심은 단일 처리로 인한 '단일 장애점(SPOF)' 위험을 대기 중인 다른 컨슈머들을 통해 상쇄하는 것입니다. 현재 독점 권한을 가진 컨슈머가 장애로 다운되었을 때의 재선출 과정은 다음과 같습니다.

  1. 연결 단절 감지: Active 상태인 컨슈머의 프로세스가 죽거나 네트워크 파티션이 발생하면, 브로커는 소켓 종료 또는 Heartbeat 타임아웃을 통해 이를 즉시 감지합니다.
  2. 권한 회수 및 롤백: 브로커는 해당 컨슈머에게 부여했던 독점 권한을 회수합니다. 이때 컨슈머에게 전달은 되었으나 아직 수신 확인(ACK)을 받지 못한 메시지들은 즉시 큐의 맨 앞으로 롤백(Rollback)되어 순서를 보존합니다.
  3. 대기열 재평가: 브로커는 현재 대기 중인(Standby) 나머지 컨슈머들의 목록을 다시 스캔합니다.
  4. 권한 이양: 앞서 언급한 원칙(1순위: 가장 높은 Priority, 2순위: 먼저 접속한 시간)에 따라 다음 승자를 결정하고 즉시 롤백된 메시지부터 전송을 재개합니다.

이러한 페일오버 과정은 브로커 내부에서 밀리초 단위로 신속하게 이루어지므로, 시스템 전체의 다운타임 없이 엄격한 순서 보장을 유지할 수 있습니다.


4. 선점(Preemption) 기능의 이해와 부작용

여기서 한 가지 중요한 아키텍처적 의문이 생깁니다.
*"우선순위 5인 Consumer B가 독점 중인 상황에서, 나중에 우선순위 10인 Consumer A가 기동되어 큐에 접속하면 어떻게 될까요?"*

  • 기본적으로 ActiveMQ의 독점 컨슈머 모델은 선점형(Preemptive)으로 동작합니다.
  • 더 높은 우선순위를 가진 Consumer A가 접속하는 순간, 브로커는 기존에 잘 동작하고 있던 Consumer B의 독점 권한을 강제로 박탈해버립니다. 그리고 새로운 Consumer A에게 권한을 부여합니다.
  • 주의사항: 이 선점 과정에서 Consumer B가 현재 처리 중이던 메시지가 있다면, 권한 박탈로 인해 처리가 롤백되고 Consumer A로 재전송되면서 일시적인 중복 처리(Redelivery) 이슈가 발생할 수 있습니다. 런타임 중에 우선순위가 더 높은 노드를 투입할 때는 이 트랜잭션 롤백 가능성을 반드시 고려하여 멱등성(Idempotency) 로직을 갖추어야 합니다.

5. 메시지 그룹(Message Groups)과의 결정적 차이

순서를 보장한다는 측면에서 종종 '독점 컨슈머'와 '메시지 그룹(JMSXGroupID)' 기능이 혼동되곤 합니다. 적용 목적을 명확히 구분해야 합니다.

  • 메시지 그룹: 특정 ID(예: 주문번호)를 가진 메시지들만 동일한 컨슈머에게 보내어 '부분적인 순서'를 보장합니다. 여러 컨슈머가 병렬로 각기 다른 그룹의 메시지를 처리하므로 분산 처리율(Throughput)이 높습니다.
  • 독점 컨슈머: 큐 전체에 자물쇠를 채워 단 하나의 컨슈머만 '전체적인 순서'를 보장하며 처리합니다. 병렬 처리가 원천적으로 불가능하므로, 시스템의 최대 처리량은 해당 컨슈머 노드 1대의 스펙(단일 스레드 처리 속도)을 절대 넘을 수 없습니다.

6. 마무리 및 가이드

독점 컨슈머는 순서 정합성이 시스템의 처리량(Throughput)보다 압도적으로 중요한 특수 도메인에서 강력한 힘을 발휘합니다.

이 기능을 도입할 때는 consumer.priority를 활용하여 Active-Standby 노드 간의 명확한 서열을 정리하고, 권한이 강제 이양되는 선점 현상 발생 시 애플리케이션 단에서 데이터 베이스 커밋이 어떻게 롤백되고 복구되는지 꼼꼼히 테스트해야 합니다. 또한, 전체 메시지 파이프라인의 병목 지점이 될 수밖에 없으므로, 해당 큐로 유입되는 메시지 발행량을 단일 서버가 충분히 소화할 수 있는지 사전 부하 테스트가 필수적입니다.

반응형