Back to Blog
KafkaTopicPartitionBrokerProducerConsumerConsumer Group

0x01. Apache Kafka - 분산 메시지 스트리밍 플랫폼

Apache Kafka의 핵심 구성 요소인 Topic, Partition, Broker, Producer, Consumer를 이해하고 분산 메시지 스트리밍의 원리를 알아본다.

"A distributed event streaming platform capable of handling trillions of events a day."

LinkedIn에서 내부 데이터 파이프라인 문제를 해결하기 위해 탄생한 Apache Kafka는, 오늘날 Fortune 100 기업의 80% 이상이 사용하는 분산 이벤트 스트리밍 플랫폼이 되었다. 실시간 로그 수집, 이벤트 소싱, 스트림 프로세싱 등 대규모 데이터 처리가 필요한 거의 모든 곳에 Kafka가 있다. 도대체 어떤 구조이기에, 이토록 널리 쓰이는 걸까?


Kafka란?

Apache Kafka는 분산 환경에서 대량의 데이터를 실시간으로 전송하고 처리하기 위한 이벤트 스트리밍 플랫폼(Event Streaming Platform) 이다.

전통적인 메시지 큐(Message Queue)와 혼동하기 쉽지만, Kafka는 단순한 메시지 전달을 넘어선다. 핵심적인 차이는 메시지를 소비해도 삭제하지 않는다는 점이다. 일반적인 메시지 큐에서는 Consumer가 메시지를 읽으면 큐에서 사라지지만, Kafka는 설정된 보존 기간(Retention Period) 동안 메시지를 디스크에 영속적으로 저장한다. 덕분에 여러 Consumer가 같은 데이터를 각자의 속도로 독립적으로 읽을 수 있다.

Kafka가 필요한 상황을 생각해 보자. 대형 이커머스 플랫폼에서 사용자가 상품을 클릭할 때마다, 이 이벤트를 추천 시스템, 실시간 분석 대시보드, 검색 인덱서, 재고 관리 시스템 등 여러 서비스가 동시에 소비해야 한다. 각 서비스에 직접 데이터를 보내는 Point-to-Point 방식은 서비스가 늘어날수록 연결이 기하급수적으로 복잡해진다. Kafka는 이 사이에 중앙 허브 역할을 하여, 데이터를 생산하는 쪽과 소비하는 쪽을 완전히 분리(Decoupling) 한다.

Kafka의 기본 아키텍처는 다음 네 가지 핵심 구성 요소로 이루어진다.

  • Producer: 메시지를 Kafka에 발행하는 클라이언트
  • Consumer: Kafka에서 메시지를 읽는 클라이언트
  • Broker: 메시지를 저장하고 관리하는 서버
  • Topic: 메시지가 분류되는 논리적 채널

이제 각 구성 요소를 하나씩 살펴보자.


Topic & Partition

Topic: 메시지의 논리적 분류

토픽(Topic) 은 Kafka에서 메시지가 발행되는 논리적인 채널이다. 파일 시스템의 폴더, 또는 데이터베이스의 테이블에 비유할 수 있다.

예를 들어, 이커머스 시스템이라면 order-events, user-clicks, payment-logs 같은 토픽을 만들어 이벤트의 종류별로 메시지를 분류한다. Producer는 특정 토픽에 메시지를 쓰고, Consumer는 관심 있는 토픽을 구독(Subscribe)하여 메시지를 읽는다.

하나의 토픽에 여러 Producer가 메시지를 쓸 수 있고, 여러 Consumer가 동시에 읽을 수도 있다.

Partition: 병렬 처리의 핵심

하나의 토픽에 초당 수만 건의 메시지가 들어온다면, 단일 서버가 이를 모두 처리하기는 어렵다. Kafka는 토픽을 여러 개의 파티션(Partition) 으로 나누어 이 문제를 해결한다.

파티션은 토픽을 물리적으로 분할한 단위다. 각 파티션은 순서가 보장되는 불변의 메시지 시퀀스이며, 서로 다른 Broker에 분산 저장될 수 있다. 마치 고속도로의 차선처럼, 차선이 많을수록(파티션이 많을수록) 동시에 더 많은 차량(메시지)이 통과할 수 있다.

Topic: order-events

Partition 0: [msg0, msg1, msg2, msg3, msg4, ...]
Partition 1: [msg0, msg1, msg2, msg3, ...]
Partition 2: [msg0, msg1, msg2, ...]

