메시지 큐, 고르라니까 뭘 골라야 해
얼마 전 팀 내 아키텍처 논의를 하다가 살짝 당황한 적이 있다.
새로 구축하는 서비스에서 이벤트 기반으로 서비스 간 통신을 해야 하는데, 어떤 메시지 큐를 도입할지 이야기가 나온 것이다. 누구는 Kafka를 써야 한다고 하고, 누구는 RabbitMQ면 충분하다고 한다. 근데 막상 "왜?"를 물어보면 대부분 "그냥... Kafka가 좋다고 들어서"라든지, "RabbitMQ가 더 간단하다던데" 수준의 대답이 돌아왔다.
나도 크게 다르지 않았다. 둘 다 써봤는데 정작 차이를 명확히 설명하라고 하면 말문이 막혔다. 그래서 이번 기회에 제대로 정리해봤다.

그 전에, 왜 메시지 큐가 필요한 거야?
Kafka든 RabbitMQ든 왜 쓰는지 먼저 알아야 한다.
가장 단순한 서비스 간 통신은 직접 HTTP 요청이다. 주문 서비스가 재고 서비스에 HTTP로 물어보고, 응답 받고, 다음 로직을 실행하는 방식. 근데 이게 문제가 생기는 건 재고 서비스가 느리거나, 아예 다운되거나, 갑자기 트래픽이 폭증했을 때다.
주문 서비스 입장에서는 재고 서비스가 응답할 때까지 계속 기다리거나, 타임아웃이 나거나, 요청 자체를 버려야 하는 상황이 된다. 플래시 세일 같은 이벤트 때 갑자기 주문이 수만 건씩 들어온다고 생각해보자. 재고 서비스가 버텨줄 수 있을까?
메시지 큐는 이 문제를 해결한다. 주문 서비스는 재고 서비스에 직접 요청하는 대신, 메시지를 큐에 던져놓고 바로 응답한다. 재고 서비스는 자기가 처리할 수 있는 속도로 큐에서 메시지를 가져다 처리한다. 폭증하는 트래픽은 큐가 흡수한다. 서비스 간 결합도가 낮아지고, 각자의 페이스로 처리가 가능해진다.

Kafka와 RabbitMQ는 이 역할을 하는 도구들이다. 그런데 내부 동작 방식이 근본적으로 다르다.
RabbitMQ: 스마트한 브로커, 단순한 컨슈머
RabbitMQ는 전통적인 메시지 브로커다. 개념적으로는 굉장히 직관적이다.
프로듀서가 메시지를 브로커에게 보내면, 브로커가 라우팅 룰에 따라 어느 큐로 보낼지 판단하고, 컨슈머가 그 큐에서 메시지를 가져다 처리한다. 컨슈머가 처리 완료 신호(ack)를 보내면 RabbitMQ는 그 메시지를 삭제한다.
브로커가 굉장히 많은 일을 한다. 메시지 라우팅, 어떤 메시지가 전달됐는지 추적, 실패 시 재시도, 처리 실패가 반복되면 자동으로 Dead Letter Queue로 이동. 컨슈머 입장에서는 그냥 큐에 연결해서 메시지 받아 처리하고 ack만 보내면 끝이다.
이걸 한 문장으로 표현하면 "스마트한 브로커, 단순한 컨슈머".

이 모델이 잘 맞는 케이스는 태스크 기반 워크로드다. 이메일 발송, 결제 처리, 이미지 리사이징. 할 일이 생기면 큐에 넣고, 누군가 꺼내서 처리하고, 처리 완료되면 사라지는 패턴.
인스타그램이 사진 업로드 후 리사이징/필터 처리에 RabbitMQ를 쓴다고 알려져 있고, 레딧도 댓글 트리 구성이나 카르마 계산 같은 백그라운드 작업에 쓰고 있다고 한다.
Kafka: 단순한 브로커, 스마트한 컨슈머
Kafka는 완전히 다른 패러다임이다.
Kafka는 메시지 브로커가 아니라 분산 로그(Distributed Append-Only Log) 에 가깝다. 프로듀서가 메시지를 보내면 Kafka는 토픽에 그냥 추가한다. 그리고 메시지는 컨슈머가 읽어도 사라지지 않는다. 설정한 보존 기간(시간, 용량)이 지날 때까지 그냥 로그에 남아있다.
컨슈머는 로그에서 어디까지 읽었는지 본인이 직접 기억한다. 이걸 오프셋(Offset) 이라고 한다. 500번째 메시지까지 읽었으면, 컨슈머가 "나 지금 500번"이라는 걸 기록해두는 것이다. 컨슈머가 죽었다가 살아나도 오프셋을 보고 501번부터 다시 읽는다. 1시간 전 메시지를 다시 읽고 싶으면? 오프셋을 뒤로 되감으면 된다.
이걸 한 문장으로 표현하면 "단순한 브로커, 스마트한 컨슈머".
이 모델의 핵심 강점은 두 가지다.
- 여러 컨슈머 그룹이 같은 스트림을 독립적으로 읽을 수 있다. 분석팀이 이벤트를 읽는 동시에, 알림 시스템도 같은 이벤트를 읽고, 6개월 뒤에 새로 만든 서비스도 처음부터 다 읽어서 백필(backfill) 할 수 있다.
- 메시지가 영속성(durability)을 가진다. 모든 이벤트의 히스토리가 보존된다.
RabbitMQ는 메시지가 흘러 지나가는 파이프고, Kafka는 메시지가 쌓이는 로그다. 이 차이가 모든 것을 결정한다.

