학습 목표

이 도메인이 묻는 것

이 섹션의 문항은 크게 세 갈래입니다. 첫째, 구조 판독 — CLI 출력이나 설정 조각을 주고 무엇이 잘못되었는지 찾게 합니다. 둘째, 보장의 경계 — 어떤 조건에서 데이터가 남고 어떤 조건에서 사라지는지 묻습니다. 셋째, 용어의 정확성 — leader와 preferred leader, replica와 ISR, LEO와 high watermark를 구분하는지 matching 유형으로 확인합니다.

이 섹션에서 반복되는 질문 형태
질문 형태 실제로 확인하는 것
"이 토픽의 IsrReplicas보다 짧다. 무슨 일인가?" ISR 축소 조건과 그것이 쓰기 가능성에 미치는 영향
"브로커 1대를 잃었을 때 데이터가 유실되는 조건은?" RF · min.insync.replicas · acks 3자 관계
"파티션 수를 줄이려면?" 줄일 수 없다는 사실과 그 이유
"컨트롤러가 하는 일을 고르시오" KRaft에서 메타데이터가 어디에 저장되고 누가 관리하는가
"각 오프셋 지표를 의미와 연결하시오" (matching) log-start-offset · committed offset · high watermark · LEO 구분

핵심 개념 요약 — 운영자 관점

클러스터의 모양

Kafka 4.x의 모든 서버는 process.roles로 역할을 선언합니다. broker는 데이터를 저장·서빙하고, controller는 메타데이터 쿼럼에 참여합니다. 컨트롤러 중 하나가 active controller이고 나머지는 hot standby입니다. 클러스터 메타데이터는 __cluster_metadata 파티션에 Raft로 복제됩니다.

KRaft 클러스터 전체 구조 — 컨트롤러 쿼럼, 브로커 3대, 클라이언트 위쪽은 컨트롤러 쿼럼입니다. controller 롤로 실행되는 노드 3대 가운데 하나가 액티브 컨트롤러이자 Raft 리더이고 나머지 둘은 팔로워이며, 메타데이터는 __cluster_metadata 내부 로그에 Raft 로 복제됩니다. 운영에서는 3대 또는 5대를 권장합니다. 가운데는 브로커 3대이며 토픽 orders 의 파티션 3개가 replication.factor 3 으로 배치되어 각 브로커가 하나의 리더와 두 개의 팔로워를 갖습니다. 브로커는 컨트롤러에서 메타데이터를 받아 가고, 쓰기와 읽기는 파티션 리더가 처리합니다. 아래쪽 프로듀서는 bootstrap.servers 로 접속해 메타데이터를 조회한 뒤 파티션 리더에 직접 전송하고, 컨슈머 그룹은 리더에서 fetch 하며 오프셋을 __consumer_offsets 에 커밋합니다. 클라이언트는 컨트롤러에 접속하지 않고 항상 브로커에 붙으며 ZooKeeper 는 4.0 에서 제거되었습니다. KRaft 클러스터 전체 구조 — 컨트롤러 쿼럼 · 브로커 · 클라이언트 컨트롤러 쿼럼 (process.roles=controller) — 권장 3대 또는 5대 controller-1 액티브 (Raft 리더) controller-2 팔로워 controller-3 팔로워 메타데이터는 __cluster_metadata 라는 내부 로그에 Raft 로 복제됩니다. 브로커는 컨트롤러에서 메타데이터를 받아 갑니다 (옵서버) 브로커 (process.roles=broker) — 토픽 orders · 파티션 3개 · replication.factor 3 broker-1 P0 리더 P1 팔로워 P2 팔로워 broker-2 P1 리더 P2 팔로워 P0 팔로워 broker-3 P2 리더 P0 팔로워 P1 팔로워 Producer bootstrap.servers 로 접속 → 메타데이터 조회 → 파티션 리더에 직접 전송 Consumer group 리더에서 fetch · 오프셋은 __consumer_offsets 에 커밋 초록색 = 파티션 리더. 쓰기와 읽기는 항상 리더가 처리하고 팔로워는 복제만 합니다. 클라이언트는 컨트롤러에 접속하지 않습니다 — 항상 브로커에 붙습니다. ZooKeeper 는 4.0 에서 제거되었습니다.
KRaft 클러스터 전체 구조 — 브로커 3대와 컨트롤러 쿼럼, 클라이언트가 어디에 붙는지

복제와 ISR — 이 섹션의 중심

