메시지 브로커
"A message broker translates messages between formal messaging protocols, decoupling services to ensure highly scalable, asynchronous communication."
— 비동기 분산 시스템 통합 가이드북에 정의된 정석적 한 줄 평.
직접 대화하기 껄끄러운 서버들 사이에서 편지를 배달하고 보관해 주며, 상대방 서버가 죽어도 '난 모르겠고 내 할 일은 끝났다'라며 책임을 회피할 수 있게 돕는 관계 단절의 대가. 서버 하나가 맛이 가도 큐가 메시지를 붙잡고 있어 주는 덕분에 일요일 밤에 당장 긴급 장애 소집을 당하지 않게 구원해 주는 개발자의 진정한 생명 줄
1. 개요
메시지 브로커는 애플리케이션, 서비스 또는 시스템 간에 메시지를 전송, 수신 및 중계하는 중간 소프트웨어 아키텍처 컴포넌트다. 전통적인 웹 환경에서 두 서비스가 통신하려면 REST API와 같은 동기식(Synchronous) 호출 방식을 사용하여 한쪽이 응답할 때까지 다른 쪽이 하염없이 기다려야 했다. 그러나 이 방식은 응답을 기다리는 쪽의 리소스를 소모시키고, 받는 쪽 서버가 터지는 순간 보내는 쪽까지 도미노처럼 함께 죽어버리는 파멸적 장애 전파를 초래한다.
메시지 브로커는 중간에 버퍼 역할을 하는 '큐(Queue)'나 '토픽(Topic)'을 두고 메시지를 비동기식(Asynchronous)으로 배달하여 두 시스템 사이의 연결 고리를 느슨하게 만들어 준다. 즉, 송신자는 수신자의 상태나 주소를 전혀 신경 쓸 필요 없이 그저 브로커에게 메시지를 툭 던져두고 자기 할 일로 넘어가며, 수신자는 자기 속도에 맞춰 브로커로부터 차근차근 메시지를 가져와 처리하면 그만이다. 이러한 특징 덕분에 대용량 트래픽 폭주 상황에서도 트래픽을 완충해 주는 훌륭한 댐 역할을 수행한다.(...)
2. 동기식 HTTP의 한계와 비동기 큐의 구원
2.1. 주문 서버가 터졌는데 결제 서버도 죽어야 하나?
우리가 자주 쓰는 이커머스 쇼핑몰의 아키텍처를 예로 들어보자. 유저가 '주문' 버튼을 누르면 서버는 주문 처리, 결제, 재고 차감, 이메일/알림 발송 등 수십 가지 후속 작업을 수행해야 한다.
- 동기식 처리: 이메일 전송 서버가 일시적인 네트워크 장애로 응답이 10초간 밀리면, 전체 주문 API의 응답 속도도 10초 넘게 지연된다. 유저는 화가 나서 뒤로 가기를 연타하고 주문 시스템 전체가 마비된다.
이메일 안 갔다고 물건도 안 팔 기세다. - 비동기식 브로커 도입: 주문 서버는 '주문 완료' 메시지를 메시지 브로커에 던지고 유저에게는 즉시 "주문 완료!" 화면을 띄워 돌려보낸다. 결제, 재고, 이메일 발송 서버는 메시지 브로커를 구독(Subscribe)하고 있다가, 자기 페이스에 맞춰 메시지를 한 장씩 꺼내서 실행한다. 중간에 이메일 서버가 뻗더라도 브로커 내부 큐에 이메일 발송 대기 메시지들이 차곡차곡 안전하게 쌓여 있으므로, 이메일 서버를 복구하여 다시 켜는 순간 아무 일 없었다는 듯 밀린 이메일 배달이 완료된다.1
3. RabbitMQ vs Apache Kafka: 메시지 배달부들의 세기적 혈투
3.1. 패러다임의 결정적 차이
메시지 브로커 시장을 양분하고 있는 두 거인, RabbitMQ와 Apache Kafka는 메시지를 다루는 철학부터가 완전히 극단적으로 다르다.
| 비교 항목 | RabbitMQ | Apache Kafka |
|---|---|---|
| 제품 성격 | 전통적인 메시지 브로커 (Message Queue) | 분산 이벤트 스트리밍 플랫폼 (Distributed Log) |
| 메시지 소비 | 메시지를 소비하는 순간 큐에서 삭제됨 | 메시지를 소비해도 파일 로그에 유지됨 (보존 기간 설정) |
| 메시지 라우팅 | Exchange를 통한 복잡하고 유연한 라우팅 가능 | 파티션(Partition)을 통한 순차적 스트림 배포 중심 |
| 주 사용처 | 고도로 유연한 라우팅과 작업 큐, 트랜잭션 보장 | 대용량 실시간 로그 수집, 이벤트 스트림, 데이터 분석 |
| 주요 특징 | 스마트 브로커 & 바보 컨슈머 (브로커가 모든 상태 관리) | 바보 브로커 & 스마트 컨슈머 (컨슈머가 오프셋으로 직접 관리) |
RabbitMQ는 메시지가 안전하게 전송되고 소비되는지를 세밀하게 관리(Acknowledgement)하며, 소비자가 받아 가는 즉시 메모리에서 날려버려 큐를 가볍게 유지한다. 반면 Kafka는 메시지를 디스크의 파일 로그 형태로 순차 저장해 두고 건드리지 않는다. 소비자가 자기가 어디까지 읽었는지 가리키는 포인터(Offset)만 들고 다니며 직접 알아서 데이터를 퍼가기 때문에, 초당 수백만 건의 미친 트래픽도 거뜬히 버티는 괴력을 자랑한다.(...)2
4. 관련 밈 및 드립
4.1. Exactly Once(정확히 한 번) 배송의 환상
분산 시스템 교과서에 등장하는 단골 논쟁 주제다. 분산 네트워크 환경에서 메시지를 '적어도 한 번(At Least Once)' 혹은 '최대 한 번(At Most Once)' 배달하는 것은 쉽다. 그러나 네트워크 순단이나 타임아웃 상황 속에서도 오직 '정확히 한 번(Exactly Once)'만 완벽히 배달하는 것은 사실상 우주적 불가능에 가깝다. 카프카 등이 정교한 트랜잭션 기능으로 이를 흉내 내지만, 대다수 실무에서는 수신자 측에서 '멱등성(Idempotency)' 설계(이미 처리된 유니크 ID면 씹기)를 곁들여서 대처하며, 이를 간과했다가 결제 메시지가 두 번 도달해 이중 결제가 터져서 단체 시말서를 쓰는 눈물겨운 현장이 종종 발각된다.
5. 여담
- Dead Letter Queue (DLQ)의 아늑함: 메시지를 꺼내서 처리하다가 에러가 계속 나서 처리가 불가능한 골치 아픈 메시지는 버리지 않고 '죽은 편지 보관소'인 DLQ로 유배 보낸다. 개발자들은 나중에 퇴근해서 DLQ에 쌓인 영혼 없는 편지들을 한 장씩 열어보며 무엇이 에러를 일으켰는지 디버깅하는 우아한 저녁 시간을 갖게 된다.
- Redis의 큐 아르바이트: 레디스는 원래 인메모리 캐시지만, Pub/Sub 기능이나 List 데이터 타입을 제공해 주기 때문에 가벼운 메시지 브로커 대용으로 요긴하게 쓰인다. 단, 메모리 기반이므로 레디스가 꺼지면 큐에 대기 중이던 메시지도 다 같이 연기처럼 사라지는 파멸적 결말이 기다리고 있으니 중요 결제 정보 같은 건 절대로 넣으면 안 된다.
- RabbitMQ의 성난 토끼: RabbitMQ는 기본적으로 Erlang(얼랭)이라는 아주 기괴하고 마이너한 언어로 작성되었다. 동시성 처리가 기가 막히게 훌륭한 언어지만, 실무에서 래빗MQ에 심각한 에러가 나서 내부 소스코드를 뜯어봐야 하는 순간 Erlang 코드를 목격한 백엔드 개발자들은 다 같이 집단 멘붕에 빠진다.