Post

RabbitMQ VS Kafka

RabbitMQ와 Kafka 의 차이점과 각 시스템의 특징을 비교하여, 적합한 사용 사례를 제시합니다.

RabbitMQ VS Kafka

[참고 자료]

서론

현대 서버 애플리케이션 구조상 메시징 시스템은 필연적이다.

로직을 분리해 응답 속도 향상, 안정성, 실행 보장, 다른 애플리케이션 서버와 통신 등 목적은 다양할 것이다. 그리고, 많은 개발팀들이 어떤 기술을 사용할 지 고민할것이다.

나 또한 “우리 팀은 왜 Kafka 가 아닌 RabbitMQ 를 사용할까?” 라는 궁금증에서 시작했고, 그 과정에서 나름대로의 답을 찾은거 같아서 공유하기 위해 작성했다.


RabbitMQ (Smart Broker & Dumb Consumer)

700

RabbitMQ는 AMQP(Advanced Message Queuing Protocol) 를 구현한 메시지 브로커이며, MQTT 프로토콜도 제공한다. 중심 아이디어는 브로커(RabbitMQ)가 라우팅, 메시지 보관 및 전달 로직의 대부분을 담당

AMQP? Advanced Message Queueing Protocol: 메시지 지향 미들웨어를 위한 표준 프로토콜 서로 다른 시스템, 애플리케이션이 메시지를 안정적으로 주고 받게 해주는 규약

  • 상호 운용성: 서로 다른 언어, 플랫폼으로 개발되어도 원활히 통신
  • 안정성, 신뢰성: 메시지를 유실되지 않고 안정적으로 전달되는 것을 보장 - 메시지 확인 (ACK), 메시지 영속성, 트랜잭션
  • 유연한 라우팅: Exchange 를 통해 라우팅 - 생산자는 Exchange 에 보내고, Exchange 가 설정 규칙에 따라 적절한 큐로 메시지 분배
  • Producer (생산자): 메시지를 생성하여 Exchange로 전송
  • Exchange (교환기): Producer로부터 받은 메시지를 어떤 Queue로 보낼지 결정하는 라우팅 규칙의 집합 (메시지 자체를 저장 X)
    • Direct Exchange: 라우팅 키가 정확히 일치하는 Queue에 메시지 전송 (유니캐스트)
    • Topic Exchange: 라우팅 키가 특정 패턴에 일치하는 Queue에 메시지 전송 (멀티캐스트)
    • Fanout Exchange: 자신에게 바인딩된 모든 Queue에 메시지를 전송 (브로드캐스트)
    • Headers Exchange: 메시지 헤더의 속성을 기반으로 라우팅
  • Queue (큐): 소비자가 메시지를 가져갈 때까지 메시지를 저장하는 버퍼
  • Binding (바인딩): Exchange와 Queue를 연결하는 규칙. ("이 Exchange는 라우팅 규칙에 따라 이 Queue로 메시지를 보내라")
  • Consumer (소비자): Queue에서 메시지를 가져와 처리

핵심 흐름: Producer → Exchange → (Binding Rule) → Queue → Consumer

스마트 브로커

  • 메시지 흐름 완벽 제어: 브로커가 Exchang & Binding 규칙에 따라 메시지 어디로 보낼지 판단 및 라우팅
  • 소비자 상태 추적: 컨슈머가 어떤 큐에 연결되어 있는지, 메시지를 제대로 처리했는지 계속 추적
  • 메시지를 소비자에게 전송: Push, 브로커가 컨슈머에게 메시지를 전달 - 컨슈머는 prefetch 로 자신이 감당할 수 있는 양만 조절
  • 다양한 부가 기능: Dead Letter Exchanges, 메시지 TTL, 우선순위 큐 등등 기능 제공

더미 컨슈머

  • 단순 역할 집중: 컨슈머는 지정된 큐 연결해 브로커가 주는 메시지를 받아 처리하고, 처리 완료 신호만 보냄
  • 라우팅 대해 알 필요 없음: 컨슈머는 메시지가 어떤 과정을 거쳐 자신의 큐에 도착하는지 고려할 필요 없음

