'tempUsage'가 꽉 찼을 때 Non-persistent 메시지의 운명은?
엔터프라이즈 메시징 아키텍처에서 시스템의 처리 속도를 극한으로 끌어올려야 할 때, 인프라 엔지니어들은 디스크 I/O를 완전히 생략하는 '비영속(Non-persistent) 메시지'라는 카드를 꺼내 듭니다. 이 메시지들은 오직 브로커의 빠른 RAM(JVM 힙 메모리) 위에서만 머물며 빛의 속도로 라우팅됩니다.
하지만 비즈니스 이벤트나 초당 수만 건의 로그 트래픽이 몰아쳐 브로커의 메모리가 한계에 도달하면 어떻게 될까요? 브로커는 시스템 붕괴(OOM)를 막기 위해 메모리에 있던 비영속 메시지들을 디스크의 임시 폴더로 피난시키는 스와핑(Swapping) 작업을 시작합니다. 이 피난처의 크기를 통제하는 설정이 바로 tempUsage입니다.
그렇다면, 이 마지막 피난처인 tempUsage 공간마저 100% 꽉 차버린다면 시스템 내부에서는 어떤 참사가 벌어지며, 전송 중이던 메시지들은 어떤 운명을 맞이하게 될까요? 이 가이드에서는 비영속 메시지의 라이프사이클과 임시 저장소 고갈 시 발생하는 연쇄 장애 메커니즘을 상세히 해부합니다.

