Kafka vs RabbitMQ: 대용량 트래픽 환경에서의 아키텍처 병목과 해결 전략

MSA(마이크로서비스) 아키텍처로의 전환 과정에서 우리는 필연적으로 서비스 간의 결합도를 낮추기 위해 비동기 통신을 도입하게 됩니다. 초기 프로젝트 단계에서는 단순한 메시지 큐만으로도 충분해 보였지만, 트래픽이 초당 수만 건(TPS)으로 급증하고 데이터의 영속성이 중요해지면서 시스템은 한계에 봉착했습니다. 단순한 라우팅 기능이 필요했던 주문 서비스와, 대량의 로그 데이터를 실시간으로 집계해야 했던 분석 서비스 사이에서 KafkaRabbitMQ 중 무엇을 선택해야 할지는 단순한 취향의 문제가 아니라 시스템의 생존이 걸린 아키텍처 결정이었습니다. 이 글에서는 두 메시지 브로커의 내부 동작 원리를 기반으로 어떤 상황에서 무엇을 선택해야 하는지, 실제 운영 경험을 바탕으로 분석합니다.

Deep Dive: Smart Broker vs Smart Consumer

두 솔루션의 가장 큰 차이점은 데이터 처리의 책임이 누구에게 있느냐는 철학적 차이에서 비롯됩니다. 이를 이해하지 못하고 단순히 성능 수치만 비교하는 것은 무의미합니다.

RabbitMQ: Smart Broker, Dumb Consumer

RabbitMQ는 AMQP(Advanced Message Queuing Protocol)를 구현한 전통적인 메시지 브로커입니다. 브로커(Server)가 메시지의 라우팅, 필터링, 전달 확인(Ack) 등 복잡한 로직을 모두 담당합니다. 컨슈머는 단순히 들어오는 메시지를 처리하기만 하면 됩니다.

핵심 아키텍처: RabbitMQ는 Exchange와 Queue 바인딩을 통해 매우 유연한 라우팅 패턴(Direct, Topic, Fanout, Header)을 제공합니다. 이는 복잡한 비즈니스 로직 분기가 필요한 시스템에 최적화되어 있습니다.

Kafka: Dumb Broker, Smart Consumer

반면 Kafka는 분산 커밋 로그(Commit Log) 시스템에 가깝습니다. 브로커는 단순히 메시지를 디스크에 순차적으로 기록(Append Only)할 뿐, 컨슈머가 어디까지 읽었는지 추적하지 않습니다. 오프셋(Offset) 관리는 전적으로 컨슈머의 책임입니다.

성능 트레이드오프: Kafka는 메시지 삭제 오버헤드가 없고 디스크의 순차 I/O를 활용하기 때문에 압도적인 처리량(Throughput)을 보여줍니다. 하지만 컨슈머가 직접 파티션과 오프셋을 관리해야 하므로 클라이언트 구현 복잡도가 높습니다.

The Solution: 시나리오별 구현 전략

실제 운영 환경에서 두 브로커의 성능을 극대화하기 위한 설정은 완전히 다릅니다. 아래는 대용량 처리를 위해 각 브로커를 튜닝할 때 사용했던 핵심 설정 코드입니다.

1. Kafka: Batch Processing을 통한 처리량 극대화

Kafka의 성능은 배치 처리에 달려 있습니다. 네트워크 왕복 비용을 줄이기 위해 linger.msbatch.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)이 매우 용이
주의: RabbitMQ 클러스터링은 데이터 복제가 목적이지 처리량을 늘리기 위한 것이 아닙니다. 반면 Kafka는 파티션 단위로 분산 처리가 가능하므로 노드를 추가할수록 처리량이 선형적으로 증가합니다.

Conclusion

Kafka와 RabbitMQ 중 하나를 선택하는 것은 '어떤 도구가 더 우수한가'가 아니라 '데이터를 어떻게 다룰 것인가'에 대한 결정입니다. 만약 이벤트 데이터의 순차적 처리가 중요하고, 하루 TB 단위의 데이터를 재처리(Replay)해야 한다면 Kafka가 유일한 대안입니다. 반면, 데이터의 양보다는 복잡한 라우팅 규칙이 필요하고, 메시지 처리의 즉시성과 개별 메시지의 상태 관리가 중요하다면 RabbitMQ가 훨씬 낮은 운영 비용으로 안정적인 서비스를 제공할 것입니다.

Post a Comment