Kafka 핵심 정리

Kafka를 처음 보면 메시지 큐(Message Queue)처럼 보인다.

실제로 메시지를 Producer가 넣고 Consumer가 가져가는 구조를 사용하기 때문에 RabbitMQ 같은 메시지 브로커와 비슷한 역할을 한다.

다만 Kafka를 단순히 메시지를 전달하고 삭제하는 큐로 이해하면 구조가 잘 잡히지 않는다.

Kafka는 대규모 이벤트와 데이터를 지속적으로 기록하고, 여러 Consumer가 독립적으로 읽을 수 있도록 만든 분산 이벤트 스트리밍 플랫폼이다.

핵심은 메시지를 중앙에서 전달하는 것보다 이벤트를 로그 형태로 저장하고 Consumer가 자신의 Offset을 기준으로 읽는 구조에 있다.

전체적인 흐름은 다음과 같다.

Producer -> Kafka Cluster -> Topic -> Partition -> Consumer Group -> Consumer

Kafka가 필요한 이유

서버가 다른 서버를 직접 호출하는 구조에서는 서비스 사이의 결합도가 높아진다.

예를 들어 주문이 완료된 뒤 여러 작업이 필요하다고 하자.

주문 완료 -> 결제 처리
주문 완료 -> 재고 차감
주문 완료 -> 이메일 발송
주문 완료 -> 통계 처리
주문 완료 -> 알림 발송

이런 구조를 주문 서버가 직접 관리하면 서비스가 늘어날수록 의존성이 복잡해진다.

Kafka를 중간에 두면 구조를 바꿀 수 있다.

Order Server -> Kafka -> Payment Consumer
Order Server -> Kafka -> Inventory Consumer
Order Server -> Kafka -> Mail Consumer
Order Server -> Kafka -> Statistics Consumer
Order Server -> Kafka -> Notification Consumer

실제로는 하나의 이벤트를 Kafka Topic에 기록하고 여러 Consumer Group이 이를 독립적으로 소비한다.

Order Server -> order-events -> Payment Group
Order Server -> order-events -> Inventory Group
Order Server -> order-events -> Statistics Group
Order Server -> order-events -> Notification Group

주문 서버는 “주문이 완료됐다”는 이벤트를 기록하고, 이후 어떤 서비스가 이 이벤트를 사용할지는 각 Consumer가 결정한다.

이렇게 Producer와 Consumer의 결합도를 낮출 수 있다.


Producer와 Consumer

Kafka에서 데이터를 넣는 쪽을 Producer, 데이터를 읽는 쪽을 Consumer라고 한다.

기본적인 흐름은 다음과 같다.

Producer -> Topic -> Consumer

Producer는 특정 Topic에 이벤트를 기록한다.

Consumer는 Topic의 데이터를 읽어 실제 작업을 수행한다.

중요한 점은 Consumer 하나가 데이터를 읽었다고 해서 다른 Consumer가 같은 이벤트를 읽지 못하는 구조가 아니라는 것이다.

Consumer Group이 다르면 같은 이벤트를 각각 독립적으로 소비할 수 있다.


Topic

Topic은 Kafka에서 이벤트를 논리적으로 구분하는 단위다.

예를 들어 다음과 같이 Topic을 구성할 수 있다.

order-events
payment-events
user-events
game-events

Producer는 특정 Topic에 이벤트를 기록하고 Consumer는 필요한 Topic을 읽는다.

Topic은 실제 데이터를 저장하는 하나의 단순한 파일이 아니라 여러 Partition으로 구성될 수 있다.

Topic -> Partition 0
Topic -> Partition 1
Topic -> Partition 2

Partition

Kafka의 성능과 확장성을 이해하려면 Partition을 알아야 한다.

하나의 Topic은 여러 Partition으로 나눌 수 있다.

Topic -> Partition 0
Topic -> Partition 1
Topic -> Partition 2

Producer가 이벤트를 보내면 Kafka는 특정 Partition에 이벤트를 기록한다.