1. 비영속 메시지의 피신 메커니즘 (Memory to Temp)
비영속 메시지의 기본 거주지는 메모리(memoryUsage)입니다. 평상시에는 디스크 근처에도 가지 않습니다.
그러나 컨슈머(Consumer) 애플리케이션에 병목이 생겨 큐에 메시지가 계속 쌓이고, 마침내 브로커에 설정된 memoryUsage 임계치에 도달했다고 가정해 보겠습니다.
브로커는 더 이상 메모리에 메시지를 올려둘 수 없으므로, 아주 무거운 결단을 내립니다. "임시로 디스크를 빌려서 쓰자"는 것입니다.
이때 브로커는 KahaDB의 주 저장소(Persistent Store)가 아닌, 임시 데이터 전용 스토어(Temporary Store, 주로 브로커 데이터 디렉토리 하위의 tmp 폴더)에 파일들을 생성하고 비영속 메시지들을 쏟아붓습니다. 이렇게 디스크로 쫓겨난 메시지들은 메모리에 여유가 생길 때까지 디스크에 잠들어 있게 되며, 이 임시 폴더가 사용할 수 있는 디스크의 최대 용량을 제한하는 설정값이 바로 tempUsage입니다.
2. 최후의 보루 'tempUsage'의 붕괴
메시지 폭주가 멈추지 않아 임시 디스크 공간마저 설정된 한계치(tempUsage의 100%)에 도달하거나, 물리적인 하드 디스크 용량 자체가 꽉 차버리는 순간(Disk Full)이 올 수 있습니다.
이 순간, 브로커는 더 이상 메모리에도, 임시 디스크에도 데이터를 담을 수 없는 진퇴양난의 '완벽한 포화 상태(Saturation)'에 빠집니다. 이때부터 시스템은 자신을 방어하기 위해 비영속 메시지를 보내려는 클라이언트(Producer)에게 매우 가혹한 조치를 취하기 시작합니다.
3. tempUsage 한계 도달 시 결정되는 3가지 운명
임시 저장소가 꽉 찬 순간, 시스템의 설정에 따라 비영속 메시지와 프로듀서 애플리케이션은 다음 세 가지 중 하나의 운명을 강요받습니다.
운명 A: 끝없는 무한 대기 (Producer Flow Control 발동)
가장 기본적이고 빈번하게 발생하는 상황입니다. 브로커는 메시지 유실을 막기 위해 큐의 입구를 강제로 틀어막습니다.
- 동작: 프로듀서가 새로운 비영속 메시지를
send()하는 순간, 브로커는 승인(ACK)을 주지 않고 클라이언트의 TCP 소켓을 블로킹(Blocking)합니다. - 파급 효과: 클라이언트 애플리케이션의 전송 스레드는 그 자리에 얼어붙어 무한정 대기 상태에 빠집니다. 웹 서버(Tomcat 등)에서 메시지를 보내고 있었다면 톰캣의 스레드 풀이 순식간에 고갈되어, 메시지 전송과 상관없는 일반 사용자의 HTTP 요청까지 모두 타임아웃 처리되는 끔찍한 연쇄 장애(Cascading Failure)로 이어집니다.
운명 B: 즉각적인 거절과 예외 투척 (Fast Fail)
시스템 아키텍트가 스레드 고갈이라는 최악의 사태를 막기 위해 클라이언트 설정에 sendFailIfNoSpace=true 옵션을 적용해 둔 경우입니다.
- 동작:
tempUsage가 꽉 찬 브로커에 메시지를 쏘는 순간, 클라이언트는 블로킹되지 않고 즉시javax.jms.ResourceAllocationException이라는 명시적인 에러를 던집니다. - 파급 효과: 비영속 메시지는 브로커에 도달하지 못하고 즉시 유실(Drop)됩니다. 하지만 클라이언트 애플리케이션의 스레드는 얼어붙지 않고 즉시 반환되므로, 에러 로그를 남기거나 사용자에게 "현재 서비스가 지연되고 있습니다"라는 우아한 실패(Graceful Degradation) 응답을 줄 수 있는 기회를 얻습니다.
운명 C: 오래된 메시지의 강제 폐기 (Eviction / Discard)
일부 차세대 브로커 설정이나 특수한 큐 정책(Eviction Policy)을 적용한 경우, 브로커가 스스로 살기 위해 이미 큐에 쌓여있던 과거의 비영속 메시지들을 버리기 시작합니다.
- 동작: 새로운 메시지가 들어올 공간을 확보하기 위해, 임시 저장소에 가장 먼저 들어와 있던(FIFO) 오래된 메시지들이나 우선순위가 낮은 메시지들을 디스크에서 조용히 삭제해 버립니다.
- 파급 효과: 클라이언트 스레드도 보호하고 시스템도 멈추지 않지만, 관리자도 모르는 사이에 데이터가 소실됩니다. 로그나 센서 데이터처럼 최신 데이터의 흐름이 과거 데이터보다 중요한 시스템(예: 주식 현재가 틱 데이터)에서 전략적으로 선택하는 운명입니다.
4. 인프라 마비를 알리는 브로커의 데스 로그 (Death Log)
tempUsage가 고갈되어 시스템이 멈추기 시작하면, 브로커의 activemq.log에는 이 비극을 알리는 매우 뚜렷한 경고 로그가 도배됩니다.
INFO | Usage Manager temp limit reached. Stopping producer ([프로듀서 ID]) to prevent flooding queue://[큐 이름].
이 로그가 발견되었다는 것은 "임시 스와핑 공간조차 다 써버려서, 살기 위해 프로듀서의 목줄을 쥐고 차단했다"는 브로커의 비명입니다. 이때는 인프라 엔지니어가 개입하여 출구를 막고 있는 병목 컨슈머를 해결하거나 강제로 큐를 비워주지 않는 이상 시스템은 스스로 회복하지 못합니다.
5. 아키텍처 생존을 위한 임시 저장소 튜닝 가이드 (Best Practices)
이러한 비영속 메시지의 재난을 막기 위해, 시스템 설계 단계부터 다음과 같은 방어 기제를 구축해야 합니다.
- 물리적 디스크 파티션의 격리:
브로커의activemq.xml에서<tempUsage>가 가리키는 디렉토리는 반드시 OS나 주 저장소(Persistent Store)와 물리적으로 다른 디스크 볼륨으로 격리해야 합니다. 비영속 메시지 폭주로 인해 임시 폴더가 디스크를 100% 채워버리더라도, 영속성 메시지를 저장하는 메인 디스크나 OS에 장애가 번지는 것을 막기 위한 격벽(Bulkhead) 설계입니다. - TTL (Time-To-Live)의 강력한 강제:
비영속 메시지는 애초에 오래 보관할 가치가 없는 데이터입니다. 프로듀서가 메시지를 보낼 때 반드시 타임아웃(예: 60초)을 설정하도록 강제하십시오.tempUsage로 밀려난 메시지라도 TTL이 지나면 브로커가 가비지 컬렉션을 통해 스스로 삭제하므로, 임시 저장소가 100%에 도달하는 것을 늦추거나 막을 수 있습니다. - 비율 기반의 여유로운 한계치 설정:
tempUsage의limit속성을 설정할 때, 서버의 전체 디스크 용량의 70%를 넘지 않도록 설정하십시오. 디스크가 물리적으로 100% 꽉 차면 OS 레벨의 I/O 에러가 발생하여 브로커 자체가 커널 패닉에 빠질 수 있습니다. 논리적인 한계(tempUsage임계치)를 물리적 한계보다 넉넉히 낮게 설정하여 브로커가 먼저 흐름 제어를 발동하게 만드는 것이 핵심입니다.
결론적으로 '비영속 메시지'는 빠르지만 위험한 칼날입니다. 메모리를 넘어 tempUsage라는 임시 저장소까지 가득 차는 순간, 그 칼날은 시스템 전체를 향해 블로킹이라는 연쇄 장애를 일으킵니다. 한계치 상황에서의 동작 원리와 방어 옵션(sendFailIfNoSpace)을 완벽하게 이해하고 튜닝하여, 어떠한 폭주 상황에서도 스레드가 얼어붙지 않는 견고한 인프라를 완성하시기 바랍니다.
'1. 개발 > 1.8. ActiveMQ' 카테고리의 다른 글
| Artemis의 'Mapped Memory' 페이징 전략이 성능을 높이는 이유는? (0) | 2026.04.01 |
|---|---|
| KahaDB의 'ignoreMissingJournalfiles' 옵션 사용 시 주의점은? (0) | 2026.04.01 |
| 'Producer Flow Control' 발생 시 로그에 찍히는 특정 메시지 패턴은? (0) | 2026.04.01 |
| 'Async Send' 사용 시 브로커의 승인(Ack) 없이 보낼 수 있는 최대 양은? (0) | 2026.04.01 |
| 브로커 내부의 'In-memory State'와 저장소 데이터 불일치 시 검증법은? (0) | 2026.03.28 |