=> 우체국(브로커)이 모든 편지를 분류하고 주소를 알려주며, 집배원(소비자)이 자신의 구역에 할당된 편지를 배달하는 시스템

Kafka (Dumb Broker & Smart Consumer)

Kafka는 분산 스트리밍 플랫폼이며, 메시지를 변경 불가능한 로그(immutable log)의 연속된 스트림으로 취급 브로커는 데이터를 저장하고 관리, 복잡한 라우팅 로직은 소비자가 담당

  • Broker (브로커): Kafka 서버 인스턴스. 메시지를 저장하고 관리하며, 여러 브로커가 모여 Cluster를 구성
    • Producer 로부터 메시지 수신, 오프셋 지정해 메시지를 디스크에 저장
    • 컨슈머의 파티션 읽기 요청에 응답하고, 디스크에 수록된 메시지 전송
    • 한 개는 클러스터의 컨트롤러 역할 수행 - 각 브로커에게 담당 파티션 할당, 브로커 정상 동작하는지 모니터링
  • Cluster (클러스터): 여러 브로커로 구성되어 데이터 복제, 장애 허용(fault tolerance), 고가용성을 제공
    • 클러스터 내 서버 브로커 추가하면, 메시지 수신 및 전달에 대한 처리량 증가
    • 시스템 전체 사용에 영향을 주지 않으며, 온라인 상태 수행 가능 ( 소규모 운영 하다가, 트래픽 양에 따라 대규모 늘릴 수 있는 장점 )

  • Topic (토픽): 메시지를 구분하기 위한 카테고리 또는 피드 이름. RabbitMQ의 Exchange와 유사한 역할이나, 메시지를 직접 저장
    • 일종의 DB 테이블, 파일 시스템의 폴더와 유사
    • 하나의 토픽은 여러 개의 파티션으로 구성
  • Partition (파티션): 추가만 가능한(append-only) 로그이며, 이를 통해 Topic의 데이터를 여러 브로커에 분산하여 저장하고 처리량을 높일 수 있습니다.
    • 한 파티션 내에서는 메시지의 순서가 보장됩니다. - 여러 파티션 간 순서 보장 X

  • Producer (생산자): 메시지(레코드)를 생성하여 특정 Topic으로 전송
  • Consumer (소비자): Topic에서 메시지를 가져와 처리
    • 하나 이상의 토픽 구독, 메시지 생성된 순서대로 읽음 - 파티션 단위로 오프셋 유지해 메시지 위지 파악 가능
  • Offset (오프셋): 파티션 내에서 각 메시지가 갖는 고유한 순번(ID)입니다. 소비자는 오프셋을 통해 어디까지 메시지를 읽었는지 추적하고 제어
    • Commit Offset: 컨슈머가 여기까지 오프셋을 처리했다는 것을 확인하는 오프셋
    • Current Offset: 컨슈머가 어디까지 메시지를 읽었는지는 것을 확인하는 오프셋
  • Consumer Group (소비자 그룹): 하나 이상의 소비자를 묶은 그룹. 하나의 Topic에 대해, 각 파티션은 소비자 그룹 내의 단 하나의 소비자에게만 할당
    • 각 컨슈머가 해당 토픽의 다른 파티션을 분담해서 메시지 읽을 수 있음
    • 컨슈머를 추가하면, 메시지 소비 성능 확장

하나의 토픽 내 파티션 개수보다 더 많은 컨슈머를 추가하는게 의미 없음

핵심 흐름: Producer → Topic (Partition) → Consumer (Consumer Group)

더미 브로커

  • 고성능 파일 저장소 역할에 집중: 브로커는 메시지를 받아서 토픽 파티션 끝에 빠르게 추가, 디스크에 저장
  • 메시지 상태 추적 X: 브로커는 어떤 소비자가 어떤 메시지를 읽었는지 고려 X, 그냥 설정된 보존 기간 동안 저장
  • 복잡한 라우팅 X: 메시지 받으면 생산자가 지정한 토픽 / 파티션에 저장, 브로커가 자체적 메시지 재분배 X

