MSA(마이크로서비스) 아키텍처로의 전환 과정에서 우리는 필연적으로 서비스 간의 결합도를 낮추기 위해 비동기 통신을 도입하게 됩니다. 초기 프로젝트 단계에서는 단순한 메시지 큐만으로도 충분해 보였지만, 트래픽이 초당 수만 건(TPS)으로 급증하고 데이터의 영속성이 중요해지면서 시스템은 한계에 봉착했습니다. 단순한 라우팅 기능이 필요했던 주문 서비스와, 대량의 로그 데이터를 실시간으로 집계해야 했던 분석 서비스 사이에서 Kafka와 RabbitMQ 중 무엇을 선택해야 할지는 단순한 취향의 문제가 아니라 시스템의 생존이 걸린 아키텍처 결정이었습니다. 이 글에서는 두 메시지 브로커의 내부 동작 원리를 기반으로 어떤 상황에서 무엇을 선택해야 하는지, 실제 운영 경험을 바탕으로 분석합니다.
Deep Dive: Smart Broker vs Smart Consumer
두 솔루션의 가장 큰 차이점은 데이터 처리의 책임이 누구에게 있느냐는 철학적 차이에서 비롯됩니다. 이를 이해하지 못하고 단순히 성능 수치만 비교하는 것은 무의미합니다.
RabbitMQ: Smart Broker, Dumb Consumer
RabbitMQ는 AMQP(Advanced Message Queuing Protocol)를 구현한 전통적인 메시지 브로커입니다. 브로커(Server)가 메시지의 라우팅, 필터링, 전달 확인(Ack) 등 복잡한 로직을 모두 담당합니다. 컨슈머는 단순히 들어오는 메시지를 처리하기만 하면 됩니다.
Kafka: Dumb Broker, Smart Consumer
반면 Kafka는 분산 커밋 로그(Commit Log) 시스템에 가깝습니다. 브로커는 단순히 메시지를 디스크에 순차적으로 기록(Append Only)할 뿐, 컨슈머가 어디까지 읽었는지 추적하지 않습니다. 오프셋(Offset) 관리는 전적으로 컨슈머의 책임입니다.
The Solution: 시나리오별 구현 전략
실제 운영 환경에서 두 브로커의 성능을 극대화하기 위한 설정은 완전히 다릅니다. 아래는 대용량 처리를 위해 각 브로커를 튜닝할 때 사용했던 핵심 설정 코드입니다.
1. Kafka: Batch Processing을 통한 처리량 극대화
Kafka의 성능은 배치 처리에 달려 있습니다. 네트워크 왕복 비용을 줄이기 위해 linger.ms와 batch.size를 조정하여 처리량을 높입니다.
// Kafka Producer Configuration (Java)
Properties props = new Properties();
props.put("bootstrap.servers", "kafka-broker:9092");
// 중요: 0이 아닌 값을 주어 브로커가 배치를 기다리게 함 (지연 시간 vs 처리량 트레이드오프)
props.put("linger.ms", 5);
// 배치 크기 설정 (기본값 16KB -> 64KB 상향)
props.put("batch.size", 65536);
// 압축을 사용하여 네트워크 대역폭 절약 (Producer 측 CPU 사용량 증가)
props.put("compression.type", "lz4");
// lz4는 속도와 압축률 균형이 우수함
// ACK 설정: 데이터 유실 방지를 위해 all 설정 권장
props.put("acks", "all");
2. RabbitMQ: Prefetch Count를 통한 Backpressure 제어
RabbitMQ는 Push 방식이므로 컨슈머가 처리할 수 있는 양보다 많은 메시지가 들어오면 장애가 발생합니다. basicQos 설정을 통해 한 번에 가져올 메시지 수를 제한해야 합니다.
// RabbitMQ Consumer Configuration (Java)
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("rabbitmq-broker");
Channel channel = connection.createChannel();
boolean durable = true; // 브로커 재시작 시 큐 보존
channel.queueDeclare("task_queue", durable, false, false, null);
// 중요: Prefetch Count 설정 (Fair Dispatch)
// 컨슈머가 메시지 처리를 완료(ack)하기 전까지는 1개 이상의 메시지를 보내지 않음
int prefetchCount = 1;
channel.basicQos(prefetchCount);
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
// 비즈니스 로직 처리
doWork(body);
} finally {
// 수동 ACK 전송 (Auto Ack는 위험함)
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
| 비교 항목 | RabbitMQ | Apache Kafka |
|---|---|---|
| 아키텍처 모델 | Push 모델 (Smart Broker) | Pull 모델 (Smart Consumer) |
| 메시지 보존 | 소비 후 즉시 삭제 (기본값) | 설정된 기간/크기만큼 디스크 보존 (이벤트 리플레이 가능) |
| 주요 용도 | 복잡한 라우팅, 작업 큐, 실시간 트랜잭션 처리 | 대용량 로그 수집, 스트림 처리, 데이터 파이프라인 |
| 처리량 (Throughput) | 약 4K ~ 10K msgs/sec (조건부) | 약 100K ~ 수백만 msgs/sec |
| 확장성 | 수직적 확장(Scale-up)이 유리 | 수평적 확장(Scale-out)이 매우 용이 |
Conclusion
Kafka와 RabbitMQ 중 하나를 선택하는 것은 '어떤 도구가 더 우수한가'가 아니라 '데이터를 어떻게 다룰 것인가'에 대한 결정입니다. 만약 이벤트 데이터의 순차적 처리가 중요하고, 하루 TB 단위의 데이터를 재처리(Replay)해야 한다면 Kafka가 유일한 대안입니다. 반면, 데이터의 양보다는 복잡한 라우팅 규칙이 필요하고, 메시지 처리의 즉시성과 개별 메시지의 상태 관리가 중요하다면 RabbitMQ가 훨씬 낮은 운영 비용으로 안정적인 서비스를 제공할 것입니다.
Post a Comment