Partition은 Kafka에서 데이터를 분산하고 병렬 처리하기 위한 핵심 단위다.

예를 들어 Partition이 3개이고 Consumer도 3개라면 다음과 같이 나눠 처리할 수 있다.

Partition 0 -> Consumer 1
Partition 1 -> Consumer 2
Partition 2 -> Consumer 3

따라서 Kafka는 많은 데이터를 여러 Consumer가 나누어 처리할 수 있다.


Offset

Partition 안의 각 이벤트에는 순서를 나타내는 Offset이 존재한다.

Partition -> Offset 0 -> OrderCreated
Partition -> Offset 1 -> OrderCreated
Partition -> Offset 2 -> OrderCancelled
Partition -> Offset 3 -> OrderCreated
Partition -> Offset 4 -> OrderCreated

Consumer는 자신이 어디까지 읽었는지를 Offset으로 관리한다.

예를 들어 Offset 2까지 처리했다면 다음 처리 위치는 Offset 3이다.

Consumer -> Offset 0 처리 -> Offset 1 처리 -> Offset 2 처리 -> Offset 3 처리

Kafka에서 중요한 점은 Consumer가 이벤트를 읽었다고 해서 해당 이벤트가 즉시 삭제되는 것이 아니라는 것이다.

Kafka는 설정된 Retention 정책에 따라 이벤트를 일정 기간 저장할 수 있다.

따라서 필요한 경우 Consumer가 과거 Offset부터 다시 읽을 수도 있다.

이 구조가 Kafka를 일반적인 메시지 큐와 구분하는 핵심 중 하나다.


Consumer Group

Consumer Group은 Kafka의 병렬 처리와 독립적인 이벤트 소비를 이해하는 데 중요한 개념이다.

같은 Consumer Group에 속한 Consumer들은 Partition을 나누어 처리한다.

Topic -> Partition 0 -> Consumer 1
Topic -> Partition 1 -> Consumer 2
Topic -> Partition 2 -> Consumer 3

따라서 같은 Group의 여러 Consumer가 동일한 이벤트를 각각 처리하는 구조가 아니다.

반대로 Consumer Group이 다르면 같은 Topic의 이벤트를 각각 독립적으로 소비할 수 있다.

Topic -> Consumer Group A
Topic -> Consumer Group B
Topic -> Consumer Group C

예를 들어 주문 이벤트라면 다음과 같이 구성할 수 있다.

order-events -> payment-group
order-events -> statistics-group
order-events -> notification-group

각 Group은 같은 주문 이벤트를 자신의 목적에 맞게 처리한다.


Kafka의 데이터 처리 방식

Kafka를 이해할 때 일반적인 메시지 큐와 비교하면 차이가 명확해진다.

일반적인 메시지 큐에서는 Consumer가 메시지를 처리하면 큐에서 제거하는 구조를 많이 사용한다.

Kafka는 이벤트를 Log 형태로 저장하고 Consumer가 Offset을 이동시키면서 읽는다.

Kafka Log -> Event 0 -> Event 1 -> Event 2 -> Event 3 -> Event 4

Consumer가 Event 3까지 처리했다고 해도 이전 이벤트가 즉시 삭제되는 것은 아니다.

Retention 정책에 따라 데이터가 Kafka에 남아 있을 수 있다.

따라서 Consumer가 필요한 경우 과거 이벤트를 다시 읽을 수 있다.

Consumer -> 과거 Offset 지정 -> Kafka -> 이벤트 재처리

이것이 Kafka의 핵심적인 특징이다.


Kafka Cluster

Kafka는 여러 Broker로 구성된 Cluster 형태로 운영할 수 있다.

Kafka Cluster -> Broker 1
Kafka Cluster -> Broker 2
Kafka Cluster -> Broker 3

Partition은 여러 Broker에 분산될 수 있다.

Partition 0 -> Broker 1
Partition 1 -> Broker 2
Partition 2 -> Broker 3

Partition에는 Replica를 구성할 수도 있다.