스마트 컨슈머

  • 읽을 위치를 직접 관리: 컨슈머가 자신이 토픽의 어떤 파티션에서 몇 번째 메시지까지 읽었는지 스스로 기록 & 관리
  • 데이터를 직접 당겨옴: Pull, 컨슈머가 브로커에게 능동적으로 요청해서 데이터를 가져옴
  • 파티션 할당 로직 담당: 소비자 그룹 내 어떤 파티션을 담당할지 결정 로직도 소비자가 속한 클라이언트 라이브러리에서 처리

=> 거대한 도서관 (브로커) 은 책 (메시지) 을 책장 (파티션) 에 계속 꽂아두기만 할 뿐 독자 (소비자) 가 직접 도서관 찾아와 자기가 읽을 부분 (오프셋) 을 기억하고, 다음 읽을 책도 꺼내가는 형태

Gemini 비유 좋다…!

흔한 오해들

두 개가 구조가 비슷하고, 성능만 차이 난다고 오해를 할 수 있는데 (또는, RabbitMQ 가 사용하지 않는 메시징 시스템이라던가…) 이 개념들을 명확히 이해해야 두 시스템의 아키텍처 철학 차이를 제대로 파악할 수 있다.

팬아웃: 하나의 메시지를 여러 독립적 소비자가 각각 동일한 복사본 받아 처리하는 패턴

1. Kafka의 ‘팬아웃’은 단순한 전파(Broadcast)가 아니다

가장 흔한 오해 중 하나는 Kafka는 Pub/Sub, RabbitMQ는 Work-Queue라는 이분법적 시각이다. 사실 Kafka는 이 두 가지 모델을 소비자 그룹(Consumer Group) 이라는 개념을 통해 우아하게 통합해준다고 한다.

  • 그룹 간(Inter-Group)에는 Pub/Sub (팬아웃/방송): 서로 다른 소비자 그룹은 같은 토픽을 구독하더라도 메시지 스트림 전체를 독립적으로 소비한다. EX) order-events라는 토픽이 있을 때, 재고 관리 서비스(그룹 A)와 데이터 분석 팀(그룹 B) 등 각각 별개의 소비자 그룹으로 구독이 가능하다. 이 경우, 두 그룹 모두 order-events의 모든 메시지를 처음부터 끝까지 받고 개별적으로 처리할 수 있다.

  • 그룹 내(Intra-Group)에서는 Work-Queue (분산 처리): 단, 하나의 같은 소비자 그룹 내에서는 이야기가 달라진다. 그룹에 속한 소비자가 토픽의 파티션(Partition)들을 나누어 처리한다. (하나의 파티션은 그룹 내 단 하나의 소비자에게만 할당) 만약 토픽에 4개의 파티션이 있고, 그룹에 4개의 소비자가 있다면, 각 소비자는 하나의 파티션을 전담하여 메시지를 처리한다. -> 이는 작업 부하를 분산하고 처리량을 높이는 ‘워크큐’ 모델과 동일하게 처리

핵심: Kafka는 소비자 그룹을 통해 서로 다른 시스템 간에는 데이터를 복제/방송하고, 단일 시스템 내에서는 작업을 분산하여 처리량을 극대화하는 두 가지 방식 모두 적용

2. 메시지 보존(Retention) 철학: ‘로그’인가, ‘큐’인가?

