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

'Producer Window Size'를 통한 클라이언트 흐름 제어 원리는?

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

'Producer Window Size'를 통한 클라이언트 흐름 제어 원리는?

엔터프라이즈 메시징 시스템을 운영할 때 맞닥뜨리는 가장 흔하고 치명적인 장애 중 하나는 '빠른 생산자(Fast Producer)' 문제입니다. 애플리케이션(생산자)이 브로커가 처리할 수 있는 속도나 컨슈머(Consumer)가 소비하는 속도보다 훨씬 빠르게 엄청난 양의 메시지를 쏟아낼 때 발생합니다.

특히 비동기 전송(Asynchronous Send)을 사용할 경우, 생산자는 브로커의 응답을 기다리지 않고 네트워크로 데이터를 무한정 밀어 넣습니다. 결국 브로커의 메모리는 고갈되고 전체 시스템이 OOM(Out of Memory)으로 다운되는 대참사가 벌어집니다. 이를 사전에 차단하고 네트워크 파이프라인의 데이터 흐름을 우아하게 통제하는 핵심 메커니즘이 바로 'Producer Window Size(생산자 윈도우 크기)' 기반의 흐름 제어(Flow Control)입니다.

이 가이드에서는 브로커가 생산자의 폭주를 어떻게 제어하는지, TCP의 슬라이딩 윈도우(Sliding Window)와 유사한 이 메커니즘의 내부 동작 원리와 최적의 튜닝 포인트를 상세히 해부합니다.


1. 동기 전송 vs 비동기 전송에서의 흐름 제어 차이

Producer Window Size의 필요성을 이해하려면 먼저 메시지 전송 방식의 차이를 알아야 합니다.

  • 동기 전송 (Synchronous Send): 영속성(Persistent) 메시지를 트랜잭션 없이 보낼 때 기본적으로 작동합니다. 생산자는 메시지 1개를 보낸 후, 브로커가 디스크에 안전하게 기록하고 ACK(수신 확인)를 돌려줄 때까지 애플리케이션 스레드를 대기(Block)시킵니다. 1개씩 확인받고 보내므로 자연스럽게 흐름 제어가 이루어집니다.
  • 비동기 전송 (Asynchronous Send): 비영속성(Non-persistent) 메시지나 트랜잭션 내의 메시지를 보낼 때 작동합니다. 생산자는 ACK를 기다리지 않고 메시지를 네트워크 버퍼에 쏟아붓습니다. 성능은 극대화되지만, 브로커가 처리할 수 있는 한계를 순식간에 돌파해버리는 '소방 호스(Firehose)' 효과가 발생합니다.

Producer Window Size는 바로 이 '비동기 전송' 환경에서 생산자가 브로커를 넉다운시키는 것을 방지하기 위해 고안된 안전장치입니다.


2. 'Producer Window Size'의 핵심 동작 매커니즘

이 매커니즘은 네트워크의 TCP 슬라이딩 윈도우 알고리즘과 매우 유사하게 동작합니다. 개별 메시지의 개수가 아닌 '바이트(Byte) 크기'를 기준으로 전송량을 통제합니다.

  1. 윈도우 할당: 생산자가 브로커와 연결을 맺을 때, 사전에 설정된 크기(예: 1MB)의 'Window Size'를 할당받습니다. 이는 브로커로부터 ACK를 받지 않고도 연속해서 보낼 수 있는 최대 데이터의 양을 의미합니다.
  2. 크기 차감 (Send): 생산자가 100KB 크기의 메시지를 하나 비동기로 전송합니다. 할당된 윈도우 크기는 즉시 900KB로 줄어듭니다.
  3. 한계 도달 및 대기 (Block): 생산자가 계속 메시지를 보내어 윈도우 크기가 0에 도달하거나 0보다 작아지면, 생산자 애플리케이션의 send() 메서드는 더 이상 진행되지 않고 블로킹(대기) 상태에 빠집니다.
  4. 윈도우 회복 (ProducerAck): 브로커는 수신한 메시지를 메모리에 적재하거나 디스크에 처리한 후, 여유가 생기면 생산자에게 ProducerAck라는 특수한 내부 신호를 보냅니다. 이 신호를 받은 생산자는 처리된 용량만큼 윈도우 크기를 다시 회복(Replenish)하고, 대기 상태에서 풀려나 다음 메시지 전송을 재개합니다.

이러한 피드백 루프(Feedback Loop)를 통해, 생산자는 항상 브로커가 소화할 수 있는 용량 내에서만 데이터를 전송하는 백프레셔(Backpressure) 구조가 완성됩니다.