Partition 0 -> Leader Broker -> Replica Broker -> Replica Broker

Leader가 장애를 일으키면 Replica를 활용해 장애에 대응할 수 있다.

이런 구조를 통해 Kafka는 데이터를 여러 서버에 분산하고 장애 상황에도 서비스를 유지할 수 있도록 구성할 수 있다.


Producer와 Partition

Producer가 이벤트를 Kafka에 기록할 때 어느 Partition에 저장할 것인지도 중요하다.

대표적으로 Key를 사용할 수 있다.

Producer -> Key(userId) -> Kafka Partition

같은 Key를 가진 이벤트는 같은 Partition으로 보내도록 구성할 수 있다.

예를 들어 다음 이벤트가 있다고 하자.

playerId=100 -> PlayerCreated
playerId=100 -> PlayerLevelUp
playerId=100 -> PlayerItemAdded
playerId=100 -> PlayerDeleted

같은 Player의 이벤트를 동일한 Partition으로 보내면 해당 Partition 안에서 이벤트 순서를 유지할 수 있다.

playerId=100 -> Partition 1 -> PlayerCreated -> PlayerLevelUp -> PlayerItemAdded -> PlayerDeleted

Kafka에서 순서 보장은 Partition 단위로 이해하는 것이 중요하다.

Topic 전체에서 모든 이벤트의 전역 순서를 보장하는 구조는 아니다.


Partition과 병렬 처리

Partition 수를 늘리면 Consumer가 데이터를 병렬로 처리할 수 있는 기반이 커진다.

하지만 Partition을 무작정 늘리는 것이 좋은 것은 아니다.

게임 서버를 예로 들면 플레이어별 이벤트 순서가 중요할 수 있다.

playerId -> Partition -> Consumer

반대로 이벤트 순서보다 처리량이 중요하다면 더 많은 Partition을 활용해 병렬성을 높이는 방향을 선택할 수 있다.

결국 Partition 설계에서는 순서 보장과 병렬 처리 사이의 균형을 고려해야 한다.


Kafka의 주요 활용

Kafka는 서비스 사이에서 이벤트를 전달하는 용도로 많이 사용한다.

이벤트 기반 아키텍처

서비스 간 직접 호출을 줄이고 이벤트 중심으로 연결할 수 있다.

데이터 파이프라인

Kafka가 최종적으로 데이터를 분석하는 시스템이라기보다 여러 시스템으로 데이터를 전달하는 중심 스트림 역할을 하는 경우가 많다.

실시간 이벤트 처리

게임 이벤트, 사용자 행동, 거래 이벤트처럼 지속적으로 발생하는 데이터를 다른 시스템에서 처리할 수 있다.


Kafka와 RabbitMQ

Kafka와 RabbitMQ를 단순히 “둘 다 메시지 큐”라고 생각하면 중요한 차이를 놓치게 된다.

RabbitMQ는 전통적인 메시지 브로커 구조에 가깝고 메시지를 전달하고 처리하는 흐름에 초점이 맞춰져 있다.

Producer -> Exchange -> Queue -> Consumer

Kafka는 이벤트를 로그 형태로 저장하고 Consumer가 Offset을 기준으로 읽는다.

Producer -> Topic -> Partition -> Consumer Group -> Consumer

따라서 두 기술은 목적에 따라 선택해야 한다.

단순한 작업 큐가 필요한지, 대규모 이벤트 스트림을 여러 시스템에서 독립적으로 소비해야 하는지를 먼저 판단해야 한다.

어느 하나가 무조건 상위 기술인 것은 아니다.


Kafka에서 고려해야 할 것

Kafka를 도입한다고 시스템이 자동으로 좋아지는 것은 아니다.

오히려 운영해야 할 요소가 늘어난다.

가장 먼저 고려해야 하는 것이 중복 처리다.

Consumer가 이벤트를 처리한 뒤 Offset을 Commit하기 전에 장애가 발생하면 같은 이벤트가 다시 처리될 수 있다.

기본적인 흐름은 다음과 같다.