두 시스템의 가장 근본적인 차이는 메시지를 다루는 방식이다.

  • Kafka: 데이터는 ‘변경 불가능한 로그(Immutable Log)’ Kafka는 소비자가 메시지를 읽어가도 즉시 삭제하지 않는다. 메시지는 설정된 보존 기간(예: 7일) 또는 용량에 도달할 때까지 토픽에 안전하게 보관한다. 소비자는 단지 ‘어디까지 읽었는지’를 나타내는 오프셋(Offset) 만 관리한다.

    • 메시지 재생(Replay) & 시간 여행:
      • 소비자에 버그가 있었다면? 코드를 수정한 뒤, 오프셋을 과거 시점으로 되돌려 모든 데이터를 다시 처리할 수 있다.
      • 메시지를 사용하는 새로운 시스템을 도입했다면? 토픽의 처음부터 모든 이벤트를 가져와 상태를 재구축할 수 있다.
    • 다목적 데이터 허브: 하나의 이벤트 스트림을 실시간 대시보드, 배치(Batch) 분석, 모델 학습 등 다양한 목적을 가진 여러 소비자가 각자의 속도에 맞게 여러 번 소비할 수 있다.
  • RabbitMQ: 데이터는 ‘처리해야 할 일(Transient Task)’ 전통적인 RabbitMQ에서 메시지는 ‘처리되어야 할 작업’. 소비자가 메시지를 가져가 성공적으로 처리했다고 확인(ack) 신호를 보내면, 메시지는 큐에서 영구적으로 제거된다.

    이 방식 특징:

    • 작업 큐에 최적화: 이메일을 보내라, 이미지를 생성해라 ,"이미지를 최적화해라" 와 같이 한 번 처리되고 나면 더 이상 필요 없는 작업들을 관리하는 데 매우 효율적이다.
    • 오류 처리 중심의 보관: 메시지 TTL(Time-To-Live)이나 Dead Letter Exchange(DLX)는 메시지가 성공적으로 처리되지 못했을 때를 대비한 기능이다. (즉, 영구 보존이 아닌 예외 처리 및 재시도 로직을 위한 장치)

참고: RabbitMQ도 시대의 흐름에 맞춰 Streams라는 새로운 큐 타입을 도입. (Kafka처럼 오프셋 기반의 비파괴 소비를 지원하여 로그와 같은 동작을 유사) 하지만 RabbitMQ 는 기본적으로 ‘소비-제거’ 방식의 큐를 위해 사용한다.

3. RabbitMQ의 팬아웃은 ‘교환기(Exchange)’의 역할

RabbitMQ도 팬아웃의 방식은 Kafka와는 다르다. RabbitMQ의 라우팅 능력의 핵심에는 교환기(Exchange) 를 통해 수행한다.