기술적 차이 비교
순서 보장
RabbitMQ는 단일 컨슈머 기준으로는 메시지 순서가 완벽하게 보장된다. 근데 컨슈머 여러 개가 붙으면 병렬 처리되면서 순서가 뒤섞일 수 있다. 처리량 vs 순서, 트레이드오프다.
Kafka는 토픽을 파티션으로 나눈다. 순서는 파티션 내에서만 보장된다. 파티션 키를 이용해서 특정 사용자의 이벤트는 항상 같은 파티션으로 몰 수 있다. 예를 들면 고객 A의 모든 주문은 파티션 1에, 고객 B는 파티션 2에. 각 고객 내에서는 순서가 보장되고, 파티션 간 병렬 처리도 가능하다.
전체 글로벌 순서가 필요하면 RabbitMQ, 엔티티 단위 순서 + 병렬 처리가 필요하면 Kafka.
처리량과 레이턴시
| RabbitMQ | Kafka | |
|---|---|---|
| 처리량 | 초당 4,000 ~ 10,000 건 | 초당 100만 건 이상 |
| 레이턴시 | 1 ~ 5ms | 5 ~ 50ms |
RabbitMQ는 레이턴시가 낮다. 브로커가 메시지를 컨슈머에게 푸시하는 방식이라 빠르게 전달된다. 근데 메시지마다 라우팅, 전달 추적, ack 관리 등 브로커가 하는 일이 많아서 처리량이 올라갈수록 부담이 커진다.
Kafka는 기본 레이턴시는 더 높다. 컨슈머가 배치로 풀링하는 방식이기 때문이다. 대신 브로커가 하는 일이 단순하다. 그냥 로그에 추가하고, 컨슈머가 알아서 읽어가게 두면 된다. 그래서 부하가 엄청나게 올라가도 레이턴시가 비교적 일정하게 유지된다. 100배 이상의 처리량 차이가 나는 이유다.
메시지 전달 보장
- At-most-once: 한 번만 전송, 실패해도 재시도 없음. 빠르지만 유실 가능.
- At-least-once: 실패하면 재시도. 유실 없지만 중복 가능.
- Exactly-once: 정확히 한 번만. 이상적이지만 복잡함.
RabbitMQ와 Kafka 모두 at-least-once를 지원한다. 대부분의 현업 애플리케이션에서 필요한 건 이거다.
Kafka는 exactly-once도 지원한다. 근데 여기서 함정이 있다. Kafka의 exactly-once는 같은 Kafka 클러스터 내에서 Kafka 토픽끼리만 동작한다. 데이터베이스에 쓰거나, 외부 API를 호출하거나, 클러스터가 달라지면 at-least-once로 돌아간다. 실제 비즈니스 로직에서 정확히 한 번 보장이 필요한 케이스라면, 어차피 컨슈머를 멱등성(idempotent)하게 만들어야 한다. "Kafka는 exactly-once 된다더라"고 선택했다가 낭패 보는 경우가 있으니 주의할 것.
운영 복잡도
RabbitMQ는 단일 바이너리에 관리 UI도 내장돼 있다. 클러스터링도 비교적 단순하고 러닝 커브가 낮다. 소규모 팀이 몇 개 큐만 운영할 때는 정말 접근하기 쉽다.
Kafka는 역사적으로 ZooKeeper가 필요했는데, 최근 버전(KRaft 모드)에서는 ZooKeeper 의존성이 제거됐다. 그렇다해도 파티션 리밸런싱, 브로커 장애 처리, 토픽 설정, 컨슈머 그룹 조율 등 신경 써야 할 게 훨씬 많다.
다만 Confluent Cloud, Amazon MSK, Azure Event Hubs 같은 매니지드 서비스를 쓰면 이 운영 부담을 상당 부분 덜 수 있다. Kafka를 도입할 생각이라면, 전담 인프라 팀이 없다면 매니지드 서비스를 강력하게 권장한다.
그래서 뭘 써야 하나
핵심을 한 줄로 요약하면 이렇다.
RabbitMQ = 일 처리 큐. Kafka = 이벤트 로그.
RabbitMQ를 선택할 때:
- 이메일 발송, 결제 처리, 이미지 변환 같은 태스크 처리가 목적일 때
- 특정 라우팅 규칙에 따라 적절한 워커에게 작업을 분배해야 할 때
- 대용량이 아니라 적당한 규모에서 낮은 레이턴시가 중요할 때
- 인프라 단순함이 중요하고 팀이 크지 않을 때
Kafka를 선택할 때:
- 여러 시스템이 같은 이벤트 스트림을 독립적으로 읽어야 할 때 (분석, 알림, 감사 로그 등)
- 과거 데이터를 재처리(replay) 해야 할 일이 있을 때
- 초당 수십만~수백만 건의 대용량 처리가 필요할 때
- 모든 이벤트의 영속적인 히스토리가 필요할 때
Netflix는 추천 시스템과 빌링에 Kafka로 하루에 페타바이트 규모 데이터를 처리하고, Uber는 실시간 가격 책정과 사기 탐지에 Kafka를 쓴다. LinkedIn은 Kafka를 직접 만든 회사고 지금도 피드와 메시징에 쓰고 있다.
둘 다 같이 쓰는 경우도 있다
사실 현실에서 규모가 어느 정도 커지면 두 가지를 병용하는 아키텍처도 자주 보인다.
Kafka를 이벤트 백본으로 쓰고, 그 이벤트가 트리거하는 실제 태스크 처리는 RabbitMQ로 하는 방식이다. 예를 들면:
- 주문 이벤트는 Kafka로 전사 시스템에 스트리밍
- 그 이벤트를 받아서 이메일 발송, 재고 차감 같은 작업은 RabbitMQ 워커가 처리
Kafka가 이벤트의 출처이자 히스토리고, RabbitMQ는 그 이벤트로 인해 발생하는 실제 작업을 처리하는 구조다.

