Kafka의 핵심 아키텍처를 심층적으로 분석합니다. KRaft 모드, 브로커와 파티션 레플리케이션, 프로듀서 전송 보장, 컨슈머 그룹과 리밸런싱까지 Kafka의 내부를 이해합니다.
acks 설정)을 이해합니다.Apache Kafka는 분산 이벤트 스트리밍 플랫폼입니다. LinkedIn에서 2011년 오픈소스로 공개된 이후, 사실상 이벤트 스트리밍의 표준으로 자리잡았습니다.
Kafka의 핵심 설계 철학은 다음과 같습니다.
Kafka는 오랫동안 Apache ZooKeeper에 의존하여 클러스터 메타데이터를 관리했습니다. 브로커 등록, 파티션 리더 선출, 토픽 설정 등을 ZooKeeper가 담당했습니다.
그러나 이 구조에는 여러 문제가 있었습니다.
**KRaft(Kafka Raft)**는 Kafka 자체에 Raft 기반 합의 프로토콜을 내장하여, ZooKeeper 없이 메타데이터를 관리하는 모드입니다.
KRaft 모드의 핵심 개념은 메타데이터를 이벤트 로그로 관리한다는 것입니다. 토픽 생성, 파티션 재배치, 설정 변경 등 모든 메타데이터 변경이 __cluster_metadata라는 내부 토픽에 이벤트로 기록됩니다. Kafka가 자기 자신의 원리를 메타데이터 관리에도 적용한 셈입니다.
| 항목 | ZooKeeper 모드 | KRaft 모드 |
|---|---|---|
| 운영 대상 | Kafka + ZooKeeper | Kafka만 |
| 파티션 상한 | 약 20만 | 수백만 |
| 컨트롤러 장애 복구 | 수 분 | 수 초 |
| 메타데이터 전파 | 비동기, 비일관 가능 | 로그 기반, 일관성 보장 |
2026년 현재, Kafka의 새 배포에서는 KRaft 모드가 기본입니다. 기존 ZooKeeper 모드에서의 마이그레이션 도구도 제공되므로, 아직 ZooKeeper를 사용하고 있다면 마이그레이션을 계획해야 합니다.
**Broker(브로커)**는 Kafka 클러스터를 구성하는 개별 서버입니다. 각 브로커는 여러 파티션의 데이터를 저장하고, 프로듀서의 쓰기 요청과 컨슈머의 읽기 요청을 처리합니다.
Kafka는 데이터 내구성을 위해 각 파티션을 여러 브로커에 **Replica(레플리카)**로 복제합니다.
토픽: orders (파티션 3개, 레플리카 팩터 3)
브로커 1 브로커 2 브로커 3
P0: Leader Follower Follower
P1: Follower Leader Follower
P2: Follower Follower Leader리더가 분산 배치되어 부하가 균등하게 분배됩니다.
**ISR(In-Sync Replicas)**은 리더와 동기화가 완료된 레플리카의 집합입니다. 팔로워가 리더의 데이터를 일정 시간 내에 복제하지 못하면 ISR에서 제외됩니다.
ISR은 데이터 안전성과 직결됩니다. 리더가 장애를 겪으면 ISR에 속한 팔로워 중 하나가 새 리더로 승격됩니다. ISR에 속하지 않은 팔로워는 데이터가 불완전할 수 있으므로, 기본 설정에서는 리더 후보에서 제외됩니다.
파티션 0의 상태:
리더(브로커 1): offset 0~100 (최신)
팔로워(브로커 2): offset 0~100 (ISR - 동기화 완료)
팔로워(브로커 3): offset 0~95 (ISR에서 제외 - 5개 뒤처짐)unclean.leader.election.enable 설정은 ISR이 비어있을 때 동기화되지 않은 레플리카를 리더로 승격할지 결정합니다. 활성화하면 가용성이 높아지지만 데이터 유실 위험이 있습니다. 데이터 정확성이 중요한 시스템에서는 반드시 비활성화(기본값)로 유지해야 합니다.
프로듀서의 acks 설정은 "메시지가 성공적으로 기록되었다"의 기준을 결정합니다.
acks=0: 프로듀서가 브로커의 응답을 기다리지 않습니다. 전송 즉시 성공으로 간주합니다. 가장 빠르지만 데이터 유실 가능성이 높습니다.
Properties props = new Properties();
props.put("acks", "0");
// 브로커 응답을 기다리지 않음 — 최대 처리량, 유실 가능acks=1: 리더 레플리카에 기록되면 성공으로 응답합니다. 리더 장애 시 팔로워에 아직 복제되지 않은 데이터가 유실될 수 있습니다.
acks=all (또는 -1): 모든 ISR 레플리카에 복제가 완료되면 성공으로 응답합니다. 가장 안전하지만 지연 시간이 늘어납니다.
Properties props = new Properties();
props.put("acks", "all");
props.put("min.insync.replicas", "2");
// ISR 중 최소 2개 레플리카에 기록되어야 성공
// 레플리카 팩터 3 + min.insync.replicas 2 → 1대 장애 허용| acks | min.insync.replicas | 의미 |
|---|---|---|
| 0 | - | 응답 안 기다림, 최대 속도 |
| 1 | - | 리더만 확인, 리더 장애 시 유실 가능 |
| all | 1 | 리더만 있어도 성공 (acks=1과 동일) |
| all | 2 | 리더 + 1개 팔로워 확인, 1대 장애 허용 |
| all | 3 | 3개 모두 확인, 가장 안전하지만 느림 |
프로덕션 환경에서의 권장 설정은 레플리카 팩터 3, acks=all, min.insync.replicas=2입니다. 이 조합은 1대의 브로커 장애를 허용하면서도 데이터 유실을 방지합니다.
**Consumer Group(컨슈머 그룹)**은 하나의 토픽을 협력하여 소비하는 컨슈머들의 논리적 그룹입니다. 토픽의 각 파티션은 그룹 내 정확히 하나의 컨슈머에게 할당됩니다.
토픽: orders (6개 파티션)
컨슈머 그룹: order-processor (3개 컨슈머)
컨슈머 1 ← P0, P1
컨슈머 2 ← P2, P3
컨슈머 3 ← P4, P5컨슈머 수가 파티션 수보다 많으면 초과 컨슈머는 유휴 상태가 됩니다. 따라서 컨슈머 수는 파티션 수 이하로 유지하는 것이 효율적입니다.
서로 다른 컨슈머 그룹은 같은 토픽을 독립적으로 소비합니다. 주문 이벤트를 분석 시스템(그룹 A)과 알림 시스템(그룹 B)이 각각 독립적으로 처리하는 것이 가능합니다.
컨슈머가 그룹에 합류하거나 떠나면, 파티션 할당이 재조정됩니다. 이를 **Rebalancing(리밸런싱)**이라고 합니다.
리밸런싱이 발생하는 상황은 다음과 같습니다.
전통적인 **Eager Rebalancing(즉시 리밸런싱)**에서는 리밸런싱이 시작되면 모든 컨슈머가 모든 파티션을 반납하고 새로 할당받습니다. 이 동안 전체 소비가 중단됩니다.
**Cooperative Rebalancing(협력적 리밸런싱)**은 이 문제를 해결합니다. 변경이 필요한 파티션만 재할당하고, 나머지 파티션은 계속 소비합니다.
[Eager 방식]
1단계: 모든 컨슈머가 모든 파티션 반납 (전체 중단)
2단계: 새로운 할당 계산
3단계: 모든 파티션 재할당 (소비 재개)
[Cooperative 방식]
1단계: 이동이 필요한 파티션만 해당 컨슈머가 반납 (나머지는 계속 소비)
2단계: 반납된 파티션을 새 컨슈머에게 할당
→ 중단 시간 최소화Properties props = new Properties();
props.put("partition.assignment.strategy",
"org.apache.kafka.clients.consumer.CooperativeStickyAssignor");
// 협력적 리밸런싱 + 기존 할당 유지 선호프로덕션 환경에서는 CooperativeStickyAssignor를 사용하는 것을 권장합니다. 리밸런싱 중 소비 중단을 최소화하고, 기존 파티션 할당을 최대한 유지하여 캐시 효율도 높입니다.
Kafka가 높은 처리량을 달성하는 핵심 기술을 간략히 살펴보겠습니다.
Kafka는 데이터를 디스크에 순차적으로 기록합니다. 순차 디스크 I/O는 랜덤 I/O보다 수십~수백 배 빠르며, 경우에 따라 메모리 접근 속도에 근접합니다.
프로듀서가 보낸 데이터를 컨슈머에게 전달할 때, 커널 영역에서 사용자 영역으로의 불필요한 복사를 건너뛰는 sendfile() 시스템 콜을 사용합니다. 이를 통해 CPU 사용량과 메모리 복사 비용을 줄입니다.
Kafka는 자체 캐시를 구현하지 않고 OS의 **Page Cache(페이지 캐시)**에 의존합니다. 최신 데이터는 대부분 페이지 캐시에 존재하므로, 컨슈머가 최신 데이터를 읽을 때 디스크 I/O 없이 메모리에서 직접 전송합니다.
프로듀서는 여러 메시지를 하나의 배치로 묶어 전송하고, 컨슈머도 여러 메시지를 한 번에 가져옵니다. 네트워크 라운드트립과 I/O 횟수를 줄여 처리량을 높입니다.
배치 단위로 gzip, snappy, lz4, zstd 등의 압축을 적용하여 네트워크 대역폭과 디스크 사용량을 줄입니다.
이번 장에서는 Apache Kafka의 핵심 아키텍처를 심층적으로 살펴보았습니다.
4장에서는 Kafka 프로듀서와 컨슈머의 고급 패턴을 다룹니다. Idempotent 프로듀서와 트랜잭셔널 프로듀서를 통한 Exactly-once 시맨틱스, 수동 오프셋 관리, 배치 크기와 압축 최적화, Dead Letter Queue 패턴까지 프로덕션 수준의 Kafka 활용법을 학습합니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
Idempotent 프로듀서, 트랜잭셔널 프로듀서, Exactly-once 시맨틱스, 수동 오프셋 관리, 배치 최적화, Dead Letter Queue 등 프로덕션 수준의 Kafka 활용 패턴을 학습합니다.
이벤트 로그, 토픽과 파티션, 오프셋 관리, 이벤트 시간과 처리 시간의 차이, 워터마크, 윈도우 연산 등 스트림 처리의 핵심 개념을 체계적으로 학습합니다.
Kafka Connect의 Source/Sink 아키텍처, 주요 커넥터 활용, 분산 모드 운영, SMT를 통한 데이터 변환, 커스텀 커넥터 개발까지 Kafka 기반 데이터 통합 전략을 학습합니다.