생산자는 메시지를 큐에 직접 보내는 것이 아니라, 교환기에 보낸다. 그러면 교환기가 설정된 타입과 규칙에 따라 메시지를 어떤 큐에 보낼지 결정한다.

  • fanout 교환기: 자신에게 연결(binding)된 모든 큐에 메시지를 복사해서 보낸다. 가장 순수한 형태의 방송(Broadcast) 모델
  • topic 교환기: 라우팅 키와 바인딩 패턴을 와일드카드(*, #)로 매칭, 조건에 맞는 큐에만 메시지를 선택해서 보낸다. (멀티캐스트)
  • direct 교환기: 라우팅 키가 바인딩 키와 정확히 일치하는 큐에만 메시지를 보냅니다. (유니캐스트)

핵심: RabbitMQ에서는 브로커(교환기)가 ‘스마트’하게 라우팅 규칙을 해석하여 메시지를 분배 반면 Kafka에서는 생산자가 토픽을 지정하고, 소비자가 ‘스마트’하게 그룹을 지어 메시지를 가져간다.


RabbitMQ vs Kafka: 7가지 핵심 비교 포인트

이제 두 시스템의 구체적인 기능과 특성을 7가지 핵심 주제로 나누어 깊이 있게 비교해보자.

1. 메시지 보존 & 재생(Replay): 데이터에 대한 관점 차이

  • Kafka: 영구적인 로그, 무한한 재생 가능성

1000

  • 철학: Kafka는 데이터를 ‘일시적인 메시지’가 아닌 ‘영구적인 사실의 기록(log of facts)’으로 취급한다. 메시지는 소비되어도 삭제되지 않고 retention.ms (시간) 또는 retention.bytes (용량) 설정에 따라 디스크에 보관된다.

    • 오프셋 리셋 (재생): 소비자는 언제든지 auto.offset.reset = 'earliest' 설정을 하면 토픽의 가장 처음부터 모든 이벤트를 다시 읽어올 수 있다. (이는 버그 수정 후 데이터 복구, 새로운 서비스의 상태 구축, A/B 테스팅 등 무한한 방법을 가능하게 해준다)
    • 로그 컴팩션 (Log Compaction): cleanup.policy=compact로 설정하면, Kafka는 토픽의 모든 메시지를 보관하지 않고 각 메시지 키(key)에 대한 가장 최신 값만을 유지한다.

      Kafka Log Compaction 내용을 참고

  • RabbitMQ: 소비 후 제거되는 작업 큐

    • 철학: RabbitMQ의 기본 모델은 ‘처리해야 할 작업’을 큐에 넣고, 작업이 완료되면(ack) 큐에서 제거한다. (데이터 보존보다 안정적인 작업 전달에 초점을 맞춘다)
    • 제한적인 재생: 메시지가 ack되면 사라지므로 Kafka와 같은 자유로운 재생이 불가능하다. 오류 발생 시 메시지를 재처리하기 위해 Dead Letter Exchange (DLX) 로 보내거나, 소비자가 nack (또는 reject)하여 큐에 다시 넣는 방식을 사용한다.
    • 스트림 큐 (Streams): RabbitMQ 3.9부터 도입된 스트림 큐는 Kafka와 유사한 로그 기반 동작을 제공한다. 메시지를 삭제하지 않고 오프셋을 통해 추적해, 비파괴적인 소비와 대용량 데이터의 팬아웃이 가능하다. (하지만 이는 RabbitMQ의 전통적인 모델을 보완하는 기능이며, 생태계는 당연히 Kafka가 더 성숙)

2. 순서(Ordering) & 병렬성: 보장과 트레이드오프

  • Kafka: 파티션 단위의 엄격한 순서 보장
    • 동작 방식: Kafka는 하나의 파티션 내 메시지의 순서를 절대적으로 보장 한다. 생산자가 보낸 순서대로 메시지가 로그에 기록되고, 소비자는 그 순서대로 읽는다.
    • 병렬성과 순서의 트레이드오프: 처리량을 높이기 위해 파티션 수를 늘리면 병렬 처리 수준은 높아지나, 서로 다른 파티션 간의 순서는 보장되지 않는다.
    • 키 기반 파티셔닝: 순서 보장이 중요한 경우, 메시지 키(key) 를 통해 보장해야 한다 (예를 들어, 모든 user_id 관련 이벤트를 동일한 user_id를 키로 전송하면, Kafka는 해시 값을 계산해 항상 동일한 파티션으로 해당 이벤트를 보낸다. - 모든 이벤트 순서대로 처리)
  • RabbitMQ: 경쟁적 소비자 환경에서의 순서 불확실성
    • 기본 동작: 큐 자체는 FIFO(First-In-First-Out) 구조를 따르지만, 여러 소비자가 하나의 큐를 동시에 구독해 경쟁적으로 메시지를 가져가면, 메시지 처리 순서는 보장되지 않는다. EX) 소비자 A가 메시지 1을, 소비자 B가 메시지 2를 거의 동시에 가져가고 만약 소비자 A의 작업이 오래 걸리고 소비자 B가 먼저 작업을 끝내면, 메시지 2가 메시지 1보다 먼저 처리될 수 있다.
    • 순서 보장 방법: 큐에 단 하나의 소비자만 활성화하여 순서를 강제할 순 있다. ( 병렬성을 포기하므로, 당연히 생산성 감소 )

      RabbitMQ 로 순서를 제어하는건 생각보다 꽤나 어렵다.. 메시지를 기반으로 다음 메시지를 연쇄적으로 발행시키는 방법으로 순서를 보장하자.