파티션에서 꼭 알아야 할 두 가지 개념이 있다.

Offset: 메시지의 고유 위치

오프셋(Offset) 은 파티션 내에서 각 메시지에 순차적으로 부여되는 고유 식별자(ID) 다. 0부터 시작하여 메시지가 추가될 때마다 1씩 증가한다. 책의 페이지 번호와 같은 역할이며, 한번 부여된 오프셋은 변경되지 않는다.

Consumer는 자신이 마지막으로 읽은 오프셋을 기억하고 있다가, 다음 번에는 그 이후부터 읽는다. 장애가 발생해 Consumer가 재시작되더라도, 저장된 오프셋 위치부터 이어서 읽으면 되므로 메시지 유실 없이 복구할 수 있다.

파티션 키: 메시지의 라우팅

메시지가 어떤 파티션에 들어갈지는 파티션 키(Partition Key) 에 의해 결정된다. Producer가 메시지를 보낼 때 키를 지정하면, Kafka는 해당 키의 해시 값을 기반으로 파티션을 선택한다. 동일한 키를 가진 메시지는 항상 같은 파티션에 저장되므로, 해당 키에 대한 메시지 순서가 보장된다.

예를 들어, 주문 이벤트의 키를 user_id로 설정하면 같은 사용자의 모든 주문 이벤트는 동일 파티션에 순서대로 저장된다. 키를 지정하지 않으면, Kafka는 라운드 로빈(Round Robin) 또는 스티키 파티셔닝(Sticky Partitioning) 방식으로 파티션을 선택하여 부하를 분산한다.

주의할 점: 순서 보장은 파티션 내부에서만 이루어진다. 파티션 간에는 순서가 보장되지 않는다. 전체 토픽에 대한 전역 순서(Global Ordering)가 필요하다면 파티션을 하나만 사용해야 하지만, 이 경우 병렬 처리의 이점을 잃게 된다.


Broker: Kafka 클러스터의 구성 단위

Broker의 역할

브로커(Broker) 는 Kafka 클러스터를 구성하는 개별 서버(노드) 이다. 각 Broker는 고유한 ID를 가지며, 다음의 역할을 수행한다.

  • 메시지를 디스크에 저장
  • Producer와 Consumer의 요청 처리
  • 파티션 데이터의 복제(Replication) 관리

하나의 Kafka 클러스터는 보통 3개 이상의 Broker로 구성된다. 토픽의 파티션들은 이 Broker들에 골고루 분산되어 저장되므로, 특정 Broker에 부하가 집중되는 것을 방지한다.

Leader와 Follower

하나의 파티션은 여러 Broker에 복제될 수 있지만, 그중 단 하나만이 Leader이고 나머지는 Follower(복제본) 이다.

  • Leader: 해당 파티션에 대한 모든 읽기/쓰기 요청을 처리한다.
  • Follower: Leader의 데이터를 동기화(replicate) 하여 복제본을 유지한다. Leader에 장애가 발생하면, Follower 중 하나가 새로운 Leader로 승격(election)된다.

이 구조 덕분에 특정 Broker가 다운되더라도 데이터 유실 없이 서비스를 지속할 수 있다.

Topic: order-events (Partition 0)

Broker 1: [Leader]     Producer/Consumer 요청 처리
Broker 2: [Follower]   Leader 데이터 복제
Broker 3: [Follower]   Leader 데이터 복제

Replication Factor

복제 팩터(Replication Factor) 는 각 파티션이 몇 개의 Broker에 복제될지를 결정하는 설정이다. 예를 들어, Replication Factor가 3이면 파티션 데이터가 3개의 Broker에 존재하게 된다 (Leader 1 + Follower 2).

복제 팩터를 높이면 내결함성(Fault Tolerance) 이 향상되지만, 그만큼 저장 공간과 네트워크 대역폭을 더 소비한다. 프로덕션 환경에서는 일반적으로 3을 권장한다. Broker 1대가 장애를 일으켜도, 나머지 2대의 복제본으로 데이터 무결성을 유지할 수 있기 때문이다.

ISR (In-Sync Replicas)

모든 Follower가 항상 Leader와 완벽히 동기화되어 있는 것은 아니다. Kafka는 ISR(In-Sync Replicas) 이라는 개념으로 현재 Leader와 동기화 상태인 복제본 목록을 관리한다.

