'memoryUsage' 임계치 도달 시 컨슈머에게 우선권을 주는 설정은?
엔터프라이즈 환경에서 메시지 브로커(ActiveMQ 등)를 운영할 때, 폭발적인 트래픽으로부터 시스템의 붕괴(OOM, Out Of Memory)를 막는 최후의 보루는 memoryUsage 임계치 설정입니다.
하지만 인프라 엔지니어들이 종종 간과하는 치명적인 함정이 있습니다. 브로커의 메모리가 100% 꽉 찼을 때, 살기 위해 프로듀서(Producer)의 입구를 막아버리는 흐름 제어(Producer Flow Control)까지는 좋지만, 정작 큐를 비워주어야 할 구원투수인 컨슈머(Consumer)의 작업마저 메모리 부족으로 함께 멈춰버리는 교착 상태(Deadlock)에 빠질 수 있다는 점입니다.
컨슈머가 메시지를 가져가려면(Dispatch), 브로커는 디스크에 있는 메시지를 메모리로 퍼 올려야 하고, 수신 확인(ACK)을 처리하기 위한 논리적 메모리 공간도 필요합니다. 프로듀서가 브로커의 메모리를 100% 점유해버리면 컨슈머는 아무 일도 하지 못하고 인프라는 완전히 마비됩니다.
이 가이드에서는 메모리 고갈 상황에서 프로듀서의 자원을 제한하고, 컨슈머에게 메모리 사용의 '우선권(Priority)'과 '예비 공간'을 강제로 할당하여 꽉 막힌 시스템의 숨통을 틔워주는 두 가지 핵심 아키텍처 설정을 상세히 해부합니다.

