자바/자바

(자바) Apache Kafka 핵심 개념 (RabbitMQ <->Redis (Pub/Sub) <->Apache Kafka 차이 )

불광동 물주먹 2025. 12. 15. 17:05

1. Apache Kafka란 무엇인가?

Apache Kafka는 LinkedIn에서 개발하여 오픈소스로 공개한 **분산 이벤트 스트리밍 플랫폼(Distributed Event Streaming Platform)**입니다.

기존의 메시징 시스템이 "메시지를 주고받는 것"에 집중했다면, Kafka는 **"데이터(이벤트)를 지속적으로 생성하고, 저장하고, 처리하는 것"**에 초점을 맞춥니다.

핵심 특징

  1. 높은 처리량(High Throughput): 초당 수백만 건의 이벤트를 처리할 수 있습니다.
  2. 영속성(Persistence): 메시지를 메모리가 아닌 **디스크(Disk)**에 저장하여, 장애가 발생해도 데이터가 유실되지 않습니다.
  3. 확장성(Scalability): 서버(Broker)를 수평적으로 늘려 성능을 확장하기 쉽습니다.

2. 기술 비교 분석: Kafka vs RabbitMQ vs Redis

메시지 큐(Message Queue)를 도입하려 할 때 가장 많이 비교되는 세 기술의 차이점입니다.

구분 RabbitMQ Redis (Pub/Sub) Apache Kafka
유형 메시지 브로커 (Message Broker) 인메모리 데이터 저장소 이벤트 스트리밍 플랫폼
메시지 보관 소비되면 삭제 (휘발성) 연결 안 된 상태면 증발 설정 기간 동안 디스크 저장 (영속성)
주요 특징 강력한 라우팅(Routing), 우선순위 큐 초저지연 속도 (Microsecond) 대용량 배치 처리, 로그 수집, 재처리
소비 모델 Push (브로커가 밀어넣음) Push Pull (컨슈머가 가져감)
비유 우체부 (배달하면 끝) 라디오 방송 (안 듣고 있으면 놓침) CCTV 녹화 (언제든 돌려보기 가능)

💡 언제 Kafka를 선택해야 하는가?

  • 데이터가 유실되면 안 되고, 일정 기간 보관되어야 할 때.
  • 생산자(Producer)의 속도가 소비자(Consumer)보다 빨라 **버퍼링(Buffering)**이 필요할 때.
  • 과거의 데이터를 다시 읽어서 **재처리(Replay)**해야 하는 로직이 있을 때.

3. Kafka 핵심 아키텍처와 용어