마무리
처음에 팀 회의에서 뭘 써야 할지 몰라 당황했던 건, 사실 두 도구의 철학적 차이를 제대로 이해하지 못했기 때문이었다.
RabbitMQ는 메시지가 흘러가는 파이프고, Kafka는 메시지가 쌓이는 로그다.
이 문장 하나를 이해하면, 나머지 기술적 차이들(처리량, 순서 보장, 전달 보장, 운영 복잡도)이 모두 자연스럽게 따라온다. 도입할 때 "Kafka가 좋다더라", "RabbitMQ가 유명하더라"가 아니라, 내 서비스가 태스크 처리가 필요한지 이벤트 스트리밍이 필요한지를 먼저 명확히 하는 게 선택의 출발점이다.
참고 자료
- Hello Interview - Kafka vs RabbitMQ (YouTube)
- RabbitMQ 공식 문서
- Apache Kafka 공식 문서
- Confluent - Kafka vs RabbitMQ
#Kafka #RabbitMQ #메시지큐 #MessageQueue #분산시스템 #백엔드 #아키텍처 #개발자 #시스템설계 #이벤트드리븐 #EventDriven #KafkavsRabbitMQ
'프로그래밍 > Back-End' 카테고리의 다른 글
| DNS 레코드 종류 완벽 정리 – A레코드, CNAME, TXT, MX 뭐가 다른 거야? (0) | 2026.04.07 |
|---|---|
| 회의 때 처음 들은 '클릭하우스(ClickHouse)', 대체 뭐길래? (1) | 2025.09.30 |
| DI 프레임워크를 사용하여 동적으로 빈을 생성하는 방법 (0) | 2022.01.27 |
| Spring expression 언어: SpEL에서 맵 조회를 위한 키로 변수 사용 (0) | 2022.01.27 |
| 네이티브 이미지에 대한 Spring Boot 빌드 이미지 실패 (0) | 2022.01.20 |