Follower가 일정 시간 내에 Leader의 데이터를 따라잡지 못하면 ISR에서 제거된다. 이는 Leader 장애 시 데이터 유실 가능성이 낮은 Follower만을 새로운 Leader 후보로 삼기 위한 메커니즘이다.


Producer: 메시지 발행

기본 동작

프로듀서(Producer) 는 Kafka에 메시지를 발행하는 클라이언트 애플리케이션이다. Producer가 메시지를 보내는 과정은 다음과 같다.

  1. 메시지를 보낼 토픽을 지정한다.
  2. (선택) 파티션 키를 지정하여 메시지가 저장될 파티션을 결정한다.
  3. Kafka 클러스터에 메시지를 전송하면, 해당 파티션의 Leader Broker가 이를 수신한다.
  4. Leader가 메시지를 저장한 후, 설정에 따라 확인 응답(Acknowledgment) 을 Producer에게 반환한다.

acks 설정: 신뢰성과 성능의 트레이드오프

Producer에서 가장 중요한 설정 중 하나가 acks(Acknowledgments) 다. 이 값은 Producer가 메시지 전송을 "성공"으로 간주하기 위해, 몇 개의 Broker로부터 확인을 받을 것인지를 결정한다.

acks동작장단점
0Broker의 응답을 기다리지 않는다가장 빠르지만 메시지 유실 가능성이 높다
1Leader가 저장을 완료하면 성공으로 간주한다적절한 균형. 단, Leader 장애 시 Follower에 아직 복제되지 않은 메시지는 유실될 수 있다
all (-1)Leader + 모든 ISR이 저장을 완료해야 성공으로 간주한다가장 안전하지만 지연 시간(Latency)이 가장 길다

acks=all은 데이터 유실이 치명적인 금융, 결제 시스템에서 주로 사용한다. 반면, 실시간 클릭 로그처럼 일부 유실이 허용되는 경우에는 acks=1이나 acks=0으로 처리량(Throughput) 을 우선시할 수 있다.

배치 전송과 압축

Producer는 메시지를 하나씩 보내지 않고, 배치(Batch) 로 묶어서 전송한다. 네트워크 요청 횟수를 줄여 처리량을 크게 향상시키는 전략이다.

배치와 관련된 주요 설정은 두 가지다.

  • batch.size: 배치의 최대 크기(바이트). 이 크기에 도달하면 즉시 전송한다.
  • linger.ms: 배치가 가득 차지 않아도 전송을 시작하는 대기 시간. 기본값은 0이며, 값을 늘리면 더 큰 배치를 만들어 처리량이 향상되지만 지연 시간이 증가한다.

추가로, Kafka는 메시지를 압축(Compression) 하여 전송할 수 있다. gzip, snappy, lz4, zstd 등의 압축 알고리즘을 지원하며, 네트워크 대역폭과 디스크 사용량을 절약할 수 있다.


Consumer & Consumer Group

Consumer: 메시지 소비

컨슈머(Consumer) 는 토픽을 구독하여 메시지를 읽는 클라이언트 애플리케이션이다. Consumer는 pull 방식으로 동작한다. Broker가 Consumer에게 메시지를 밀어넣는 것이 아니라, Consumer가 원하는 시점에 Broker에게 메시지를 요청(poll)한다.

Pull 방식의 장점은 Consumer가 자신의 처리 속도에 맞게 메시지를 가져갈 수 있다는 것이다. 처리가 느린 Consumer가 있더라도 Broker에 부하를 주지 않으며, 빠른 Consumer는 가능한 한 빠르게 메시지를 소비한다.

Consumer Group: 병렬 소비의 핵심

컨슈머 그룹(Consumer Group) 은 하나 이상의 Consumer가 논리적으로 묶인 그룹이다. 같은 Consumer Group에 속한 Consumer들은 하나의 토픽을 분담하여 처리한다.

핵심 규칙은 다음과 같다. 하나의 파티션은 Consumer Group 내에서 단 하나의 Consumer에만 할당된다.

Topic: order-events (3 Partitions)

Consumer Group A:
  Consumer 1  Partition 0
  Consumer 2  Partition 1
  Consumer 3  Partition 2

Consumer Group B:
  Consumer 4  Partition 0, 1, 2