Kafka는 독특한 구조를 통해 고성능과 고가용성을 보장합니다.

 

  • ① 브로커(Broker)와 주키퍼(Zookeeper)
    • Broker: Kafka 서버 그 자체를 의미합니다. 실행된 Kafka 애플리케이션 서버 1대를 브로커라고 부르며, 보통 3대 이상으로 구성하여 **클러스터(Cluster)**를 구축합니다. 브로커는 프로듀서로부터 메시지를 받아 디스크에 저장하고, 컨슈머에게 데이터를 전달하는 중추적인 역할을 합니다.
    • Zookeeper: 분산 시스템인 Kafka 브로커들을 관리하는 코디네이터입니다.
      • 브로커 중 누가 리더인지(Controller 선출), 현재 살아있는 브로커는 누구인지 등 메타데이터(Metadata)를 관리합니다. (※ 최신 Kafka 버전에서는 Zookeeper를 제거한 KRaft 모드로 전환되는 추세입니다.)
    ② 토픽(Topic)과 파티션(Partition)
    • Topic (데이터의 분류): 데이터가 저장되는 논리적인 구분 단위입니다. 파일 시스템의 **'폴더'**와 유사합니다. 예를 들어 click_log, payment_history 처럼 목적에 따라 토픽을 생성합니다.
    • Partition (물리적 분할): 하나의 토픽은 성능 향상을 위해 여러 개의 **'파티션'**으로 쪼개져서 브로커들에 분산 저장됩니다.
      • Queue가 아닌 Log 방식: 파티션은 데이터가 뒤에 계속 추가되는(Append-only) 로그 파일 형태입니다.
      • 병렬 처리의 핵심: 파티션이 3개라면, 최대 3개의 컨슈머가 동시에 붙어서 데이터를 처리할 수 있습니다. 즉, 파티션의 개수가 곧 병렬 처리량(Concurrency)의 최대치가 됩니다.
    ③ 프로듀서(Producer)의 데이터 전송 전략
    • Key가 없는 경우 (Round Robin): send("order", value) 처럼 키 없이 보내면, 프로듀서는 데이터를 여러 파티션에 골고루 뿌려줍니다. (부하 분산)
    • Key가 있는 경우 (Hash): send("order", user_id, value) 처럼 키(Key)를 지정하면, 특정 user_id는 항상 동일한 파티션으로 들어갑니다. 이는 **순서 보장(Ordering)**이 필요할 때 필수적입니다.
    ④ 컨슈머(Consumer)와 컨슈머 그룹(Consumer Group)
    • Consumer Group: 하나의 목적(예: DB 저장)을 위해 협력하는 컨슈머들의 집합입니다.
    • 1:1 매핑 규칙: 하나의 파티션은 동일 그룹 내의 하나의 컨슈머만 연결될 수 있습니다.
      • 상황 A: 파티션 3개, 컨슈머 1개 → 컨슈머 혼자 3개 파티션을 다 처리함.
      • 상황 B: 파티션 3개, 컨슈머 3개 → 1:1 매핑되어 최상의 성능 발휘.
      • 상황 C: 파티션 3개, 컨슈머 4개 → 컨슈머 1개는 놉니다(Idle). (파티션 수보다 컨슈머 수가 많으면 낭비 발생)
    • Rebalancing: 컨슈머 서버 하나가 장애로 죽으면, 그룹 내의 다른 컨슈머가 죽은 친구가 맡던 파티션을 알아서 넘겨받아 처리를 계속합니다. (고가용성)
    ⑤ 오프셋(Offset)과 커밋(Commit) 
    • Offset: 파티션 내의 데이터 위치를 나타내는 고유한 숫자(인덱스)입니다.
    • Commit: 컨슈머는 데이터를 처리한 후, *"나 여기까지 처리했어"*라고 Kafka에 알립니다. 이를 통해 컨슈머 서버가 재시작되어도, 마지막으로 커밋된 오프셋 이후부터 데이터를 다시 읽어올 수 있어 데이터 중복이나 유실을 방지합니다.
  • Kafka의 가장 큰 특징 중 하나는 컨슈머가 개별적으로 동작하지 않고 '그룹' 단위로 관리된다는 점입니다.
  • 프로듀서는 단순히 데이터를 보내는 것이 아니라, **'어떤 파티션'**에 넣을지를 결정합니다.

 

6. 리플리케이션(Replication)과 고가용성

Kafka는 서버(Broker) 장애에 대비해 데이터를 복제합니다.

  • Leader Partition: 실제 데이터의 읽기/쓰기를 담당하는 원본.
  • Follower Partition: 리더의 데이터를 실시간으로 복제하는 백업본.
  • ISR (In-Sync Replicas): 리더와 싱크가 맞는 팔로워들의 그룹. 리더가 죽으면 ISR 중 하나가 새로운 리더가 됩니다.

Cluster 구조의 특징:

Kafka 클러스터의 모든 브로커는 각자 다른 파티션의 리더 역할을 나누어 맡습니다. (Active-Active 구조). 따라서 노는 서버 없이 모든 자원을 효율적으로 사용합니다.

 

 


4. 아키텍처의 진화: End-to-End에서 Event-Driven으로4-1. 기존 방식: End-to-End (Point-to-Point) 연결의 한계

  • 구조: User Request → WAS → DB (INSERT)
  • 동작 방식 (Synchronous): 사용자의 요청이 들어오면 WAS는 DB에 데이터를 넣을 때까지 스레드를 점유하고 기다립니다.
  • 치명적인 문제점 (Spaghetti & Bottleneck):
    1. 복잡도 증가 ($N \times M$): 연동해야 할 시스템이 늘어날수록 연결선이 기하급수적으로 늘어나 관리 불가능한 '스파게티 코드'가 됩니다.
    2. 장애 전파: DB가 일시적으로 느려지거나 멈추면, 앞단의 WAS도 같이 멈춰버립니다. (Cascading Failure)
    3. 트래픽 폭주 시 DB 사망: 10만 명의 요청이 들어오면 10만 번의 DB 커넥션이 동시에 발생하여, DB가 부하를 견디지 못하고 뻗어버립니다.

