포스트

Kafka 해부 - topic, partition, consumer group으로 이해하는 이벤트 스트리밍

Kafka가 생산자와 소비자를 분리하는 방식을 정리합니다. broker, topic, partition, consumer group, offset의 구조와 Docker 기반 첫 실행, console 도구 최소 예제, 운영 관행과 자주 밟는 함정을 다룹니다.

Kafka 해부 - topic, partition, consumer group으로 이해하는 이벤트 스트리밍

이벤트 스트리밍 플랫폼 Kafka의 구조와 사용법을 정리한 도구 해부 단편입니다. 기준 버전은 4.0입니다.

Kafka가 푸는 문제

데이터를 만드는 쪽(앱 서버, 로그, 센서)과 쓰는 쪽(DB 적재, 모니터링, 실시간 피처 계산)이 늘어나면 연결이 N:M으로 얽힙니다. Kafka는 그 사이에 이벤트 log를 두어 양쪽을 분리합니다. 생산자는 Kafka에 쓰기만 하고 소비자는 각자의 속도로 읽으며, Airflow 해부 1편에서 배치 스케줄링의 경계 밖이라고 했던 초 단위 이벤트 처리가 이 도구의 영역입니다.

설치와 첫 실행

로컬 실습은 공식 Docker image가 가장 간단합니다. container 실행이 처음이면 Docker 기초 시리즈를 먼저 봅니다.

1
2
docker run -d --name kafka -p 9092:9092 apache/kafka:4.0.0
docker logs kafka        # "Kafka Server started" 로그가 보이면 정상

검색하면 나오는 예전 자료의 ZooKeeper 구성은 필요 없습니다. Kafka는 4.0부터 ZooKeeper를 제거했고, 메타데이터 관리는 내장된 KRaft mode가 맡습니다. 위 한 줄로 단일 broker가 뜨고, 공식 image의 기본 설정이 localhost:9092로 잡혀 있어 로컬에서는 추가 설정 없이 접속됩니다.

구조

구성요소역할
brokerKafka 서버 프로세스. 여러 대가 cluster를 이룬다
topic이벤트의 분류 단위. orders, click-events처럼 이름을 붙인다
partitiontopic을 나눈 추가 전용 log. 병렬 처리와 순서 보장의 단위
producertopic에 이벤트를 쓰는 client
consumertopic에서 이벤트를 읽는 client
consumer groupconsumer의 묶음. group 안에서 partition을 나눠 맡는다
offsetpartition 안의 위치 번호. consumer가 어디까지 읽었는지의 기준

동작 원리는 메시지 큐와의 차이에서 잡힙니다. 일반적인 큐는 메시지를 꺼내면 지워지지만, Kafka는 이벤트를 추가 전용 log에 쌓고 읽어도 지우지 않습니다. 이벤트는 소비 여부와 무관하게 retention 기간 동안 보존되고, 어디까지 읽었는지는 broker가 아니라 consumer 쪽 offset이 관리합니다. 그래서 새 소비자를 붙여 과거 이벤트를 처음부터 다시 읽을 수 있고, 같은 topic을 여러 팀이 독립적으로 소비할 수 있습니다.

partition과 consumer group의 규칙 세 개가 실사용의 뼈대입니다.

  • 순서는 partition 안에서만 보장됩니다. topic 전체의 순서는 보장되지 않습니다. 순서가 필요한 이벤트(같은 주문, 같은 사용자)는 같은 key를 줘서 같은 partition으로 보냅니다
  • 같은 group의 consumer들은 partition을 나눠 맡습니다. consumer를 늘리면 처리가 분산되고, 하나가 죽으면 그 partition을 다른 consumer가 이어받습니다
  • 다른 group은 각자 전체를 읽습니다. 적재용 group과 모니터링용 group이 같은 topic을 독립적으로 소비하는 구성이 이것으로 가능합니다

최소 예제

console 도구로 producer와 consumer를 직접 돌려봅니다. 터미널 두 개를 씁니다.

1
2
3
4
5
6
7
8
9
10
11
# topic 생성
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
  --bootstrap-server localhost:9092 --create --topic events --partitions 3

# 터미널 1: producer. 입력하는 줄 하나가 이벤트 하나가 된다
docker exec -it kafka /opt/kafka/bin/kafka-console-producer.sh \
  --bootstrap-server localhost:9092 --topic events

# 터미널 2: consumer. --from-beginning으로 처음부터 읽는다
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 --topic events --from-beginning

producer 터미널에 입력한 줄이 consumer 터미널에 나타나면 전체 경로가 확인된 것입니다. 애플리케이션 코드에서는 Python이면 confluent-kafka 패키지의 Producer와 Consumer가 같은 역할을 합니다.

UI 훑기

Kafka에는 기본 web UI가 없고 CLI가 기본 창구입니다. 운영에서는 AKHQ 같은 서드파티 web UI를 붙여 topic 목록, partition별 offset, consumer group의 lag를 확인합니다. lag는 partition의 마지막 offset과 group이 읽은 offset의 차이로, 소비가 생산을 따라가고 있는지를 보여주는 핵심 지표입니다.

실전 운영

  • 단일 broker는 학습용입니다. 운영은 broker 3대 이상에 replication factor 3, min.insync.replicas=2, producer acks=all 조합이 유실 방지의 관행입니다
  • retention 기본값은 7일(log.retention.hours=168)입니다. 용량 기준(log.retention.bytes)과 병행할 수 있고, 디스크 계획은 이 설정에서 시작합니다
  • partition 수는 만들 때 정하고 이후 늘릴 수만 있습니다. 줄이는 방법은 없고, 늘리면 key 기반 라우팅이 재배치되므로 처음부터 여유 있게 잡습니다
  • cluster 직접 운영은 비용이 큽니다. AWS MSK나 Confluent Cloud 같은 관리형 서비스가 흔한 선택입니다
  • 배치 주기로 충분한 파이프라인이라면 Kafka 없이 Airflow가 단순합니다. Kafka는 “지금 일어난 이벤트에 초 단위로 반응해야 하는가”에 예라고 답할 때 들어옵니다

자주 밟는 함정

  1. container 밖에서 접속하면 처음엔 연결되고 그 뒤에 실패합니다. client는 최초 접속 후 broker가 알려주는 advertised listener 주소로 다시 접속하는데, 이 주소가 client 기준으로 닿을 수 없는 주소면 이후 통신이 전부 실패하기 때문입니다. 공식 image 기본값은 localhost:9092라 로컬 실습은 그대로 되지만, Compose network나 다른 머신에서 접속하려면 advertised listener를 client가 닿는 주소로 바꿔야 합니다
  2. 이벤트 처리 순서가 뒤섞입니다. partition이 여러 개인 topic에서 순서를 topic 전체 기준으로 기대한 것이 원인입니다. 순서가 필요한 단위마다 같은 key를 지정해 같은 partition으로 보내면 해결됩니다
  3. producer는 보냈는데 consumer에 아무것도 안 나옵니다. console consumer의 기본값이 최신 offset부터 읽기라서 접속 이전의 이벤트를 건너뛴 것입니다. --from-beginning을 붙이거나 group의 offset을 리셋해 확인합니다
  4. consumer를 늘렸는데 처리량이 그대로입니다. 같은 group에서 partition 수를 넘는 consumer는 유휴 상태로 남기 때문입니다. 병렬성의 상한은 consumer 수가 아니라 partition 수이므로, 처리량 계획은 partition 설계에서 시작합니다

관련 글

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.