1. 목적지(Destination) 레벨의 방어선: cursorMemoryHighWaterMark
개별 큐(Queue)나 토픽(Topic) 레벨에서 컨슈머의 숨통을 틔워주는 가장 기본적이고 훌륭한 안전장치는 바로 cursorMemoryHighWaterMark 옵션입니다.
기본적으로 이 값은 70 (70퍼센트)으로 설정되어 있습니다. 이 숫자에 담긴 아키텍처적 의미는 매우 깊습니다.
- 동작 원리: 특정 큐에 할당된 최대 허용 메모리(
memoryLimit)가 100MB라고 가정해 보겠습니다. 프로듀서가 메시지를 계속 쏟아내어 큐의 메모리 사용량이 70MB(70퍼센트)에 도달하는 순간, 브로커의 메모리 커서(Cursor)는 더 이상 들어오는 메시지를 메모리에 적재하지 않고 즉각적으로 디스크(KahaDB)로 우회시켜 페이징(Paging)하기 시작합니다. - 컨슈머 우선권의 확보: 왜 100퍼센트가 아니라 70퍼센트에서 멈출까요? 남은 30퍼센트의 메모리 공간을 '컨슈머가 메시지를 소비하고 디스패치하는 데 필요한 전용 작업 공간'으로 철저하게 비워두기 위해서입니다.
- 프로듀서의 트래픽이 아무리 폭주해도 큐 메모리의 70퍼센트 이상을 점유할 수 없으므로, 컨슈머는 100퍼센트 한계에 부딪히는 일 없이 남은 30퍼센트의 여유 공간을 쾌적하게 활용하여 큐를 빠르게 비워낼 수 있는 완벽한 우선권을 보장받게 됩니다.
[설정 방법]activemq.xml의 <destinationPolicy> 블록에서 다음과 같이 설정합니다.
<policyEntry queue=">" cursorMemoryHighWaterMark="70" />
만약 컨슈머의 처리 속도가 느리고 메시지 본문 페이로드가 큰 무거운 환경이라면, 이 값을 50으로 낮추어 컨슈머의 숨통 공간(여유분 50퍼센트)을 더 크게 확보하는 튜닝이 필요할 수 있습니다.
2. 브로커(Broker) 레벨의 완벽한 격리: splitSystemUsageForProducersConsumers
앞서 설명한 커서 수위 조절은 개별 큐 레벨에서의 방어책입니다. 하지만 전체 브로커 서버의 글로벌 메모리(systemUsage의 memoryUsage) 자체가 100% 꽉 차버린다면, 커서 설정과 무관하게 시스템 전체가 멈춰버립니다.
이러한 최악의 재난을 원천 차단하기 위해 ActiveMQ는 글로벌 메모리 풀 자체를 '생산자용(Producer)'과 '소비자용(Consumer)' 두 개의 물리적 공간으로 완벽하게 반으로 나누어(Split) 버리는 극단적이고 강력한 옵션을 제공합니다. 바로 splitSystemUsageForProducersConsumers 입니다.
- 동작 원리: 이 옵션을
true로 켜면, 브로커 전체에 할당된 힙 메모리(예: 1GB)를 프로듀서의 유입 버퍼와 컨슈머의 디스패치 및 프리패치(Prefetch) 버퍼로 완전히 분리합니다. - 기본적으로 프로듀서에게 전체 메모리의 60퍼센트, 컨슈머에게 40퍼센트의 메모리가 할당됩니다.
- 압도적인 아키텍처적 이점: 외부의 악의적인 공격이나 클라이언트의 버그로 인해 수십만 개의 프로듀서가 브로커에 1GB 분량의 트래픽 폭탄을 던졌다고 가정해 봅니다. 프로듀서의 메모리 풀인 600MB는 순식간에 100% 꽉 차게 되며, 프로듀서들은 즉시 블로킹(Blocking)됩니다.
- 하지만 컨슈머에게 할당된 400MB의 메모리 풀은 프로듀서에 의해 단 1바이트도 침범당하지 않고 청정하게 유지됩니다. 따라서 컨슈머는 아무런 방해를 받지 않고 자신만의 400MB 메모리 인프라를 독점하며 여유롭게 메시지를 퍼갑니다. 결국 컨슈머가 큐를 원활하게 비워냄으로써, 멈춰있던 프로듀서의 60퍼센트 메모리 공간도 다시 살아나는 진정한 '구원자' 역할을 수행하게 됩니다.
[설정 방법]
이 기능은 activemq.xml의 최상단 <broker> 태그에 속성으로 직접 부여해야 합니다.
<broker xmlns="http://activemq.apache.org/schema/core"
splitSystemUsageForProducersConsumers="true"
producerSystemUsagePortion="60"
consumerSystemUsagePortion="40">
비즈니스의 성격에 따라 데이터 인입 트래픽이 훨씬 강력하다면 프로듀서 비율을 높이고, 구독자(Subscriber)가 많아 브로드캐스팅 디스패치 부하가 큰 Topic 위주의 환경이라면 컨슈머의 비율을 50이나 60으로 상향 조절하여 소비자에게 우선권을 쥐여주는 튜닝이 필수적입니다.
3. 추가적인 튜닝: Consumer Prefetch Size의 타이트한 조절
메모리가 가득 찬 상황에서 컨슈머에게 시스템의 제어권과 메모리를 쥐여주었다면, 컨슈머 본인들도 메모리를 아껴 써야 합니다.
컨슈머가 브로커에 연결될 때, 브로커는 네트워크 I/O 효율을 극대화하기 위해 컨슈머가 아직 요청하지도 않은 메시지를 미리 브로커의 메모리에서 컨슈머의 로컬 메모리로 쭉 밀어내는 프리패치(Prefetch) 동작을 수행합니다. (기본적으로 큐의 프리패치 한도는 1000개입니다.)
브로커 메모리가 만성적으로 부족한 환경에서, 수십 개의 컨슈머가 각각 1000개씩 메시지를 프리패치해 버리면 이 역시 브로커의 소비자 메모리 풀(Consumer Portion)을 급격히 고갈시키는 주범이 됩니다.
따라서 메모리 임계치 장애를 자주 겪는 인프라 환경이라면, 클라이언트 애플리케이션의 연결 URI 옵션에 jms.prefetchPolicy.queuePrefetch=100 과 같이 프리패치 사이즈를 과감하게 10분의 1 수준으로 줄여보십시오. 브로커 힙 메모리에 상주하는 디스패치 대기열의 크기가 극적으로 줄어들면서, 훨씬 더 적은 메모리로도 수많은 컨슈머들이 지연 없이 안정적으로 데이터를 빼갈 수 있는 경량화된 파이프라인이 완성됩니다.
요약 및 운영 가이드
메시지 브로커에서 '메모리 100퍼센트 고갈'은 단순히 더 이상 저장할 공간이 없다는 뜻이 아니라, 큐를 비워주어야 할 작업자(Consumer)의 팔다리마저 묶여버리는 치명적인 인프라 마비 상태를 의미합니다.
인프라 아키텍트는 반드시 cursorMemoryHighWaterMark를 통해 개별 큐의 70퍼센트 선에서 선제 방어막을 치고, splitSystemUsageForProducersConsumers 옵션을 통해 브로커 전체의 글로벌 메모리를 수신용과 발신용으로 엄격하게 격리해야 합니다. 이렇게 물리적, 논리적으로 방어선이 설계된 인프라만이 어떠한 트래픽 폭주 상황에서도 컨슈머의 디스패치 권한을 끝까지 보장하며, 시스템 스스로 장애를 회복하는 강력한 복원력(Resilience)을 갖추게 될 것입니다.
'1. 개발 > 1.8. ActiveMQ' 카테고리의 다른 글
| KahaDB의 인덱스 파일(db.data) 크기 제한과 분할 방법은? (0) | 2026.04.04 |
|---|---|
| 'Message Expiry Scan' 작업이 CPU를 점유할 때 튜닝 방법은? (0) | 2026.04.04 |
| 디스크 조각화(Fragmentation)가 KahaDB 읽기 성능에 미치는 영향? (0) | 2026.04.04 |
| 'Zero-copy' 전송 기술이 Artemis 저장소 읽기에서 어떻게 쓰이나? (1) | 2026.04.03 |
| Artemis에서 'Database'를 저장소로 쓸 때의 스키마 구조는? (0) | 2026.04.03 |