- Kafka는 분산 시스템이다. 파티션마다 리더가 있고, 리더만 읽기/쓰기를 처리한다.
- Kafka에서 각 파티션은 여러 브로커에 복제된다. 복제본(replica) 중 하나가 리더, 나머지는 팔로워다.
- 프로듀서와 컨슈머는 리더하고만 통신한다.
컨트롤러
컨트롤러라는 특별한 브로커 하나는 다음 역할들을 담당한다:
- 파티션 리더 선출
- 브로커 장애 감지
- 파티션 재할당 조율
컨트롤러 자체도 선출된다. 여러 브로커가 동시에 컨트롤러가 되려고 경쟁하고, 하나만 성공한다.
ZooKeeper 모드에서는 /controller znode를 먼저 생성한 브로커가 컨트롤러가 된다. ephemeral node라서 해당 브로커가 죽으면 자동으로 삭제되고, 다른 브로커가 다시 경쟁한다.
KRaft 모드에서는 Raft 합의 알고리즘으로 컨트롤러 쿼럼 중 리더를 선출한다. ZooKeeper 의존성이 없어서 운영이 단순해진다.
ISR (In-Sync Replicas)
리더 선출을 이해할 때 ISR 개념이 중요하게 쓰인다. ISR은 “리더와 동기화된 복제본 집합”이다. 팔로워가 리더의 데이터를 제때 복제하고 있으면 ISR에 포함된다. 뒤처지면 ISR에서 빠진다.
Partition 0: Replicas: [1, 2, 3] ← 전체 복제본 (브로커 ID) ISR: [1, 2] ← 동기화된 복제본만 Leader: 1위 상황에서 broker 3은 복제가 뒤처져서 ISR에서 제외된 상태다.
ISR 판단 기준은 replica.lag.time.max.ms다. 이 시간 내에 리더에게 fetch 요청을 보내지 않으면 ISR에서 제외된다. 기본값은 30초.
왜 ISR이 중요할까? 리더가 죽으면 ISR 안에서만 새 리더를 뽑기 때문이다. ISR에 없는 복제본은 데이터가 뒤처져 있으니, 리더가 되면 최신 데이터를 잃을 수 있다.
리더 선출 과정
클러스터 시작 시
클러스터가 처음 시작되면:
- 브로커들이 올라온다
- 컨트롤러가 선출된다
- 컨트롤러가 각 파티션의 replica 목록을 확인한다
- replica 목록의 첫 번째 브로커를 리더로 지정한다 (preferred replica)
- LeaderAndIsr 요청을 통해 모든 브로커에게 알린다
# 파티션 생성 시 replica 할당 예시Partition 0: replicas=[1,2,3] → Leader=1 (preferred)Partition 1: replicas=[2,3,1] → Leader=2 (preferred)Partition 2: replicas=[3,1,2] → Leader=3 (preferred)라운드 로빈으로 리더가 분산되도록 replica 목록 순서를 다르게 배치한다.
리더 브로커 장애 시
리더가 죽으면 어떻게 될까? 실제 과정을 따라가보자.
상황: Broker 1(Leader)이 죽음Partition 0: Replicas: [1, 2, 3] ISR: [1, 2] Leader: 1 (dead)-
장애 감지: 컨트롤러가 broker 1의 장애를 감지한다
- ZooKeeper 모드:
/brokers/ids/1znode 삭제 감지 - KRaft 모드: heartbeat timeout
- ZooKeeper 모드:
-
영향받는 파티션 식별: broker 1이 리더인 모든 파티션을 찾는다
-
새 리더 선출: ISR에서 살아있는 첫 번째 브로커를 선택한다
ISR: [1, 2] → 1은 죽음 → 2가 새 리더 -
메타데이터 업데이트: 새로운 리더 정보를 저장한다
Partition 0:ISR: [2] ← 1 제거됨Leader: 2 ← 새 리더 -
브로커들에게 전파: LeaderAndIsr 요청으로 모든 브로커에게 알린다
-
클라이언트 갱신: 프로듀서/컨슈머가 메타데이터를 갱신하고 새 리더로 연결한다
전체 과정은 보통 수백 밀리초에서 수 초 내에 완료된다.
Unclean Leader Election
만약 ISR에 살아있는 복제본이 하나도 없다면?
Partition 0: Replicas: [1, 2, 3] ISR: [1] ← 리더만 ISR에 있었음 Leader: 1 (dead)broker 1이 죽었는데 ISR에 1밖에 없었다. broker 2, 3은 있지만 데이터가 뒤처져 있다.
두 가지 선택지가 있다:
- 파티션을 사용 불가 상태로 두기 (
unclean.leader.election.enable=false, 기본값)
- ISR에 있던 브로커가 복구될 때까지 기다린다
- 데이터 유실 없음
- 가용성 희생
- ISR 밖에서 리더 선출 (
unclean.leader.election.enable=true)
- broker 2나 3이 리더가 된다
- 서비스 계속 가능
- 복제되지 않은 메시지 유실
어떤 것을 선택할지는 상황에 따라 다르다. 금융 데이터처럼 손실이 치명적이면 false. 로그처럼 일부 유실이 괜찮으면 true.
Preferred Replica Election
시간이 지나면 리더 분포가 불균형해질 수 있다. 장애 복구 후 원래 리더가 돌아와도 자동으로 리더가 되지 않기 때문이다.
# 장애 전Partition 0: Leader=1Partition 1: Leader=2Partition 2: Leader=3
# Broker 1 장애 후 복구Partition 0: Leader=2 ← 1이 원래 리더였는데 2가 유지됨Partition 1: Leader=2Partition 2: Leader=3
# Broker 2에 리더가 몰림이를 해결하는 것이 preferred replica election이다.
auto.leader.rebalance.enable=true로 설정하면 컨트롤러가 주기적으로 확인해서, preferred replica(replica 목록의 첫 번째)가 리더가 아닌 파티션의 리더를 재조정한다.
수동으로도 할 수 있다:
kafka-leader-election.sh --bootstrap-server localhost:9092 \ --election-type PREFERRED \ --all-topic-partitionsKRaft
Kafka 3.3부터 ZooKeeper 없이 KRaft 모드로 운영 가능하다. 리더 선출 원리는 동일하지만 구현이 다르다.
-
단일 컨트롤러 대신 여러 컨트롤러가 쿼럼을 이룬다. Raft 프로토콜로 합의한다.
-
메타데이터를 저장할 때 ZooKeeper 대신 내부 토픽
__cluster_metadata에 저장한다. 컨트롤러들이 이 토픽을 복제한다. -
장애 감지시 ZooKeeper session 대신 브로커 간 직접 heartbeat를 사용한다.
broker.heartbeat.interval.ms와broker.session.timeout.ms로 제어한다.
선출 로직 자체는 동일하다. ISR에서 새 리더를 뽑고, LeaderAndIsr로 전파한다.
참고