복제 단위는 토픽이 아니라 토픽-파티션입니다. 각 파티션에는 리더 하나와 팔로워가 있고, 쓰기와 읽기는 모두 리더로 갑니다. 팔로워는 리더에서 fetch해 따라잡습니다. ISR(in-sync replicas)은 "충분히 따라잡은" 레플리카 집합입니다.

팔로워가 replica.lag.time.max.ms(기본 30000ms) 안에 리더의 로그 끝까지 따라잡은 fetch 요청을 보내지 못하면 ISR에서 빠집니다. 다시 따라잡으면 되돌아옵니다. 이 축소·확장은 각각 IsrShrinksPerSec · IsrExpandsPerSec 메트릭으로 관측됩니다.

복제와 ISR — ISR 축소가 min.insync.replicas 에 걸리는 순간 replication.factor 3 인 파티션 orders-0 을 세 단계로 보여 줍니다. 1단계는 리더 broker-1 과 팔로워 broker-2, broker-3 이 모두 따라잡아 ISR 이 3 이고 acks=all 쓰기가 성공합니다. 2단계는 broker-3 이 replica.lag.time.max.ms 30000 밀리초 동안 따라오지 못해 ISR 에서 빠지고 ISR 이 2 로 줄지만 min.insync.replicas 가 2 이므로 아직 성공합니다. 3단계는 broker-2 까지 빠져 ISR 이 1 이 되어 min.insync.replicas 미달이 되고, acks=all 프로듀서는 NOT_ENOUGH_REPLICAS 오류를 받습니다. 이때도 리더는 살아 있으므로 컨슈머 읽기는 계속됩니다. replication.factor 는 3 그대로이고 줄어드는 것은 ISR 집합이라는 점이 핵심입니다. 파티션 orders-0 · replication.factor=3 · min.insync.replicas=2 · acks=all ① 정상 — ISR 3 리더 broker-1 · LEO 120 팔로워 broker-2 · LEO 120 팔로워 broker-3 · LEO 120 ISR = {1, 2, 3} → 크기 3 3 ≥ 2 (충족) 쓰기 성공 ISR 3대 전원이 응답 ② ISR 축소 — 아직 충족 리더 broker-1 · LEO 168 팔로워 broker-2 · LEO 168 팔로워 broker-3 · LEO 88 (지연) replica.lag.time.max.ms 초과 ISR = {1, 2} → 크기 2 2 ≥ 2 (경계, 충족) 쓰기 성공 · 여유 0 한 대만 더 빠지면 중단 ③ 미달 — 쓰기 거부 리더 broker-1 · LEO 168 팔로워 broker-2 · 다운 팔로워 broker-3 · 지연 ISR = {1} → 크기 1 1 < 2 (미달) 쓰기 거부 NOT_ENOUGH_REPLICAS 줄어드는 것은 ISR 집합입니다. replication.factor 는 계속 3 이고, 레플리카가 따라잡으면 ISR 에 다시 들어옵니다. 거부되는 것은 쓰기뿐입니다. 리더가 살아 있으므로 컨슈머는 high watermark 까지 계속 읽을 수 있습니다. ③ 을 피하려면 replication.factor=3 + min.insync.replicas=2 조합으로 레플리카 1대 손실을 허용합니다. 쓰기 성공 조건: ISR 크기 ≥ min.insync.replicas 이고, 그 ISR 전원이 응답. 지연 판정 기준은 30000ms 입니다.
복제와 ISR — 팔로워 지연으로 ISR이 축소되어 min.insync.replicas를 밑도는 과정

리더 선출 3종

리더 선출의 종류와 대가
종류 언제 일어나는가 대가
정상 선출 리더 브로커가 내려가면 컨트롤러가 ISR 안에서 새 리더를 뽑습니다. 없음. 커밋된 데이터는 보존됩니다.
preferred 선출 레플리카 목록의 첫 번째 브로커로 리더를 되돌립니다. auto.leader.rebalance.enable=true(기본)이면 자동, 아니면 kafka-leader-election.sh --election-type preferred로 수동. 선출 순간 짧은 리더 없음 구간. 대신 리더가 한쪽 브로커에 쏠리는 것을 막습니다.
unclean 선출 ISR이 비어 있을 때 ISR 밖 레플리카를 리더로 올립니다. unclean.leader.election.enable=true일 때만 가능합니다(기본 false). 데이터 유실. 가용성을 데이터로 사는 거래입니다. UncleanLeaderElectionsPerSec는 항상 0이어야 합니다.

로그의 물리 구조

