기본개념 · 3장
KRaft와 클러스터 메타데이터
Kafka 4.x에서 클러스터 메타데이터는 Kafka 자신의 내부 토픽에 Raft 합의로 기록됩니다. 외부 코디네이션 시스템은 필요하지 않습니다. 이 장은 그 구조가 왜 필요했는지, 노드 롤을 어떻게 나누는지, 새 클러스터를 어떤 명령으로 부트스트랩하는지, 그리고 3.x에서 오는 조직이 무엇을 준비해야 하는지를 다룹니다. CCAAK의 Deployment Architecture와 Cluster Configuration 도메인의 기반이 되는 내용입니다.
학습 목표
- KIP-500이 해결한 세 가지 문제(이중 운영, 메타데이터 확장 한계, 컨트롤러 페일오버 지연)를 설명할 수 있습니다.
- 컨트롤러 쿼럼과
__cluster_metadata토픽의 관계를 설명할 수 있습니다. process.roles의 세 가지 값을 구분하고 combined 모드를 언제 쓰는지 판단할 수 있습니다.kafka-storage.sh random-uuid/format으로 새 클러스터를 부트스트랩할 수 있습니다.controller.quorum.voters(정적)와controller.quorum.bootstrap.servers(동적)의 차이를 설명할 수 있습니다.- 컨트롤러 수를 3 또는 5로 두는 근거를
2N+1공식으로 설명할 수 있습니다.
왜 외부 메타데이터 저장소를 없앴는가 — KIP-500
Kafka는 2.x까지 두 개의 분산 시스템을 함께 운영해야 했습니다. 데이터는 Kafka 브로커에, 메타데이터는 외부 앙상블에 있었고, 각각 별도의 설정·모니터링·보안·백업·업그레이드 절차를 요구했습니다. KIP-500은 이 구조를 걷어내고 메타데이터를 Kafka 안으로 옮기는 제안이었습니다. 4.0에서 그 이행이 완료되어 KRaft가 유일한 모드가 되었습니다.
해결하려 한 세 가지 문제
| 문제 | 이전 구조에서 무슨 일이 일어났는가 | KRaft에서 어떻게 달라졌는가 |
|---|---|---|
| 이중 운영 부담 | 서로 다른 두 시스템의 설정 언어·보안 모델·모니터링 지표·업그레이드 절차를 각각 익혀야 했습니다. 장애 시 어느 쪽 문제인지 판단하는 것부터 일이었습니다 | 배포·설정·인증·모니터링이 Kafka 하나로 통합됩니다. 컨트롤러도 kafka-server-start.sh로 기동하는 같은 프로세스입니다 |
| 메타데이터 확장 한계 | 브로커와 토픽 수가 늘어나면 외부 저장소 의존이 병목이 되어 성능과 확장성에 영향을 줬습니다. 메타데이터 상태가 두 시스템 사이에서 어긋날 여지도 있었습니다 | 메타데이터가 단일 파티션 로그가 되고, 브로커는 컨트롤러가 밀어 주는 것을 받는 대신 리더로부터 직접 pull합니다. 이 설계가 더 높은 파티션 수를 지원하는 근거입니다 |
| 컨트롤러 페일오버 지연 | 컨트롤러가 바뀌면 새 컨트롤러가 메타데이터를 처음부터 다시 읽어 상태를 만들어야 했습니다. 파티션이 많은 클러스터에서는 이 로딩 시간이 그대로 장애 시간이었습니다 | 스탠바이 컨트롤러가 이미 같은 로그를 따라오고 있습니다. 공식 문서 표현대로 각 컨트롤러는 "활성 컨트롤러이거나 그것의 핫 스탠바이"이므로, 전환 시 새로 로딩할 것이 없습니다 |
__cluster_metadata를 소유하는 4.x 구조(우)
KRaft 아키텍처 — 컨트롤러 쿼럼과 __cluster_metadata
__cluster_metadata에 쓰고,
스탠바이 컨트롤러와 브로커가 그것을 복제·재생하며, 스냅샷으로 로그를 잘라내는 구조
메타데이터도 하나의 토픽입니다
클러스터 메타데이터는 __cluster_metadata라는 내부 토픽의
단일 파티션(__cluster_metadata-0)에 기록됩니다.
토픽 생성, 파티션 리더 변경, ISR 변경, 브로커 등록, ACL, 동적 설정 변경이
모두 이 로그의 레코드입니다.
차이가 하나 있습니다. 일반 토픽은 리더가 팔로워의 fetch를 기다려 high watermark를 올리는 ISR 방식이지만, 이 파티션은 Raft 합의로 동작합니다. 즉 과반(majority)이 기록하면 커밋됩니다. 컨트롤러 3대면 2대, 5대면 3대가 받으면 확정입니다.
브로커는 이 로그의 observer입니다. 투표에 참여하지 않고 복제만 받아 자기 메모리에 메타데이터 캐시를 만듭니다. 그래서 브로커는 "메타데이터를 조회한다"기보다 "메타데이터 스트림을 구독한다"에 가깝습니다.
활성 컨트롤러가 하는 일
쓰기는 활성 컨트롤러(Raft 리더)만 합니다. 토픽 생성 요청이 오면 활성 컨트롤러가 메타데이터 레코드를 만들어 로그에 append하고, 과반이 복제하면 커밋된 것으로 처리합니다. 브로커들은 그 레코드를 받아 각자 행동합니다.
활성 컨트롤러가 죽으면 남은 컨트롤러들이 새 리더를 뽑습니다. 이때
controller.quorum.fetch.timeout.ms(기본 2000ms) 안에 리더로부터 fetch 응답을
받지 못한 팔로워가 선거를 시작하고, controller.quorum.election.timeout.ms
(기본 1000ms) 안에 결론이 나지 않으면 재시도합니다.
새 리더는 이미 같은 로그를 갖고 있으므로 즉시 일을 이어받습니다.
메타데이터 스냅샷
메타데이터 로그는 계속 자랍니다. 그대로 두면 새 노드가 합류할 때마다
처음부터 전부 재생해야 하므로, Kafka는 주기적으로
스냅샷(snapshot)을 만들어 그 시점의 상태를 하나의 파일로 저장하고
그 앞의 로그를 잘라냅니다. 파일 이름은
{끝오프셋}-{에폭}.checkpoint 형태입니다.
스냅샷 생성 기준은 두 가지입니다.
| 설정 | 기본값 | 의미 |
|---|---|---|
metadata.log.max.record.bytes.between.snapshots |
20971520 (20MiB) | 이만큼의 레코드가 쌓이면 새 스냅샷을 만듭니다 |
metadata.log.max.snapshot.interval.ms |
3600000 (1시간) | 레코드가 적어도 이 시간이 지나면 스냅샷을 만듭니다 |
metadata.log.segment.bytes |
1073741824 (1GiB) | 메타데이터 로그의 세그먼트 롤링 크기 |
metadata.max.idle.interval.ms |
500 | 변경이 없어도 이 주기로 하트비트 레코드를 append해 로그가 정지하지 않게 합니다 |
노드 롤 — process.roles
서버 프로세스의 역할은 process.roles로 정합니다.
허용되는 값은 broker, controller, broker,controller이며,
마지막 조합을 공식 문서는 "combined" 서버라고 부릅니다.
| 값 | 역할 | 쿼럼 투표 | 권장 용도 |
|---|---|---|---|
broker |
파티션 로그 저장, 클라이언트 요청 처리 | 없음 (observer) | 프로덕션 데이터 노드 |
controller |
메타데이터 관리, Raft 쿼럼 참여 | 참여 (voter) | 프로덕션 컨트롤러 노드 3 또는 5대 |
broker,controller |
둘 다 | 참여 (voter) | 개발·테스트 환경. 프로덕션 비권장 |
combined 모드는 언제 쓰는가
공식 문서는 combined 서버가 개발 환경처럼 작은 용도에서 운영이 더 단순하다고 인정하면서, 동시에 두 가지 단점을 분명히 지적합니다.
- 컨트롤러가 나머지 시스템으로부터 격리되지 않습니다. 같은 JVM에서 데이터 트래픽을 처리하므로, 브로커 쪽 GC 정지나 디스크 포화가 컨트롤러의 Raft 하트비트에 그대로 영향을 줍니다.
- 컨트롤러와 브로커를 따로 롤링·스케일할 수 없습니다. 브로커를 재시작하면 컨트롤러도 함께 내려갑니다.
컨트롤러 수는 3 또는 5
공식 문서는 컨트롤러 노드를 보통 3대 또는 5대 선택한다고 하고, 내성 공식을 이렇게 정리합니다.
| 컨트롤러 수 | 과반 | 견딜 수 있는 동시 장애 | 비고 |
|---|---|---|---|
| 1 | 1 | 0 | 단일 장애점. 개발용만 |
| 3 | 2 | 1 | 가장 흔한 프로덕션 구성 |
| 5 | 3 | 2 | 랙/AZ 2개 손실을 견뎌야 할 때 |
즉 N대의 동시 장애를 견디려면 컨트롤러가 2N+1대 필요합니다.
짝수를 쓰는 이점은 없습니다 — 4대는 3대와 같은 1대 내성을 가지면서 비용만 늘어납니다.
용량 산정도 공식 문서에 있습니다. 컨트롤러는 모든 메타데이터를 메모리와 디스크에 보관하며, 전형적인 Kafka 클러스터라면 메인 메모리 5GB와 메타데이터 로그 디렉터리 디스크 5GB로 충분하다고 명시합니다.
클러스터 부트스트랩 — kafka-storage.sh
KRaft에서는 저장 디렉터리를 사람이 명시적으로 포맷해야 합니다. 공식 문서는 이 변경의 이유를 밝히고 있습니다 — 자동 포맷은 오류 상황을 가려버리기 때문입니다. 특히 메타데이터 로그가 중요합니다. 컨트롤러 과반이 빈 로그 디렉터리로 기동할 수 있다면, 커밋된 데이터가 빠진 상태로 리더가 선출될 수 있습니다.
1단계 — 클러스터 ID 생성
$ KAFKA_CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
$ echo $KAFKA_CLUSTER_ID
이 ID는 클러스터에 속한 모든 노드를 포맷할 때 같은 값을 써야 합니다. 노드마다 다른 ID로 포맷하면 그 노드는 클러스터에 합류하지 못합니다.
2단계 — 첫 컨트롤러를 standalone으로 포맷
공식 문서가 권장하는 방법은 voter 한 대로 부트스트랩한 뒤 나머지 컨트롤러를 동적으로 추가하는 것입니다.
$ bin/kafka-storage.sh format \
--cluster-id $KAFKA_CLUSTER_ID \
--standalone \
--config config/controller.properties
공식 문서가 설명하는 이 명령의 결과는 두 가지입니다.
metadata.log.dir에meta.properties를 만들고 무작위directory.id를 부여합니다.00000000000000000000-0000000000.checkpoint스냅샷을 만들어 이 노드를 쿼럼의 유일한 voter로 지정하는 제어 레코드 (KRaftVersionRecord,VotersRecord)를 기록합니다.
대안 — 여러 컨트롤러를 한 번에 부트스트랩
--initial-controllers를 쓰면 처음부터 여러 voter로 시작할 수 있습니다.
이때 모든 컨트롤러에서 같은 값을 써야 합니다.
CLUSTER_ID="$(bin/kafka-storage.sh random-uuid)"
CONTROLLER_0_UUID="$(bin/kafka-storage.sh random-uuid)"
CONTROLLER_1_UUID="$(bin/kafka-storage.sh random-uuid)"
CONTROLLER_2_UUID="$(bin/kafka-storage.sh random-uuid)"
# 각 컨트롤러에서 실행 — --initial-controllers 값은 세 노드 모두 동일해야 합니다
bin/kafka-storage.sh format --cluster-id ${CLUSTER_ID} \
--initial-controllers "0@controller-0:1234:${CONTROLLER_0_UUID},1@controller-1:1234:${CONTROLLER_1_UUID},2@controller-2:1234:${CONTROLLER_2_UUID}" \
--config config/controller.properties
0@controller-0:1234:3Db5QLSqSZieL3rJBUUegA 형식에서
0은 replica id, controller-0은 호스트,
1234는 포트, 마지막 값은 replica directory id입니다.
3단계 — 브로커와 추가 컨트롤러 포맷
이미 존재하는 클러스터에 붙일 노드는 --no-initial-controllers로 포맷합니다.
$ bin/kafka-storage.sh format \
--cluster-id $KAFKA_CLUSTER_ID \
--config config/server.properties \
--no-initial-controllers
4단계 — 기동
$ bin/kafka-server-start.sh config/controller.properties # 컨트롤러 노드
$ bin/kafka-server-start.sh config/server.properties # 브로커 노드
최소 설정 예시
process.roles=controller
node.id=1
# 컨트롤러는 컨트롤러 리스너만 엽니다 (클라이언트 리스너 없음)
listeners=CONTROLLER://controller1.example.com:9093
controller.listener.names=CONTROLLER
# 쿼럼 위치. 모든 브로커와 컨트롤러가 이 값을 가져야 합니다
controller.quorum.bootstrap.servers=controller1.example.com:9093,controller2.example.com:9093,controller3.example.com:9093
log.dirs=/var/lib/kafka/metadata
process.roles=broker
node.id=101
listeners=INTERNAL://:9092,EXTERNAL://:9094
advertised.listeners=INTERNAL://broker1.internal:9092,EXTERNAL://kafka.example.com:9094
listener.security.protocol.map=INTERNAL:PLAINTEXT,EXTERNAL:SASL_SSL,CONTROLLER:PLAINTEXT
inter.broker.listener.name=INTERNAL
# 브로커도 컨트롤러 리스너 이름과 쿼럼 위치를 알아야 합니다
controller.listener.names=CONTROLLER
controller.quorum.bootstrap.servers=controller1.example.com:9093,controller2.example.com:9093,controller3.example.com:9093
log.dirs=/var/lib/kafka/data
num.partitions=6
default.replication.factor=3
min.insync.replicas=2
정적 쿼럼과 동적 쿼럼 — voters vs bootstrap.servers
공식 문서는 KRaft를 운영하는 두 가지 방식을 구분합니다 — KIP-853 동적 컨트롤러 쿼럼과 기존의 정적 쿼럼입니다. 이 구분이 설정 두 개로 드러납니다.
| 구분 | 정적 쿼럼 | 동적 쿼럼 (KIP-853) |
|---|---|---|
| 사용하는 설정 | controller.quorum.voters |
controller.quorum.bootstrap.servers |
| 값의 형식 | {id}@{host}:{port} 목록 — 모든 컨트롤러의 id·호스트·포트를 전부 |
{host}:{port} 목록 — 클라이언트의 bootstrap.servers와 같은 성격 |
| 전부 나열해야 하는가 | 예. 브로커·컨트롤러 모든 설정 파일에 동일하게 | 아니오. 다만 가능한 많이 적는 편이 좋습니다 |
| 컨트롤러 추가·제거 | 불가 — 전체 재구성·재시작 필요 | 가능 — kafka-metadata-quorum.sh add-controller / remove-controller |
kraft.version |
0 또는 없음 | 1 이상 |
| 결정 시점 | 포맷 시점에 결정됩니다. controller.quorum.voters가 없고 --standalone · --initial-controllers · --no-initial-controllers 중 하나를 쓰면 동적 쿼럼이 됩니다 |
|
지금 어느 쪽인지 확인하는 방법
$ bin/kafka-features.sh --bootstrap-controller localhost:9093 describe
Feature: kraft.version SupportedMinVersion: 0 SupportedMaxVersion: 1 FinalizedVersionLevel: 0 Epoch: 7
Feature: metadata.version SupportedMinVersion: 3.3-IV3 SupportedMaxVersion: 4.0-IV3 FinalizedVersionLevel: 4.0-IV3 Epoch: 7
kraft.version의 FinalizedVersionLevel이
0이거나 항목이 없으면 정적 쿼럼,
1 이상이면 동적 쿼럼입니다.
# 전체 feature를 특정 릴리스 수준으로
$ bin/kafka-features.sh --bootstrap-server localhost:9092 upgrade --release-version 4.1
# kraft.version 만 올리기
$ bin/kafka-features.sh --bootstrap-server localhost:9092 upgrade --feature kraft.version=1
컨트롤러 추가와 제거 (동적 쿼럼)
추가는 새 컨트롤러를 포맷·기동한 뒤 복제가 따라잡은 것을 확인하고 쿼럼에 넣는 순서입니다. 제거는 반드시 프로세스를 내리기 전에 쿼럼에서 빼야 합니다.
# 복제 진행 확인
$ bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --replication
# 추가 (브로커 엔드포인트를 쓰는 경우)
$ bin/kafka-metadata-quorum.sh --command-config config/controller.properties \
--bootstrap-server localhost:9092 add-controller
# 제거 — 프로세스를 내리기 전에 먼저 실행하세요
$ bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 \
remove-controller --controller-id 3 --controller-directory-id ILZ5MPTeRWakmJu99uBJCA
KRaft 클러스터를 들여다보는 도구
kafka-metadata-quorum.sh — 쿼럼 상태
$ bin/kafka-metadata-quorum.sh --bootstrap-server localhost:9092 describe --status
ClusterId: fMCL8kv1SWm87L_Md-I2hg
LeaderId: 3002
LeaderEpoch: 2
HighWatermark: 10
MaxFollowerLag: 0
MaxFollowerLagTimeMs: -1
CurrentVoters: [{"id": 3000, "directoryId": "ILZ5MPTeRWakmJu99uBJCA", "endpoints": ["CONTROLLER://localhost:9093"]},
{"id": 3001, "directoryId": "b-DwmhtOheTqZzPoh52kfA", "endpoints": ["CONTROLLER://localhost:9094"]},
{"id": 3002, "directoryId": "g42deArWBTRM5A1yuVpMCg", "endpoints": ["CONTROLLER://localhost:9095"]}]
CurrentObservers: [{"id": 0, "directoryId": "3Db5QLSqSZieL3rJBUUegA"},
{"id": 1, "directoryId": "UegA3Db5QLSqSZieL3rJBU"},
{"id": 2, "directoryId": "L3rJBUUegA3Db5QLSqSZie"}]
CurrentVoters가 컨트롤러, CurrentObservers가 브로커입니다.
MaxFollowerLag가 계속 커지면 컨트롤러 한 대가 뒤처지고 있다는 뜻이므로
디스크·네트워크를 확인해야 합니다.
kafka-dump-log.sh — 메타데이터 레코드 해독
# 로그 세그먼트
$ bin/kafka-dump-log.sh --cluster-metadata-decoder \
--files metadata_log_dir/__cluster_metadata-0/00000000000000000000.log
# 스냅샷
$ bin/kafka-dump-log.sh --cluster-metadata-decoder \
--files metadata_log_dir/__cluster_metadata-0/00000000000000000100-0000000001.checkpoint
kafka-metadata-shell.sh — 대화형 탐색
$ bin/kafka-metadata-shell.sh --snapshot metadata_log_dir/__cluster_metadata-0/00000000000000007228-0000000001.checkpoint
>> ls /
brokers local metadataQuorum topicIds topics
>> ls /topics
foo
>> cat /topics/foo/0/data
{
"partitionId" : 0,
"topicId" : "5zoAlv-xEh9xRANKXt1Lbg",
"replicas" : [ 1 ],
"isr" : [ 1 ],
"removingReplicas" : null,
"addingReplicas" : null,
"leader" : 1,
"leaderEpoch" : 0,
"partitionEpoch" : 0
}
>> exit
컨트롤러 로그 레벨 변경
동적 로그 레벨 변경은 KRaft에서도 그대로 동작합니다. 다만
컨트롤러를 대상으로 할 때는 --bootstrap-controller를 써야 합니다.
# 브로커
$ bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type broker-loggers --entity-name 1 \
--alter --add-config org.apache.kafka.raft.KafkaNetworkChannel=TRACE
# 컨트롤러 — --bootstrap-controller 를 씁니다
$ bin/kafka-configs.sh --bootstrap-controller localhost:9093 \
--entity-type broker-loggers --entity-name 3000 \
--alter --add-config org.apache.kafka.raft.KafkaNetworkChannel=TRACE
3.x에서 오는 경우 — 마이그레이션 개요
4.x는 KRaft 클러스터만 업그레이드 대상으로 받습니다. 4.3 업그레이드 문서는 소프트웨어와 메타데이터 버전이 최소 3.3.x여야 한다고 명시합니다. 이전 메타데이터 저장소를 쓰던 클러스터는 4.x로 올리기 전에 마이그레이션을 끝내야 합니다.
| 현재 버전 | 경로 | 핵심 제약 |
|---|---|---|
| 2.x | 2.x → 3.9 → (마이그레이션) → 4.x | 3.9가 마지막 브리지 릴리스입니다. 반드시 경유해야 합니다 |
| 3.0 ~ 3.8 (구 메타데이터 저장소) | 3.9로 업그레이드 → 마이그레이션 → 4.x | 마이그레이션 절차는 3.9 문서에만 존재합니다. 4.x 문서에는 없습니다 |
| 3.3 ~ 3.9 (이미 KRaft) | 바로 4.x로 롤링 업그레이드 | 소프트웨어·metadata.version 모두 3.3 이상이어야 합니다 |
4.x에서 사라진 설정
3.x 설정 파일을 그대로 가져오면 브로커가 알 수 없는 설정으로 기동을 거부합니다. 공식 문서가 열거하는 제거 항목 중 실무에서 자주 걸리는 것들입니다.
| 제거된 설정 | 무엇을 하던 설정인가 | 지금은 |
|---|---|---|
zookeeper.connect 및 zookeeper.* 전체 |
외부 메타데이터 저장소 접속 정보와 TLS 설정 | controller.quorum.bootstrap.servers + controller.listener.names |
inter.broker.protocol.version |
브로커 간 프로토콜 버전 고정 | metadata.version feature로 관리 — kafka-features.sh |
control.plane.listener.name |
컨트롤러·브로커 통신용 내부 컨트롤 플레인 | controller.listener.names + listeners + listener.security.protocol.map |
broker.id.generation.enable, reserved.broker.max.id |
브로커 ID 자동 생성 | node.id를 명시합니다 (자동 생성 없음) |
leader.imbalance.per.broker.percentage |
preferred 리더 선출 빈도 제한 | 제거됨. auto.leader.rebalance.enable과 leader.imbalance.check.interval.seconds만 사용 |
controlled.shutdown.max.retries, controlled.shutdown.retry.backoff.ms |
정상 종료 재시도 제어 | 제거됨. 종료 절차를 쿼럼 컨트롤러가 관리합니다 |
password.encoder.* |
동적 설정의 민감 값 암호화 키 | 제거됨. 민감 값은 레코드에 저장되며 Kafka가 암호화하지 않습니다 |
이 장에서 나온 설정
| 설정 | 기본값 | 설명 | 튜닝 포인트 |
|---|---|---|---|
process.roles |
(빈 값) | 허용 값 broker · controller |
프로덕션에서는 하나만. combined는 개발용 |
node.id |
(없음, 필수) | 클러스터 안에서 이 노드를 식별하는 정수 (0 이상) | 중복되면 클러스터가 깨집니다. 브로커·컨트롤러가 같은 번호 공간을 씁니다 |
controller.listener.names |
(없음, KRaft 필수) | 컨트롤러 통신에 쓰는 리스너 이름 목록 | 브로커에도 설정해야 합니다. 브로커의 listeners에는 넣지 않습니다 |
controller.quorum.bootstrap.servers |
"" | 컨트롤러 쿼럼을 찾기 위한 host:port 목록 (동적 쿼럼) |
모든 브로커·컨트롤러에 설정. 가능한 많은 컨트롤러를 적으세요 |
controller.quorum.voters |
"" | {id}@{host}:{port} 목록 (정적 쿼럼). 4.1에서 deprecated |
동적 쿼럼을 쓸 때는 설정하지 않아야 합니다 |
controller.quorum.fetch.timeout.ms |
2000 | 이 시간 안에 리더로부터 fetch를 못 받으면 새 선거를 시작 | 네트워크가 불안정한 환경에서 선거가 잦으면 올립니다 |
controller.quorum.election.timeout.ms |
1000 | 선거가 결론나기를 기다리는 시간 | 초과 시 새 선거를 시작합니다 |
controller.quorum.append.linger.ms |
25 | 리더가 쓰기를 모아서 복제하기까지 대기하는 시간 | 메타데이터 쓰기 처리량과 지연의 트레이드오프 |
controller.quorum.request.timeout.ms |
2000 | 쿼럼 내부 요청 타임아웃 | 일반적으로 기본값을 유지합니다 |
controller.quorum.auto.join.enable |
false | 컨트롤러가 voter 집합에 스스로 합류할지 여부 (KIP-853) | true로 두면 voter에서 제거하기 전에 반드시 프로세스를 먼저 내려야 합니다 — 그러지 않으면 제거된 컨트롤러가 다시 자동 합류합니다 |
metadata.log.dir |
null | 메타데이터 로그를 둘 디렉터리 | null이면 log.dirs의 첫 번째를 씁니다. 데이터와 다른 디스크로 분리하는 것이 안전합니다 |
metadata.log.segment.bytes |
1073741824 | 메타데이터 로그의 세그먼트 크기 | 보통 기본값을 유지합니다 |
metadata.log.max.record.bytes.between.snapshots |
20971520 | 스냅샷 생성 기준 (누적 레코드 크기) | 메타데이터 변경이 많으면 스냅샷이 자주 생깁니다 |
metadata.log.max.snapshot.interval.ms |
3600000 | 스냅샷 생성 기준 (시간) | 0으로 두면 시간 기준 생성을 끕니다 |
metadata.max.idle.interval.ms |
500 | 변경이 없을 때 하트비트 레코드를 append하는 주기 | 0이면 비활성화됩니다 |
broker.heartbeat.interval.ms |
2000 | 브로커가 컨트롤러에 하트비트를 보내는 주기 | broker.session.timeout.ms와 함께 조정합니다 |
broker.session.timeout.ms |
9000 | 컨트롤러가 브로커를 죽었다고 판정하는 시간 | 짧으면 GC 정지에 브로커가 fenced됩니다 |
initial.broker.registration.timeout.ms |
60000 | 기동 시 컨트롤러에 등록을 기다리는 시간 | 초과하면 기동이 실패합니다. 쿼럼 주소·리스너 설정 오류의 대표 증상입니다 |
흔한 오해
시험 포인트 정리
확인 문제
단일 선택 · 복수 선택 · 연결형 · 순서 배열이 섞여 있습니다.
이어서 볼 곳
-
4장 · Producer 심화
전송 파이프라인, 배치,
acks, 멱등성, 순서 보장, 에러 처리. -
CLI 치트시트
kafka-storage,kafka-metadata-quorum,kafka-features등 목적별 명령 표. - CCAAK 개요 운영자 시험의 도메인 구성과 챕터 매핑, 실습 필수 항목.
- 예제 1 · 로컬 KRaft 클러스터 docker compose로 3노드 KRaft 클러스터를 직접 띄워 봅니다.
- 부록 · 버전 표기와 레거시 2.x/3.x 운영 배경, 버전 타임라인, 업그레이드 경로 상세.
- 11장 · 운영 기초 보안 4계층, 필수 메트릭, 성능 튜닝, Share Groups.
공식 문서 출처
이 장의 명령어·설정 기본값·권장 사항은 모두 아래에서 확인했습니다 (Apache Kafka 4.3 문서 기준).
- KRaft —
process.roles값, combined 서버의 단점, 컨트롤러 3/5 권장과 과반 요건, 배포 고려사항, 컨트롤러 메모리·디스크 5GB - KRaft — Provisioning Nodes —
kafka-storage.sh random-uuid/format --standalone/--initial-controllers/--no-initial-controllers, 자동 포맷을 없앤 이유 - KRaft — Controller membership changes — 정적/동적 쿼럼 구분,
kafka-metadata-quorum.sh add-controller/remove-controller - KRaft — Upgrade —
kraft.version=1,controller.quorum.votersdeprecated,kafka-features.sh사용법 - KRaft — Debugging —
kafka-metadata-quorum.sh describe --status,kafka-dump-log.sh --cluster-metadata-decoder,kafka-metadata-shell.sh - Differences Between KRaft mode and ZooKeeper mode — 제거된 설정 목록, 컨트롤 플레인 리스너 대체, 민감 데이터 미암호화,
advertised.listeners동적 변경 불가, 컨트롤러 로그 레벨 변경 시--bootstrap-controller - Broker Configs —
node.id,controller.listener.names,controller.quorum.*,metadata.log.*,broker.heartbeat.interval.ms,broker.session.timeout.ms,initial.broker.registration.timeout.ms - Upgrading Apache Kafka — 4.3 업그레이드 최소 요건(KRaft, 3.3.x 이상), 4.0에서 설정 파일이
config/로 통합된 사실 - ZooKeeper to KRaft Migration — 브리지 릴리스가 필요하며 마지막 브리지 릴리스가 3.9라는 안내
- KIP-500 — Replace ZooKeeper with a Self-Managed Metadata Quorum — 설계 동기(단순화·확장성), 스탠바이 컨트롤러로 로딩 시간 제거, 브로커가 메타데이터를 pull하는 구조
- KIP-853 — 동적 컨트롤러 쿼럼과
controller.quorum.auto.join.enable(4.3 업그레이드 문서가 인용하는 링크)