3. 전달 보장(Delivery Semantics): 데이터 유실 및 중복에 대한 약속

  • Kafka: Exactly-Once (정확히 한 번 전달) 지원
    • At-least-once (최소 한 번): 기본 설정. 소비자가 메시지를 처리하고 오프셋을 커밋하기 전에 실패하면, 재연결 후 마지막 커밋된 오프셋부터 다시 메시지를 읽어 중복이 발생할 수 있다.
    • Exactly-Once (EOS): Kafka 0.11부터 멱등성 프로듀서(Idempotent Producer)트랜잭션(Transactions) 을 조합하여 EOS를 지원한다.
      • 멱등성 프로듀서: 네트워크 오류 등으로 재시도를 하더라도, 각 메시지에 부여된 고유 ID를 통해 브로커가 중복을 제거한다.
      • 트랜잭션: 여러 토픽/파티션에 걸친 여러 메시지 쓰기 작업을 하나의 원자적 단위로 묶어, ‘읽기-처리-쓰기’ 패턴으로 완벽한 EOS를 가능하게 해준다.

        [참고 자료] Kafka의 Exactly-Once 심층 분석: Exactly-Once Semantics Are Possible: Here’s How Kafka Does It 내용 참고

  • RabbitMQ: At-least-once가 기본, Exactly-once는 애플리케이션의 몫
    • At-least-once (최소 한 번): 소비자가 메시지를 처리하고 브로커에게 ack를 보내기 전에 연결이 끊어지면, 브로커는 해당 메시지를 다른 소비자에게 다시 전달하여 중복이 발생할 수 있다.
    • At-most-once (최대 한 번): auto-ack 모드를 사용하면, 소비자가 메시지를 받는 즉시 브로커가 ack된 것으로 간주한다. 만약 소비자가 메시지를 처리하던 중 실패하면 해당 메시지는 유실된다. (서비스 특성에 따라 주의깊게 사용)

    • Exactly-once의 부재: RabbitMQ는 프로토콜/브로커 수준에서 EOS를 직접 지원하지 않는다. EOS를 구현하려면, 소비자가 직접 멱등성 로직을 구현해야 한다.

근데, At-least-once 가 전혀 나쁜 설정이 아니다. 왜냐하면, 소비자가 원한 특정 로직을 무조건 해줘야 할 필요가 있는데 특정 컨슈머가 처리 못해도 다른 컨슈머가 무조건 처리해주는 걸 보장해줄 수 있기 때문이다. 데이터 처리한 내용을 DB 에 반영하는건, 다른 DB 가 다른 메시지로 처리해주면 된다.

4. 흐름 제어 & 백프레셔: 누가 속도를 제어하는가?

  • Kafka: Pull 기반 (소비자가 주도)
    • 철학: 소비자가 자신의 처리 능력에 맞게 브로커의 데이터를 당겨온다(Pull). 소비자가 poll() 메소드를 호출할 때만 데이터 전송
    • 백프레셔: 이 모델 자체가 백프레셔. 소비자가 바쁘면 poll()을 호출하지 않고, 데이터는 브로커에서 대기한다. (max.poll.records (한 번에 가져올 최대 메시지 수), fetch.min.bytes (최소 이만큼 데이터가 쌓여야 응답) 등 옵션으로 소비 속도를 세밀하게 튜닝 가능)
  • RabbitMQ: Push 기반 (브로커가 주도) + Prefetch
    • 철학: 브로커가 소비자에게 메시지를 밀어준다(Push). 소비자가 준비되었는지 여부와 상관없이 메시지를 보내므로, 소비자가 압도당할 위험 존재
    • 백프레셔 (Prefetch): 이 문제를 해결하기 위해 소비자는 prefetch 값(QoS - Quality of Service) 을 설정. 브로커는 해당 소비자가 처리 중인 메시지 수가 prefetch 값에 도달하면 더 이상 메시지를 보내지 않고 대기.

RabbitMQ에서 prefetch 값 설정은 성능과 안정성에 매우 중요. 너무 작으면 네트워크 왕복이 잦아져 처리량이 낮아지고, 너무 크면 소비자의 메모리가 고갈 가능 ( 특이한 도메인은 메시지를 무조건 하나씩 받아야 할 수 있다. - AI 이미지 생성 로직이라면 GPU 를 쓰고 한번에 하나의 작업만 보장하기 위해 prefetch 1 )

