'Blob Message' 전송 시 외부 저장소(FTP/HTTP)와의 연동 원리는?
엔터프라이즈 아키텍처를 설계할 때, 메시지 브로커(ActiveMQ 등)를 통해 텍스트나 JSON 형태의 가벼운 데이터만 주고받는 것이 이상적입니다. 하지만 현실의 비즈니스 요구사항은 녹록지 않습니다. 수백 메가바이트(MB)의 로그 압축 파일, 기가바이트(GB) 단위의 고화질 동영상이나 도면 파일 등을 비동기 파이프라인을 통해 전송해야 하는 상황이 빈번하게 발생합니다.
이러한 거대한 페이로드(Payload)를 일반적인 BytesMessage에 담아 브로커로 쏘아 보내면, 브로커의 JVM 힙 메모리는 순식간에 임계치를 돌파하여 OOM(Out Of Memory)으로 시스템이 붕괴됩니다. 브로커는 초고속 라우팅 엔진이지, 대용량 파일 저장소가 아니기 때문입니다.
이 딜레마를 완벽하게 해결하기 위해 도입된 개념이 바로 'Blob Message (Binary Large Object Message)'이며, 핵심 원리는 실제 거대한 파일 데이터를 브로커가 아닌 외부 저장소(FTP, HTTP 서버, S3 등)로 우회(Bypass)시키는 것입니다. 본 가이드에서는 이 아웃 오브 밴드(Out-of-Band) 패턴의 연동 원리와 인프라 최적화 포인트를 상세히 해부합니다.