3. 브로커 메모리 한계(Memory Limit)와의 차이점

종종 Producer Window Size를 브로커 설정 파일에 있는 큐의 memoryLimit 설정과 혼동하는 경우가 있습니다. 두 기능은 목적은 같지만 개입하는 타이밍과 레이어가 다릅니다.

  • Producer Window Size (사전 예방): 클라이언트(생산자)와 브로커 사이의 네트워크 파이프라인(In-flight data)에 떠 있는 데이터양을 제한합니다. 특정 큐의 상태와 무관하게, 브로커 서버 자체가 네트워크 트래픽에 압사당하는 것을 방지하는 클라이언트 측의 1차 방어선입니다.
  • Broker Memory Limit (사후 차단): 큐 단위로 설정되며, 이미 브로커 내부에 들어와 큐에 적재된 메시지의 총량을 제한합니다. 큐가 꽉 찼을 때 동작하는 2차 방어선(Producer Flow Control)입니다.

4. 실무 설정 방법 및 튜닝 가이드 (ActiveMQ 기준)

ActiveMQ 환경에서 Producer Window Size를 설정하는 방법은 클라이언트의 연결 URI를 수정하는 것으로 매우 간단합니다. 단위는 바이트(Bytes)입니다.

A. 설정 예시 (클라이언트 연결 URI)

// 윈도우 크기를 1MB (1048576 Bytes)로 설정하는 예시
tcp://localhost:61616?jms.producerWindowSize=1048576

B. 크기(Size) 결정에 따른 트레이드오프 (Trade-off)

  • 윈도우 크기가 너무 작을 때 (예: 10KB):
    브로커는 매우 안전해지지만, 생산자는 몇 개의 메시지만 보내도 금세 윈도우가 고갈되어 브로커의 ProducerAck를 기다려야 합니다. 사실상 동기 전송(Synchronous Send)과 다를 바 없어지며, 대량의 데이터를 빠르게 보내야 하는 시스템에서는 심각한 처리량(Throughput) 저하를 겪게 됩니다.
  • 윈도우 크기가 너무 클 때 (예: 1GB):
    생산자는 거침없이 데이터를 쏟아냅니다. 처리량은 극대화되지만, 브로커에 일시적인 지연(GC Pause나 디스크 병목)이 발생했을 때 네트워크 버퍼와 브로커 메모리에 수백 MB의 데이터가 한꺼번에 몰려들어 결국 브로커가 OOM 에러를 뿜으며 사망할 위험이 급증합니다.

튜닝 모범 사례 (Best Practice):
일반적인 엔터프라이즈 환경에서는 기본값 그대로 두거나, 애플리케이션의 평균 메시지 크기와 네트워크 지연 시간(Ping RTT)을 계산하여 수 MB ~ 수십 MB (예: 10MB ~ 64MB) 사이로 설정하는 것이 권장됩니다. 브로커의 물리적 메모리가 8GB라면, 연결될 총 생산자 스레드 수 $\times$ Window Size의 합이 브로커 가용 메모리의 10~20%를 넘지 않도록 계산하여 설계해야 합니다.


5. 아키텍처 설계 시 핵심 주의사항

Producer Window Size를 적용할 때 애플리케이션 개발자가 반드시 대비해야 하는 아키텍처적 변수가 있습니다.

스레드 블로킹(Thread Blocking)에 대한 방어 로직:
윈도우가 고갈되어 send() 메서드가 대기 상태에 빠진다는 것은, 해당 로직을 실행 중인 웹 서버(Tomcat 등)의 요청 처리 스레드나 비즈니스 스레드가 멈춘다는 것을 의미합니다. 만약 브로커 장애로 인해 영원히 ProducerAck가 오지 않는다면, 애플리케이션 서버의 스레드 풀(Thread Pool)이 모두 고갈되어 웹 서비스 전체가 마비되는 연쇄 장애(Cascading Failure)로 이어집니다.

따라서 윈도우 사이즈를 설정할 때는 반드시 클라이언트 단에서 메시지 전송 시 비동기 전송 API(Async Send)의 콜백(Callback) 방식을 활용하거나, 전송 시도 시 엄격한 타임아웃(Timeout)을 설정하여 일정 시간 윈도우가 열리지 않으면 예외(Exception)를 발생시키고 서킷 브레이커(Circuit Breaker)를 작동시키는 방어적 프로그래밍(Defensive Programming)이 필수적으로 병행되어야 합니다.

반응형