4-2. 개선된 방식: Kafka 기반의 Hub & Spoke 아키텍처

  • 구조: WAS (Producer) → Kafka (Buffer) → WAS (Consumer) → DB
  • 핵심 변화 (Decoupling):
    • 생산자(Producer): DB의 상태를 신경 쓰지 않고, Kafka에 메시지만 던지고(send) 즉시 빠져나옵니다. (응답 속도 향상)
    • Kafka(Broker): 폭주하는 트래픽을 디스크에 받아내며 댐(Dam) 역할을 합니다. (Backpressure 처리)
    • 소비자(Consumer): 자신의 처리 능력에 맞춰서 데이터를 천천히 가져갑니다.

4-3. 대량 처리 : Batch Processing (Bulk Insert)

  • Scenario: 초당 1,000건의 주문 요청이 발생할 때
    • Before (End-to-End): 1,000번의 DB 커넥션 생성 + 1,000번의 INSERT 쿼리 실행.
    • After (Kafka Batch): Consumer가 100건씩 데이터를 모아서(Buffering), 단 10번의 Bulk INSERT 쿼리로 처리.
Java
// Spring Kafka Batch Listener 예시
@KafkaListener(topics = "order-topic", containerFactory = "batchFactory")
public void batchListener(List<OrderDto> orders) {
    // 100개의 주문을 1번의 쿼리로 처리 (MyBatis <foreach> 활용)
    orderMapper.insertBulk(orders); 
}
  • 📊 요약: 왜 아키텍처를 바꿨는가?
    구분 End-to-End (기존) Event-Driven with Kafka (개선)
    연결 구조 Point-to-Point (직접 연결) Hub & Spoke (중앙 집중형)
    결합도 강한 결합 (Tight)

    하나 죽으면 다 죽음
    느슨한 결합 (Loose)

    DB 죽어도 주문 접수는 가능
    트래픽 대응 폭주 시 DB 병목으로 서비스 마비 Kafka가 버퍼링하여 시스템 보호
    DB 처리 효율 1건당 1회 I/O (비효율) N건당 1회 I/O (Bulk Insert 극대화)
  • 아키텍처 변화가 가져온 가장 큰 기술적 이점은 DB I/O의 획기적인 감소입니다. End-to-End 방식에서는 불가능했던 **'모아서 처리하기'**가 가능해짐.
  • Kafka를 중앙 허브(Hub)로 두어 시스템 간의 결합을 끊어내는 비동기(Asynchronous) 구조로 전환.
  • 초기 시스템은 웹 애플리케이션(WAS)이 DB나 타 시스템과 **1:1로 직접 연결(Tight Coupling)**되는 구조.
  • 대용량 트래픽을 처리할 때 가장 큰 병목은 애플리케이션(WAS)이 아니라 데이터베이스(DB)의 I/O에서 발생.
  • 단순히 "배치 처리를 한다"라고 말하는 것보다, **"기존의 End-to-End(Point-to-Point) 아키텍처의 한계를 카프카 기반의 이벤트 구동(Event-Driven) 아키텍처로 어떻게 극복했는지"**를 비교해서 보여주면 글의 깊이가 훨씬 깊어짐.

👉 결과: 사용자에겐 빠른 응답 속도를 제공하고, DB에는 최소한의 I/O 부하만 주게 됨.


5. 결론: "적재적소"의 기술 선택

Kafka는 만능열쇠가 아님.

  • 단순한 데이터 조회나, 즉각적인 Request-Response가 필요한 로직에는 적합하지 않습니다. (이 경우 Redis나 RDBMS가 유리합니다.)

하지만 시스템 간의 결합도(Coupling)를 낮추고, 트래픽 폭주(Backpressure)를 유연하게 제어하며, 데이터의 흐름을 안정적으로 관리해야 하는 대규모 시스템에서는 대체 불가능한 핵심 솔루션.

안정적인 백엔드 시스템 설계를 고민한다면, 단순히 '메시지를 보낸다'는 관점을 넘어 **'데이터 파이프라인을 구축한다'**는 관점에서 Kafka 도입을 고려해야 함.

 

 

 

다음 글에는 개인프로젝트 (우분투) 에 카프카를 적용하는 방법에 대해 작성하겠습니다.