1. 핵심 아키텍처: 'Out-of-Band' 전송 패턴
Blob Message의 근본적인 철학은 "제어 흐름(Control Flow)과 데이터 흐름(Data Flow)의 완벽한 분리"입니다.
일반적인 메시징 모델에서는 메시지의 헤더와 무거운 본문(Body)이 하나의 패킷으로 묶여 브로커를 통과합니다. 하지만 Blob Message 패턴을 사용하면, 수 기가바이트의 무거운 파일은 FTP나 HTTP 서버를 통해 전송(Data Flow)되고, 브로커를 통과하는 메시지에는 오직 "파일이 저장된 외부 서버의 URL 주소(포인터)"라는 아주 가벼운 메타데이터(Control Flow)만 담기게 됩니다.
브로커 입장에서는 단 몇 바이트짜리 텍스트 메시지를 라우팅하는 것과 완벽하게 동일한 부하만 발생하므로, 극한의 대용량 파일 전송 환경에서도 메시징 시스템 본연의 처리량(TPS)과 메모리 안정성을 100% 방어할 수 있습니다.
2. FTP/HTTP 외부 저장소 연동의 3단계 파이프라인
프로듀서(Producer)가 Blob Message를 생성하고 컨슈머(Consumer)가 이를 소비하기까지의 과정은 클라이언트 라이브러리 내부에서 다음과 같은 3단계로 자동화되어 처리됩니다.
1단계: 프로듀서의 파일 스트림 업로드 (Client -> File Server)
프로듀서 애플리케이션은 전송할 대용량 파일의 InputStream이나 File 객체를 ActiveMQ 클라이언트 라이브러리에 넘깁니다. 클라이언트는 브로커와 통신하기 전에, 설정된 외부 저장소(HTTP의 경우 PUT 메서드, FTP의 경우 STOR 명령어)로 해당 파일을 백그라운드에서 직접 업로드합니다. 이 과정에서 브로커 서버는 전혀 개입하지 않습니다.
2단계: URL 포인터 메시지 라우팅 (Client -> Broker -> Client)
업로드가 성공적으로 완료되면, 외부 저장소는 저장된 파일의 고유 URL(예: http://fileserver.internal:8080/uploads/msg-12345.bin)을 반환합니다.
클라이언트 라이브러리는 이 URL 문자열을 페이로드로 삼아 작고 가벼운 ActiveMQBlobMessage 객체를 생성한 뒤, 브로커의 특정 큐(Queue)로 전송합니다. 브로커는 이 가벼운 메시지를 받아 대기 중인 컨슈머에게 즉각 디스패치(Dispatch)합니다.
3단계: 컨슈머의 스트림 다운로드 (File Server -> Client)
컨슈머가 브로커로부터 Blob Message를 수신합니다. 컨슈머 애플리케이션이 message.getInputStream() 메서드를 호출하는 순간, 클라이언트 라이브러리는 내장된 URL을 해독하여 외부 FTP/HTTP 서버로 직접 연결(Connection)을 맺고 파일 스트림을 다운로드하기 시작합니다. 컨슈머는 이 스트림을 읽어 로컬 디스크에 저장하거나 즉시 비즈니스 로직 처리에 활용합니다.
3. 클라이언트 통제 센터: 'BlobTransferPolicy'
이러한 마법 같은 우회 전송을 가능하게 하는 ActiveMQ 클라이언트 내부의 핵심 객체가 바로 BlobTransferPolicy입니다. 프로듀서와 컨슈머는 이 정책 객체를 통해 외부 저장소와 어떻게 통신할지 구체적인 규약을 설정합니다.
- 업로드 URL 설정: 프로듀서 측 커넥션 팩토리(Connection Factory)나 메시지 생성 단에서
blobTransferPolicy.setUploadUrl("http://내부파일서버IP:8080/uploads/")와 같이 파일 서버의 엔드포인트를 명시해야 합니다. - 전략 커스터마이징 (Strategy): 기본적으로 HTTP 전송을 지원하지만, 사내 인프라 표준이 FTP이거나 AWS S3, 혹은 보안 공유 폴더(SMB/NFS)라면 어떨까요? 이때는 클라이언트 측에서
UploadStrategy와DownloadStrategy인터페이스를 직접 구현(Implement)하여BlobTransferPolicy에 주입하면, 세상에 존재하는 그 어떤 스토리지 프로토콜과도 유연하게 연동할 수 있습니다.
4. 외부 저장소 연동 시 인프라 아키텍트의 필수 고려사항
Blob Message는 브로커를 살리는 대신, 인프라의 복잡성을 외부 파일 서버로 전가하는 아키텍처입니다. 프로덕션 환경에 이 패턴을 도입할 때 반드시 다음 세 가지 운영 요소를 통제해야 합니다.
A. 파일의 생명주기(Lifecycle)와 고아 파일 문제
가장 빈번하게 발생하는 인프라 장애 원인입니다. 컨슈머가 메시지를 수신하고 파일 다운로드까지 완료하여 브로커에 ACK(처리 완료) 신호를 보냈다고 가정해 보겠습니다. 브로커는 큐에서 메시지를 지우지만, 외부 HTTP/FTP 서버에 업로드된 거대한 원본 파일은 누가 지울까요?
ActiveMQ가 테스트용으로 제공하는 내장 파일 서버는 일정 수준 자동 정리를 지원하지만, 프로덕션용 전용 웹 서버(Nginx, Apache)나 FTP 서버를 연동했다면 브로커는 외부 서버의 파일을 함부로 삭제하지 못합니다. 다운로드를 마친 컨슈머 애플리케이션 코드가 외부 저장소의 API를 호출하여 명시적으로 삭제(DELETE) 로직을 실행하거나, 파일 서버 측에 일괄 삭제 스케줄러(Cronjob)를 구축하지 않으면 파일 서버의 디스크가 순식간에 100% 꽉 차게 됩니다.
B. 네트워크 대역폭의 물리적 분리 (Network Isolation)
수 GB의 파일이 HTTP/FTP 서버로 업로드되고 다운로드되는 트래픽은 데이터베이스나 브로커가 사용하는 핵심 비즈니스 네트워크(메인 스위치)를 통과해서는 안 됩니다. 거대한 스트림 전송은 순식간에 스위치의 대역폭을 고갈시켜 다른 마이크로서비스들의 핑(Ping) 지연을 유발합니다. 스토리지 서버와의 연동은 반드시 별도의 전용 백본망(Storage Network)이나 물리적으로 분리된 NIC(네트워크 인터페이스 카드)를 통해 우회하도록 망을 설계해야 합니다.
C. 외부 저장소의 단일 장애점(SPOF) 방지
브로커를 이중화(HA)하여 아무리 견고하게 만들어도, 연동된 외부 FTP 서버가 단일 노드로 구성되어 있다면 파일 서버가 다운되는 순간 전체 Blob Message 파이프라인이 멈춰버립니다. 따라서 외부 저장소 역시 로드 밸런서(L4/L7) 뒤에 다중화된 웹 서버 클러스터를 배치하거나, 클라우드 환경의 고가용성 객체 스토리지(S3 등)를 연동 타겟으로 삼아 시스템의 회복 탄력성(Resilience)을 브로커 수준으로 끌어올려야 합니다.
결론적으로 Blob Message 아키텍처는 고속도로(브로커)에 무거운 화물차(대용량 파일)가 진입하지 못하도록, 별도의 전용 화물 철도(외부 FTP/HTTP 저장소)를 깔아주고 송장(URL)만 고속도로로 빠르게 전달하는 현대 시스템 공학의 지혜입니다. 연동되는 외부 저장소의 디스크 관리와 네트워크 병목을 정교하게 제어하여, 대용량 미디어 및 로그 처리 환경에서도 흔들림 없는 이벤트 기반 아키텍처(EDA)를 완성하시기 바랍니다.
'1. 개발 > 1.8. ActiveMQ' 카테고리의 다른 글
| maxBrowsePageSize가 대량 큐 조회 시 브로커 메모리에 주는 부하? (0) | 2026.04.07 |
|---|---|
| 브로커 재시작 없이 저장소 설정을 변경할 수 있는 범위는? (0) | 2026.04.05 |
| KahaDB의 인덱스 파일(db.data) 크기 제한과 분할 방법은? (0) | 2026.04.04 |
| 'Message Expiry Scan' 작업이 CPU를 점유할 때 튜닝 방법은? (0) | 2026.04.04 |
| 'memoryUsage' 임계치 도달 시 컨슈머에게 우선권을 주는 설정은? (0) | 2026.04.04 |