5. 라우팅/토폴로지: ‘스마트 브로커’ vs ‘더미 브로커’

위에서 이미 작성한 내용이라 간단하게만 작성한다.

  • Kafka: 단순한 토폴로지, 스마트한 클라이언트
    • 더미 브로커: Kafka 브로커의 역할은 단순. 생산자가 지정한 토픽의 파티션에 메시지를 순서대로 저장. (복잡한 라우팅 로직 X)
    • 스마트 클라이언트: 라우팅 결정은 주로 생산자가 담당. 어떤 토픽으로, 어떤 키를 사용해 어느 파티션으로 보낼지를 결정. 소비자는 어떤 토픽을 구독할지 결정.
  • RabbitMQ: 유연한 라우팅, 스마트한 브로커
    • 스마트 브로커: RabbitMQ 은 교환기(Exchange) 를 통해 스마트하게 처리한다. 생산자는 메시지를 교환기에 보내고, 교환기는 설정된 타입(direct, topic, fanout, headers)과 라우팅 규칙(binding)에 따라 메시지를 적절한 큐로 분배
    • 복잡한 워크플로우 구현: 이 모델을 통해 메시지 내용에 따라 다른 큐로 보내거나, 여러 큐에 동시에 보내는 등 정교하고 복잡한 워크플로우를 브로커 수준에서 쉽게 구현할 수 있다. (Dead Letter Exchange, Delayed Message Plugin 등을 조합해 재시도, 지연 처리 등 고급 패턴도 가능)

6. 확장성 & 고가용성(HA): 스케일 아웃과 데이터 복제

  • Kafka: 파티션 기반의 완벽한 수평 확장
    • 설계: Kafka는 처음부터 분산 시스템으로 설계. 토픽을 여러 파티션으로 나누고, 이 파티션들을 클러스터 내 여러 브로커에 분산해 읽기/쓰기 부하를 수평으로 확장(scale-out) 가능
    • 고가용성 (HA): 각 파티션은 복제 계수(Replication Factor) 를 가진다.
      • 파티션 중 하나는 리더(Leader) 가 되어 읽기/쓰기를 담당, 나머지는 팔로워(Follower) 가 되어 리더의 데이터를 복제
      • 리더 브로커에 장애가 발생하면, 팔로워 중 하나가 자동으로 새로운 리더로 선출되어 서비스 중단을 최소화 - failover
  • RabbitMQ: Quorum 큐와 스트림을 통한 현대적인 HA
    • Mirrored Queues: 과거에는 미러 큐를 사용했지만, 리더-팔로워 모델의 한계와 데이터 안정성 문제로 인해 현재는 권장되지 않습니다.
    • Quorum Queues: RabbitMQ 3.8부터 도입된 쿼럼 큐는 Raft 합의 알고리즘을 기반으로 동작합니다. 이는 모든 메시지 복제가 클러스터의 과반수 노드에 기록되었음을 보장하여 미러 큐보다 훨씬 높은 데이터 안정성을 제공합니다. 고가용성과 데이터 안전이 중요한 대부분의 경우에 권장됩니다.
    • 스트림 큐 (Streams): 대용량 처리량과 수평 확장이 필요할 때 사용됩니다. 스트림은 여러 브로커에 걸쳐 샤딩(sharding)될 수 있어 Kafka와 유사한 확장성을 제공합니다.

리더 -팔로워 모델의 문제?

  • 쓰기 병목: 메시지 발행하면 무조건 리더 큐에 먼저 도달, 팔로워들이 메시지 복제하고 확인 응답 보낼때 까지 대기
  • 읽기 병목:소비자는 리더 큐에만 연결해서 메시지 가져올 수 있음 => 발행과 소비 트래픽이 하나의 리더 노드에 몰리게 된다.

