Kafka Shallow Dive - 아키텍처 — Broker, Topic, Partition

Published:

1주차에 “append-only 로그”라고 정리했으니, 이번 주는 그 로그가 실제로 어떻게 생겼고 어디에 저장되는지 들여다보자.

클러스터 전체 그림

        ┌──────────── Kafka Cluster ────────────┐
        │                                        │
 Producer ─▶  Broker 1     Broker 2     Broker 3
        │     ├ orders-0    ├ orders-1    ├ orders-2  (leader)
        │     └ orders-2    └ orders-0    └ orders-1  (follower)
        │                                        │
        └─── Controller (KRaft quorum) ──────────┘
                      ▲
 Consumer ────────────┘
  • Broker — Kafka 서버 한 대. 토픽의 파티션을 나눠서 들고 있음
  • Controller — 클러스터 메타데이터(토픽/파티션/리더)를 관리하는 특별한 역할. 예전엔 ZooKeeper였고 지금은 KRaft
  • Producer/Consumer — 브로커 몇 대만 알려주면(bootstrap servers) 나머지 메타데이터는 자동으로 받아옴

Topic ?

이벤트를 분류하는 논리적 이름. orders, clicks, user.signup 처럼 이름을 붙임.

  • Topic은 생성 시 파티션 수(partitions)복제 계수(replication factor)를 정함
  • Topic 자체는 파일이 아니라 “파티션들의 집합”일 뿐
kafka-topics.sh --bootstrap-server localhost:9092 \
  --create --topic orders \
  --partitions 6 --replication-factor 3

Partition ?

Topic을 나눈 물리적 로그. Kafka의 모든 보장(순서, 복제, 병렬성)은 파티션 단위로 걸려 있다.

orders topic (partitions=3)

orders-0:  [ msg0 | msg1 | msg2 | msg3 | ... ]
orders-1:  [ msg0 | msg1 | msg2 | ... ]
orders-2:  [ msg0 | msg1 | msg2 | msg3 | msg4 | ... ]
           └────────── append only ──────────▶
  • 순서 보장 — 한 파티션 내부에서만. 파티션 간 순서는 보장 안 됨
  • 병렬성 — 파티션 수 = consumer group의 최대 병렬성
  • 오프셋 — 파티션별로 0부터 단조 증가하는 정수. 메시지의 위치 ID

왜 나누냐? 한 노드가 못 받는 트래픽을 여러 브로커에 흩뿌리고, 소비 쪽도 병렬로 돌리려고.

Partition의 파일 구조

브로커의 log.dirs 밑에 <topic>-<partition>/ 디렉토리가 생김. 그 안에 “세그먼트 파일” 여러 개.

/var/lib/kafka/data/orders-0/
├── 00000000000000000000.log       # 실제 메시지
├── 00000000000000000000.index     # offset → 파일 위치
├── 00000000000000000000.timeindex # 타임스탬프 → offset
├── 00000000000000123456.log       # 롤링되어 생긴 다음 세그먼트
├── 00000000000000123456.index
└── leader-epoch-checkpoint
  • 파티션 = 세그먼트(segment)들의 체인
  • 세그먼트가 일정 크기(log.segment.bytes, 기본 1GB)나 시간(log.roll.ms)이 되면 새 파일로 롤링
  • 읽기는 .index.log 2단계 lookup. mmap 기반이라 빠름

보존 정책(retention):

  • log.retention.hours — 시간 기반 (기본 168시간 = 7일)
  • log.retention.bytes — 용량 기반
  • log compaction — 같은 key의 최신 값만 남김 (e.g. __consumer_offsets)
일반 topic   → 시간/용량 지나면 세그먼트째로 삭제
compacted   → key별로 마지막 값 유지 (스냅샷 용도)

Offset ?

파티션 내 메시지 위치. 2가지 얼굴이 있음.