위 예시에서 Consumer Group A는 3개의 Consumer가 각각 1개의 파티션을 담당하므로, 3배의 처리량을 얻는다. Consumer Group B는 Consumer가 1개뿐이므로, 혼자서 3개 파티션 전부를 처리한다.

여기서 중요한 점이 있다. Consumer 수가 파티션 수보다 많으면, 초과된 Consumer는 아무 파티션도 할당받지 못하고 유휴 상태가 된다. 따라서 Consumer Group의 최대 병렬도는 파티션 수에 의해 결정된다. 토픽 설계 시 예상되는 최대 Consumer 수를 고려하여 파티션 수를 설정해야 한다.

한편, 서로 다른 Consumer Group은 같은 토픽의 데이터를 독립적으로 소비한다. Group A가 메시지를 읽어도 Group B의 오프셋에는 영향을 주지 않는다. 이 덕분에 하나의 토픽 데이터를 실시간 분석, 검색 인덱싱, 알림 전송 등 여러 목적으로 동시에 활용할 수 있다.

리밸런싱 (Rebalancing)

Consumer Group에 Consumer가 추가되거나 제거되면, 파티션 할당을 재조정해야 한다. 이 과정을 리밸런싱(Rebalancing) 이라고 한다.

리밸런싱이 발생하는 상황은 다음과 같다.

  • Consumer Group에 새로운 Consumer가 합류했을 때
  • 기존 Consumer가 장애로 이탈했을 때 (heartbeat 미수신)
  • 토픽의 파티션 수가 변경되었을 때

리밸런싱이 진행되는 동안 해당 Consumer Group의 메시지 소비가 일시 중단될 수 있다. 이는 Kafka 운영에서 주의해야 할 부분이며, 불필요한 리밸런싱을 최소화하는 것이 중요하다. session.timeout.msheartbeat.interval.ms 설정을 적절히 조정하면 불필요한 리밸런싱을 줄일 수 있다.

오프셋 커밋 (Offset Commit)

Consumer가 메시지를 어디까지 읽었는지를 Kafka에 기록하는 행위를 오프셋 커밋(Offset Commit) 이라고 한다. 커밋된 오프셋 정보는 Kafka 내부 토픽인 __consumer_offsets에 저장된다.

오프셋 커밋 방식에는 두 가지가 있다.

  • 자동 커밋(Auto Commit): enable.auto.commit=true로 설정하면, 일정 간격(auto.commit.interval.ms)마다 자동으로 오프셋을 커밋한다. 편리하지만, 커밋 후 처리 실패 시 메시지 유실 가능성이 있고, 처리 완료 전 커밋되지 않으면 중복 처리가 발생할 수 있다.
  • 수동 커밋(Manual Commit): 애플리케이션 코드에서 메시지 처리 완료 후 명시적으로 commitSync() 또는 commitAsync()를 호출한다. 정확한 오프셋 관리가 가능하지만, 코드 복잡도가 증가한다.

데이터 정합성이 중요한 시스템에서는 수동 커밋이 일반적이다. 메시지를 처리한 후에 커밋하면, 장애 발생 시 마지막 커밋 지점부터 재처리하여 유실을 방지할 수 있다.


정리

Apache Kafka는 대규모 실시간 데이터 처리를 위한 분산 이벤트 스트리밍 플랫폼이다. 핵심 구성 요소를 다시 정리하면 다음과 같다.

  • Topic & Partition: 메시지를 논리적으로 분류하고, 파티션을 통해 병렬 처리와 확장성을 확보한다. 순서 보장은 파티션 단위로 이루어진다.
  • Broker: 클러스터를 구성하는 서버 노드로, Leader-Follower 복제를 통해 고가용성을 보장한다.
  • Producer: acks 설정으로 신뢰성과 성능의 균형을 조절하고, 배치 전송과 압축으로 처리량을 최적화한다.
  • Consumer & Consumer Group: Consumer Group을 통해 파티션을 분담하여 병렬로 소비하며, 오프셋 커밋으로 정확한 메시지 추적을 보장한다.

Kafka를 설계할 때 가장 중요한 판단은 결국 트레이드오프다. 파티션 수, 복제 팩터, acks 설정, 오프셋 커밋 방식 등 모든 설정은 처리량, 지연 시간, 내결함성 사이의 균형점을 찾는 과정이다. 시스템의 요구 사항에 맞게 이 균형을 조절하는 것이 Kafka 운영의 핵심이다.