각 파티션은 log.dirs 아래 {토픽명}-{파티션번호} 디렉터리 하나에 들어갑니다. 여러 디렉터리를 지정하면 파티션이 라운드로빈으로 배치되며, 하나의 파티션이 두 디렉터리에 걸치지는 않습니다. 디렉터리 안은 세그먼트 파일들로 나뉘고, 삭제는 세그먼트 단위로만 일어납니다. 그래서 retention.ms를 짧게 줘도 segment.bytes/segment.ms가 크면 실제 보관량이 예상보다 오래 남습니다. 자세한 계산은 7장에 있습니다.

필수 CLI 명령어

이 섹션에서 손에 익어야 하는 명령은 많지 않습니다. 대신 출력 판독이 중요합니다. 전체 목록은 CLI 치트시트에 있습니다.

토픽 구조 확인 — 이 세 줄이 시험 지문으로 그대로 나옵니다
# 토픽 생성 (프로덕션에서는 항상 명시 생성)
bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic orders \
    --partitions 20 --replication-factor 3 --config min.insync.replicas=2

# 리더 · 레플리카 · ISR 판독
bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic orders

# 목록
bin/kafka-topics.sh --bootstrap-server localhost:9092 --list

# 파티션 증설 (감소는 불가)
bin/kafka-topics.sh --bootstrap-server localhost:9092 --alter --topic orders --partitions 40
메타데이터 쿼럼 상태 — 클러스터가 살아 있는지 가장 먼저 보는 곳
# 쿼럼 요약: 리더, 에폭, high watermark, voter/observer 목록
bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --status

# 각 노드의 복제 진행 (Lag, LastFetchTimestamp, LastCaughtUpTimestamp)
bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --replication

# 브로커가 다 죽어 있으면 컨트롤러로 직접
bin/kafka-metadata-quorum.sh --bootstrap-controller localhost:9093 describe --status
리더 균형 회복 — 롤링 재시작 뒤에 반드시 확인
# preferred 레플리카로 리더 되돌리기 (전체 파티션)
bin/kafka-leader-election.sh --bootstrap-server localhost:9092 \
    --election-type preferred --all-topic-partitions
컨슈머 위치 확인 — lag의 기본 경로
# CURRENT-OFFSET / LOG-END-OFFSET / LAG
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group

# 그룹 상태 요약 (코디네이터, 할당 전략, 멤버 수)
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group --state

# 그룹 종류를 한눈에 (Consumer / Share / Streams)
bin/kafka-groups.sh --bootstrap-server localhost:9092 --list

반드시 외워야 할 설정값

Kafka 4.3 기준 기본값. 브로커 설정과 토픽 설정을 구분했습니다.
설정 기본값 의미 운영 포인트
replica.lag.time.max.ms
broker
30000 이 시간 안에 리더를 따라잡지 못한 팔로워를 ISR에서 제거 낮추면 ISR이 예민해져 잦은 축소·확장이 생깁니다. read-only라 재시작이 필요합니다.
min.insync.replicas
broker topic
1 acks=all 쓰기가 성공하기 위해 필요한 최소 ISR 크기 기본값 1이면 acks=all도 사실상 리더 1대만 확인합니다. RF=3이면 2가 표준입니다.
unclean.leader.election.enable
broker topic
false ISR 밖 레플리카를 리더로 올릴지 여부 true로 바꾸면 유실을 허용합니다. 켜야 한다면 해당 토픽만 한시적으로 켜세요.
default.replication.factor
broker
1 자동 생성 토픽의 복제 계수 auto.create.topics.enable=true(기본)와 겹치면 오타 토픽이 RF=1로 조용히 생깁니다.
num.partitions
broker
1 자동 생성 토픽의 파티션 수 소비 병렬성 상한이 1이 됩니다.
auto.create.topics.enable
broker
true 없는 토픽에 요청이 오면 자동 생성 운영 클러스터에서는 끄는 편이 안전합니다.
controlled.shutdown.enable
broker
true 종료 전에 리더십을 다른 레플리카로 넘김 RF>1이고 살아 있는 레플리카가 있어야 성공합니다. RF=1 토픽이 있으면 실패합니다.
auto.leader.rebalance.enable
broker
true preferred 레플리카로 리더를 자동 복구 점검 주기는 leader.imbalance.check.interval.seconds(기본 300)입니다.
offsets.topic.replication.factor
broker
3 __consumer_offsets의 복제 계수 브로커가 3대 미만인 환경에서는 토픽 생성 자체가 실패합니다. 실습 클러스터에서 자주 걸립니다.
transaction.state.log.replication.factor
broker
3 __transaction_state의 복제 계수 짝인 transaction.state.log.min.isr 기본값은 2입니다.
log.segment.bytes
broker
1073741824
(1 GiB)
세그먼트 롤 크기 삭제는 세그먼트 단위라, 이 값이 크면 리텐션이 실제보다 늦게 듣습니다.
segment.ms
topic
604800000
(7일)
시간 기준 세그먼트 롤 저트래픽 토픽은 크기 기준으로는 안 굴러가므로 이 값이 실질 기준이 됩니다.
retention.ms
topic
604800000
(7일)
delete 정책의 보관 시간 장애 복구에 며칠이 필요한 파이프라인이면 먼저 확인할 값입니다.
message.max.bytes
broker
1048588 허용되는 최대 레코드 배치 크기(압축 후) "1MB"가 아니라 정확히 1048588입니다. 토픽 레벨 max.message.bytes가 오버라이드합니다.