7. 생태계 & 도구: ‘플랫폼’ vs ‘브로커’

  • Kafka: 데이터 파이프라인 플랫폼 Kafka는 단순한 메시지 브로커를 넘어, 그 자체로 하나의 거대한 데이터 플랫폼을 형성
    • Kafka Connect: 데이터베이스, S3, Elasticsearch 등 수백 개의 시스템과 Kafka를 코딩 없이 연결하는 프레임워크.
    • Kafka Streams / ksqlDB: Kafka 토픽 데이터를 실시간으로 처리하고 분석하는 스트림 처리 라이브러리 및 SQL 엔진.
    • Schema Registry: 데이터의 스키마를 관리하고 발전시켜, 데이터 일관성과 호환성을 보장
  • RabbitMQ: 다재다능한 메시지 브로커 RabbitMQ는 특정 목적에 맞춰 유연하게 사용할 수 있는 강력한 브로커 의 느낌
    • Management UI: 큐 상태, 메시지 흐름, 연결 등을 시각적으로 모니터링하고 관리할 수 있는 훌륭한 웹 UI를 기본 제공
    • Federation / Shovel: 서로 다른 데이터 센터나 환경에 있는 브로커들을 연결하는 강력한 플러그인
    • 다양한 프로토콜 지원: AMQP 0-9-1, AMQP 1.0, STOMP, MQTT 등 다양한 프로토콜을 플러그인을 통해 지원해 유연성이 높음

언제 무엇을 고를까..?

AI 가 요약해 준 내용이자, 팀에서 RabbitMQ 를 왜 쓰는가에 대해 고민할 때 이정도로 생각난 내용으로 당연히 틀릴 내용일 수 있다.

  • “과거 데이터를 다시 처리해야 하는 경우가 있는가?”
    • Yes → Kafka: 이벤트 소싱, 데이터 분석, 새로운 서비스 도입 시 상태 재구축 등 데이터 재생이 필요하다면 무조건 Kafka
  • “메시지마다 복잡한 조건에 따라 다른 곳으로 보내야 하는가?”
    • Yes → RabbitMQ: 메시지 헤더나 라우팅 키 패턴에 따라 정교하게 라우팅하는 기능이 필요하다면 RabbitMQ - 스마트 브로커
  • “주된 용도가 백그라운드에서 실행될 비동기 작업을 처리하는 것인가?”
    • Yes → RabbitMQ: 이메일 발송, 이미지 처리 등 전통적인 ‘작업 큐’ 시나리오에서는 RabbitMQ - 간단하고, 직관적인 해결책 ( 메시지 유지할 필요도 없기에 )
  • “수많은 독립적인 서비스들이 동일한 이벤트 스트림을 각자의 목적에 맞게 소비해야 하는가?”
    • Yes → Kafka: 여러 팀, 서비스가 각자의 속도로 동일 데이터를 소비하는 ‘데이터 허브’ 또는 ‘이벤트 백본’을 구축하는 경우 - 소비자 그룹 모델과 메시지 보존 정책
  • “초당 수십만 건 이상의 매우 높은 처리량이 필수적인가?”
    • Yes → Kafka: Kafka는 디스크 기반의 순차 I/O를 활용하여 대규모 스트림 데이터를 처리하는 데 최적화 - Append-Only, OS 레벨 페이지 캐시, Zero-Copy 기술 활용
  • “메시지별 TTL, 지연 처리, 요청-응답(RPC) 패턴이 필요한가?”
    • Yes → RabbitMQ: RabbitMQ는 이러한 고급 메시징 패턴들을 플러그인 등을 통해 더 성숙하게 지원

마무리

꼭 메시지 큐잉 시스템을 Kafka 로 써야한다고 생각에 빠지지 말자. 회사 인프라, 팀 내 기술, 프로젝트 내 요구사항 등 다양한 것들이 고려되어 결정이 되어야 한다.

우리팀의 메시지 큐잉 시스템은

  • AI 생성 로직이 API 서버와 분리 되기 위해서 사용
  • 작업이 무조건 수행 되는걸 보장
  • 아직 대용량의 처리 및 시스템을 구축할 필요가 없는점 + 리플레이 및 메시지를 저장할 필요가 없는점

이런점들을 통해 RabbitMQ 를 사용한다고 생각한다.