Message Broker: RabbitMQ vs Kafka
💡 동기적(Synchronous) 방식은 한 쪽이 메시지를 보낼 때, 다른 쪽이 이를 수신해야 함을 의미한다. 즉, 마치 전화처럼 한 쪽이 말할 때 다른 쪽이 듣고 있어야 한다는 것이다.
동기 방식을 사용할 경우, 클라이언트가 서버에 데이터를 전달하는 시점에 서버가 꺼져 있거나 과부하 등의 문제가 발생하면 해당 데이터는 유실될 수 있다. 반면 비동기(Asynchronous) 방식으로 메시지를 전송하면 이러한 문제는 해결되지만, 수신 측에 정상적으로 전달되지 않을 가능성이 존재한다. 즉, 메시지의 전달이 보장되지 않는 문제가 발생할 수 있다. 따라서 수신 측 서버가 다시 활성화되더라도 당시의 메시지는 사라질 수 있다.
메시지 브로커(Message Broker)가 없으면 송신 및 수신 서버가 모두 정상적으로 작동해야만 메시지 전송이 정상적으로 동작하게 된다. 또한, 문제가 없는 상황에서도 메시지 전달은 보장되어야만 한다. 메시지 브로커가 없는 경우, 송신자와 수신자가 직접 재시도 로직이나 메시지 저장소를 관리해야 한다. 하지만 이러한 메커니즘을 구현하기는 쉽지 않을 뿐더러 유지보수도 어렵다.
Message Broker는 왜 필요할까?
- 송신자와 수신자 간의 의존성 해결 : 두 시스템이 직접 연결되지 않아도 통신 가능
- 메시지 전달 보장 : 수신자가 일시적으로 중단되어도 메시지 유실 방식
- 시스템 간의 결합도(Coupling) 감소 : 각 시스템이 독립적으로 개발/운영 가능
- 비동기 통신 지원 : 송신자는 수신자의 응답을 기다릴 필요 없음
- 복잡한 메시지 라우팅 및 처리 : 메시지를 조건에 따라 다양한 목적지로 전달
- 장애 복구 및 재처리 지원 : 장애 발생 시 메시지 재전송 가능
- 구현 및 유지보수의 간소화 : 복잡한 메시징 로직을 중앙화하여 관리
Message Broker는 분산 시스템이나 MSA(Microservice Architecture) 환경에서 안정적이고 효율적인 메시지 전달을 보장한다. Message Broker 방식은 Producer(송신자)와 Consumer(수신자)로 구성되며, 각각 별개의 서버에서 운영된다. 오늘은 가장 대표적인 Message Broker인 RabbitMQ와 Kafka에 대해 알아보고, 비교해보자.
RabbitMQ와 Kafka의 기본 아키텍처
RabbitMQ 아키텍처
RabbitMQ는 다음과 같은 핵심 컴포넌트로 구성된다.
- Producer : 메시지를 생성하여 RabbitMQ로 전송
- Exchange : 메시지를 받아 라우팅 규칙에 따라 적절한 큐로 전달
- Queue : 메시지가 Consumer에게 전달되기 전까지 저장되는 버퍼
- Binding : Exchange와 Queue를 연결하는 규칙
- Consumer : Queue에서 메시지를 수신하여 처리
Kafka 아키텍처
Kafka는 다음과 같은 핵심 컴포넌트로 구성된다.
- Producer : 메시지(레코드)를 생성하여 Kafka(토픽 이름)로 전송
- Topic : 메시지가 저장되는 카테고리 (Producer와 Consumer들이 카프카로 보낸 자신들의 메시지를 구분하기 위한 교유의 이름)
- Partition : 각 Topic이 분할되는 단위, 병렬 처리를 위한 기본 단위 (병렬 처리가 가능하도록 토픽을 나눌 수 있고, 많은 양의 메시지 처리를 위해 파티션의 수를 늘려줄 수 있다)
- Broker : Kafka 서버, 메시지를 저장하고 관리
- Consumer : Topic에서 메시지를 구독하여 처리
- Consumer Group : 여러 Consumer를 그룹화하여 병렬 처리
- ZooKeeper/KRaft : 클러스터 관리 및 메타데이터 저장 (최신 버전은 KRaft로 전환 중) 분산 애플리케이션을 위한 코디네이션 시스템으로, 분산된 애플리케이션의 정보를 중앙에 집중하고 구성 관리, 그룹, 네이밍, 동기화 등의 서비스를 수행한다.
메시지 저장 및 처리 방식
RabbitMQ : Smart Broker, Dumb Consumer
- 큐(Queue) 기반 저장 방식을 사용한다.
- Producer가 보낸 순서대로 메시지를 저장하고, Consumer가 메시지를 받아가면 Queue에서 제거된다.
- 메시지는 주로 메모리에 저장되어 처리가 빠르지만, 유실의 위험이 있다.
- 디스크에도 저장이 가능하긴 하다(Persistent Queue)
- 메시지 지속성(message durability)을 설정할 수 있는데, 지속성이 설정된 큐(durable queue)와 메시지(persistent message)는 디스크에 저장되어 RabbitMQ 서버가 재시작되더라도 메시지가 유지된다. 다만 디스크 I/O가 발생하므로 성능 트레이드오프가 있다.
- RabbitMQ에서 Broker는 Producer로부터 메시지를 받아서 저장할 뿐만 아니라, Consumer가 메시지를 받아갈 때마다 이를 Queue에서 제거하는 일도 담당한다. 또한 메시지 라우팅, 우선순위 관리, 메시지 TTL(Time-To-Live) 설정 등 복잡한 로직을 처리한다.
- 반면 Consumer는 다른 것을 고려할 필요 없이 Broker에게 메시지를 요청하기만 하면 된다.
Broker는 메시지를 저장하고 제거하는 일을 담당하기 때문에 Smart Broker로,
Consumer는 단순히 메시지를 요청하기만 하면 되기 때문에 Dumb Consumer로 묘사되기도 한다.
Kafka : Dumb Broker, Smart Consumer
- 로그(Log) 기반 저장 방식을 사용한다.
- 메시지는 파티션(Partition)이라는 단위로 분할되어 저장된다.
- Consumer가 메시지를 받아가도 로그에서 삭제되지 않고, 설정된 보존 기간 동안 유지된다.
- 각 메시지는 배열의 요소들이 index를 갖는 것처럼 각각의 오프셋(Offset)을 가지며, Consumer가 자신의 마지막 오프셋을 기억함으로써 중복 없이 다음 메시지를 요청하게 된다.
- 필요에 따라 Consumer가 특정 메시지를 여러 번 받아갈 수도 있고, 장애가 발생했을 시 문제가 있었던 부분부터 다시 받아갈 수 있다.
- 디스크(Disk)에 순차적으로 저장하여 데이터 유실 위험이 적고, 순차적 I/O와 페이지 캐시를 활용하여 디스크 기반임에도 높은 성능을 제공한다.
Broker는 Consumer에 요청에 따른 작업이 필요 없기 때문에 Dumb Broker,
Consumer는 마지막 메시지의 오프셋을 기억해야 하기 때문에 Smart Consumer로 묘사되기도 한다.
순차 처리 비교
RabbitMQ의 순차 처리
- 기본적으로 FIFO(First In First Out) 방식으로 메시지를 처리한다.
- 단일 큐 내에서는 메시지 순서가 보장된다.
- 하나의 큐에 여러 Consumer가 연결되면, 라운드 로빈 방식으로 메시지를 분배하므로 순서가 보장되지 않을 수 있다.
- 순서 보장이 필요한 경우, 각 Consumer 마다 별도의 큐를 사용하거나,
prefetch
설정을 1로 해야한다.
Kafka의 순차 처리
- 파티션 단위로 순서를 보장한다.
- 하나의 파티션은 항상 하나의 Consumer (동일 Consumer Group 내)만 처리할 수 있어 순서가 보장된다.
- 메시지가 디스크에 저장되어 있어, 장애 발생 시에도 순서 유지가 가능하다.
- 오프셋을 통해 특정 시점부터 재처리가 가능하다.
- 파티션이 여러 개일 경우, 파티션 간의 순서는 보장되지 않는다.
주요 특징 비교
저장 방식
- RabbitMQ: 메모리 기반 (디스크에도 저장 가능하지만 주로 메모리 사용)
- Kafka: 항상 디스크에 저장, 디스크 기반임에도 순차적 I/O와 페이지 캐시 최적화로 높은 성능을 제공하며, 서버 재시작 후에도 데이터가 유지도니다.
확장성
- RabbitMQ: 큐 단위로 확장, 여러 consumer 사용 시 순서 보장이 어려울 수 있음
- 클러스터링이 가능하지만, 모든 노드가 모든 큐의 메타데이터를 복제하므로 대규모 확장에 제한이 있음
- Kafka: 파티션 단위로 확장하면서도 파티션 내 순서를 보장
- 수평적 화장이 용이 : 브로커를 추가하는 것만으로 처리량을 선형적으로 증가시킬 수 있음
- 클러스터 구성을 통한 고가용성 확보
- 파티션이 브로커 간에 자동으로 분산되어 부하 균형을 유지
재처리
- RabbitMQ: 한번 처리된 메시지(acknowledged)는 기본적으로 제거됨
- 메시지 재처리를 위해서는 Consumer가 명시적으로 nack/reject를 보내고 requeue 옵션을 사용해야 한다.
- Dead Letter Exchange를 통해 처리하지 못한 메시지를 다른 큐로 보낼 수 있지만, 원본 순서는 유지되지 않는다.
- 장애 복구 시 처리 중이던 메시지만 재처리 가능하며, 이미 처리된 메시지는 복구할 수 없다.
- Kafka: 설정된 기간 동안 모든 메시지를 보관한다. (기본 7일, 설정 가능)
- Consumer는 자신의 오프셋을 관리하므로, 언제든지 원하는 시점으로 되돌아가 메시지를 재처리할 수 있다.
- 장애 발생 시 마지막으로 커밋된 오프셋부터 자동으로 재처리가 가능
- 시스템 업그레이드나 버그 수정 후 과거 데이터를 다시 처리해야 할 때 매우 유용
따라서 단순히 순차 처리만 필요하다면 둘 다 사용 가능하지만, 대규모 처리와 장애 복구가 중요하다면 Kafka가 더 적합하다. 특히 데이터 볼륨이 크고, 확장성과 내구성이 중요한 경우 Kafka의 장점이 더욱 두드러진다.
지표 | RabbitMQ | Kafka |
---|---|---|
최대 처리량 | 초당 수만 건 | 초당 수백만 건 |
지연 시간 | 밀리초 단위(더 짧음) | 밀리초 ~ 초 단위 |
메시지 크기 | 소형~중형 메시지에 최적화 | 대용량 처리에 최적화 |
확장성 | 수직적 확장에 유리 | 수평적 확장에 유리 |
적합한 사용 사례
RabbitMQ
- 작업 분산 처리
- 이미지 처리와 같은, 시간이 걸리는 작업
- 이메일 발송, 알림 전송 등의 비동기 작업
- 트랜잭션이 중요한 시스템
- 금융 거래, 주문 처리 등 정확성이 중요한 시스템
- 메시지 손실이 허용되지 않는 업무
- 복잡한 라우팅이 필요한 시스템
- 다양한 조건에 따른 메시지 분배
- 우선순위가 다른 작업 처리
Kafka
- 대용량 실시간 스트리밍
- 사용자 활동 로그 수집 및 분석
- 이벤트 소싱 아키텍처
- 시스템 상태 변화를 이벤트로 기록
- 이벤트 기반 마이크로서비스 통신
- 데이터 파이프라인
- ETL(Extract, Transform, Load) 처리
- 실시간 데이터 분석 파이프라인
- 로그 집계 및 분석
- 분산 시스템의 로그 중앙화
- 모니터링 데이터 수집 및 처리
💡 RabbitMQ는 우편물을 받아서 수취인에게 배달하는 우체국, Kafka는 생산자가 게시하는 다양한 장르의 메시지를 진열대에 정리하는 도서관과 비슷하다.
현대 대형 엔터프라이즈 환경에서는 MSA가 많이 채택되고 있으며, 이때 확장성(Scalability)과 성능(Performance) 측면에서 Kafka가 뛰어난 장점을 갖추고 있다. 다만, 이러한 기술적 선택을 결정할 때는 단순히 기술의 우수성만이 아니라, 팀 내의 충분한 사용 경험(기술 숙련도)과 적절한 인적 자원의 보유 여부도 중요한 고려 사항으로 작용한다. 기술의 도입은 이를 효과적으로 운영하고 관리할 수 있는 조직의 역량과도 직결되기 때문이다.
Reference