관점의미
Log end offset (LEO)파티션에 지금까지 쓰인 메시지 수
High watermark (HW)모든 replica가 복제 완료한 지점 (consumer가 읽을 수 있는 한계)
Consumer offset특정 consumer group이 어디까지 읽었는지

Consumer offset은 Kafka 내부 topic인 __consumer_offsets 에 compacted log로 저장됨. Kafka가 자기 자신을 먹는다.

Replication 맛보기 (자세한 건 Replication 편)

replication-factor=3이면 파티션 하나가 브로커 3대에 복제됨. 그 중 한 대가 Leader, 나머지는 Follower.

  • 모든 read/write는 leader가 처리
  • follower는 leader에서 데이터를 계속 fetch해서 따라붙음
  • leader가 죽으면 follower 중 하나가 leader로 승격
orders-0 (RF=3):
  Broker 1: Leader   ⇦ Producer/Consumer 요청
  Broker 2: Follower ⇠ fetch
  Broker 3: Follower ⇠ fetch

ZooKeeper vs KRaft

예전 Kafka는 메타데이터(토픽/파티션/ACL/컨트롤러 선거)를 ZooKeeper에 저장했음. 문제가 많았음.

구분ZooKeeper 모드KRaft 모드
메타데이터 저장별도 ZK 클러스터Kafka 내부 (Raft quorum)
운영 복잡도ZK + Kafka 이중 관리Kafka만
메타데이터 확장성ZK 한계에 걸림수백만 파티션도 OK
컨트롤러 장애 복구수 초~수십 초수백 ms
상태3.x부터 deprecated3.3+에서 GA, 4.0에서 기본

현재 시점(2025)엔 신규 클러스터는 무조건 KRaft. ZK는 레거시 이관용.

KRaft에서 브로커는 역할을 가짐:

  • process.roles=broker — 데이터만 저장
  • process.roles=controller — 메타데이터 관리만
  • process.roles=broker,controller — 둘 다 (개발/소규모)

메시지 구조

한 record가 대충 이렇게 생겼음:

┌──────────────────────────┐
│ key (optional)           │  → 파티셔닝에 사용
│ value                    │  → 실제 payload
│ headers (optional)       │  → 메타데이터 (trace id 등)
│ timestamp                │  → producer가 찍거나 broker가 찍음
│ offset                   │  → 브로커가 매김
│ partition                │  → producer/브로커가 결정
└──────────────────────────┘

Key를 잘 설계하는 게 중요함. 같은 key는 같은 파티션에 감 → 순서가 보장됨. 예: user_id를 key로 쓰면 한 유저의 이벤트는 순서대로 처리됨.

클러스터 한 번 훑어보기

# 클러스터 메타데이터
kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --status

# 토픽 상세 (파티션/리더/ISR)
kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic orders

# 예시 출력
# Topic: orders  PartitionCount: 3  ReplicationFactor: 3
#   Topic: orders  Partition: 0  Leader: 1  Replicas: 1,2,3  Isr: 1,2,3
#   Topic: orders  Partition: 1  Leader: 2  Replicas: 2,3,1  Isr: 2,3,1
#   Topic: orders  Partition: 2  Leader: 3  Replicas: 3,1,2  Isr: 3,1,2

Isr (In-Sync Replicas)가 leader와 동기화된 replica 목록. 숫자가 줄어들기 시작하면 어딘가 따라붙지 못하는 브로커가 있다는 신호.

정리

Topic은 논리, Partition은 물리. 파티션은 세그먼트 파일의 체인으로 디스크에 쌓이고, consumer는 offset으로 자기 위치를 기억한다. KRaft로 오면서 ZooKeeper라는 족쇄도 풀렸다. 파일 시스템 수준에서 감이 잡히니 “로그”라는 말이 추상적이지 않아졌다.

다음 주는 Producer — 메시지를 어떻게 파티션에 뿌리고 acks로 어떻게 내구성을 맞추는지.