장애 시나리오와 대응

시나리오 1 — ISR이 계속 줄었다 늘어난다

ISR 플래핑(flapping) 대응
단계할 일
증상IsrShrinksPerSecIsrExpandsPerSec가 0이 아닌 값으로 반복. UnderReplicatedPartitions가 0과 양수를 왕복.
1어느 브로커가 빠지는지 특정합니다. kafka-topics.sh --describe에서 Isr에 빠진 노드 ID를 확인합니다.
2그 브로커의 디스크 I/O와 네트워크를 봅니다. 팔로워 fetch가 느려지는 원인은 대개 디스크 포화입니다.
3RequestHandlerAvgIdlePercent가 낮으면 요청 처리 스레드가 부족합니다. num.io.threads(기본 8)를 검토합니다.
4진행 중인 파티션 재할당이 있는지 확인합니다. 스로틀이 해제되지 않은 상태면 정상 복제까지 조여집니다.
5근본 원인을 못 찾았는데 급하다면 num.replica.fetchers(기본 1)를 늘려 팔로워 fetch 병렬성을 올립니다.

시나리오 2 — 브로커를 정상 종료했는데 종료가 끝나지 않는다

  1. controlled.shutdown.enable=true(기본)이면 브로커는 종료 전에 자기 리더 파티션을 넘기려 시도합니다.
  2. 그런데 RF=1 파티션이 하나라도 있으면 넘길 대상이 없어 controlled shutdown이 실패합니다.
  3. 먼저 kafka-topics.sh --describe로 RF=1 토픽을 찾습니다. 자동 생성으로 만들어진 토픽이 흔한 원인입니다.
  4. 재할당 JSON에 레플리카를 추가해 kafka-reassign-partitions.sh --execute복제 계수를 올립니다(절차는 클러스터 구성).
  5. 근본 대책은 auto.create.topics.enable=falsedefault.replication.factor 상향입니다.

시나리오 3 — 롤링 재시작 뒤 특정 브로커만 CPU가 높다

  1. 재시작된 브로커는 팔로워로만 복귀합니다. 리더가 남은 브로커들에 쏠려 부하가 불균형해집니다.
  2. PreferredReplicaImbalanceCount가 0보다 큰지 확인합니다. 이 값이 곧 "리더가 preferred가 아닌 파티션 수"입니다.
  3. auto.leader.rebalance.enable=true라면 leader.imbalance.check.interval.seconds(기본 300) 주기로 자동 복구됩니다. 기다립니다.
  4. 급하면 kafka-leader-election.sh --election-type preferred --all-topic-partitions로 수동 복구합니다.
  5. 롤링 절차 자체에 각 노드 재시작 후 URP가 0으로 돌아올 때까지 대기를 넣어 두면 이 상황이 크게 줄어듭니다.

시나리오 4 — "파티션이 너무 많으니 줄여 달라"는 요청

Kafka는 파티션 수 감소를 지원하지 않습니다. 유일한 방법은 새 토픽을 원하는 파티션 수로 만들어 데이터를 옮기고 클라이언트를 전환하는 것입니다. 늘리는 것도 공짜가 아닙니다. 키 해시 분배가 바뀌어 기존 키의 순서 보장이 깨지고, auto.offset.reset=latest인 컨슈머는 새 파티션의 초기 메시지를 놓칠 수 있습니다.

자주 나오는 함정

관련 케이스 스터디

배경 개념이 더 필요하면 2장 아키텍처와 핵심 개념3장 KRaft와 클러스터 메타데이터를 먼저 보세요.

미니 퀴즈

matching과 list order 유형이 섞여 있습니다. 실제 CCAAK에서도 이 두 유형이 출제되므로 함께 연습하세요.

공식 문서 출처