Kafka Event -> Consumer -> Business Logic -> 처리 성공 -> Offset Commit

하지만 처리 이후 Offset Commit 전에 장애가 발생하면 다음과 같은 상황이 생길 수 있다.

Kafka Event -> Consumer -> Business Logic -> 처리 성공 -> 장애 -> Offset 미반영

Consumer가 다시 실행되면 동일한 이벤트를 다시 받을 수 있다.

따라서 Kafka Consumer의 비즈니스 로직은 중복 처리에 안전한 구조로 설계하는 것이 중요하다.

이와 함께 다음 요소도 고려해야 한다.

Kafka -> Partition 수
Kafka -> Consumer 수
Kafka -> Replication
Kafka -> Retention
Kafka -> Consumer Lag

Kafka 전체 구조

Kafka의 전체 흐름을 하나로 묶으면 다음과 같다.

Producer -> Kafka Cluster -> Topic -> Partition -> Consumer Group -> Consumer

세부적으로 보면 다음과 같이 연결된다.

Producer -> Topic -> Partition -> Offset -> Consumer Group -> Consumer

여기에 분산 환경에서는 다음 구조가 추가된다.

Kafka Cluster -> Broker -> Partition -> Replica

전체 구조를 실제 이벤트 처리 관점에서 보면 다음과 같다.

Application -> Producer -> Kafka Cluster -> Topic -> Partition -> Consumer Group -> Consumer -> Business Logic

Consumer Group이 여러 개라면 같은 이벤트 스트림을 서로 다른 목적의 시스템에서 독립적으로 사용할 수 있다.


Kafka를 공부하면서 같이 알아야 할 것

Kafka는 단순한 라이브러리가 아니라 분산 시스템이다.

따라서 Kafka를 제대로 이해하려면 다음 개념을 같이 알아두는 것이 좋다.

Network -> TCP -> Distributed System -> Message Delivery -> Replication -> Partition -> Consumer Group -> Event Processing

특히 다음 개념이 중요하다.

Partition -> 분산 처리 단위
Offset -> 소비 위치
Consumer Group -> 병렬 소비 단위
Replication -> 장애 대응
Retention -> 데이터 보존 정책
Consumer Lag -> Consumer 처리 지연

Kafka의 동작만 외우기보다 왜 Partition과 Consumer Group이 필요한지, 왜 Offset을 따로 관리하는지 이해하는 것이 중요하다.


정리

Kafka를 이해하기 위해서는 개별 개념을 따로 외우기보다 하나의 흐름으로 연결해서 보는 것이 좋다.

Producer -> Topic -> Partition -> Offset -> Consumer Group -> Consumer

Producer는 이벤트를 Kafka에 기록한다.

Topic은 이벤트를 논리적으로 구분한다.

Partition은 데이터를 분산하고 병렬 처리를 가능하게 한다.

Offset은 Consumer의 읽기 위치를 나타낸다.

Consumer Group은 여러 Consumer가 Partition을 나누어 처리하도록 한다.

Consumer는 이벤트를 읽고 실제 비즈니스 로직을 수행한다.

Kafka Cluster와 Replica는 이런 구조를 여러 Broker에 분산하고 장애에 대응할 수 있도록 한다.

핵심 구조를 하나로 압축하면 다음과 같다.

Producer -> Kafka Cluster -> Topic -> Partition -> Consumer Group -> Consumer -> Business Logic

Kafka를 단순히 “메시지를 전달하는 서버”라고 생각하면 Partition, Offset, Consumer Group이 복잡하게 느껴진다.

반대로 이벤트를 분산된 로그에 기록하고 여러 Consumer가 각자의 Offset을 기준으로 독립적으로 읽는 시스템으로 보면 대부분의 구조가 자연스럽게 연결된다.

Kafka의 핵심은 메시지를 전달하는 것 자체가 아니다.

이벤트를 지속적으로 기록하고, 필요한 여러 시스템이 독립적으로 소비할 수 있도록 만드는 것에 있다.