KRaft vs ZooKeeper
KRaft vs ZooKeeper
ZooKeeper 모드와 KRaft 모드의 차이를 다루는 문서예요. Kafka 4.x부터 ZooKeeper 모드는 제거됐고, KRaft(Kafka Raft) 모드가 기본이 됐어요. 이 문서는 KRaft 모드에서 제거되거나 달라진 설정·지표·동작을 정리해요.
출처: 문서
본문
이 문서는 KRaft 모드와 ZooKeeper 모드의 동작 차이를 설명해요. 특히 여러 설정, 지표, 기능이 KRaft 모드에서 변경됐거나 더 이상 필요하지 않아요. 기존 클러스터를 ZooKeeper 모드에서 KRaft 모드로 마이그레이션하려면 ZooKeeper to KRaft Migration 섹션을 참고해요.
제거된 ZooKeeper 기능
설정 (Configurations)
제거된 비밀번호 인코더 관련 설정. ZooKeeper 모드에서는 민감한 데이터(예: 비밀번호)를 암호화하는 키·백업 키를 정의하고, 비밀번호 암호화의 알고리즘과 키 생성 방법(AES, RSA 등)을 지정하고, 키 길이와 암호화 강도를 제어하는 데 이 설정들을 사용했어요. KRaft 모드에서 Kafka는 민감한 데이터를 레코드(record)에 저장하며, Kafka 내에서 데이터를 암호화하지 않아요.
password.encoder.secretpassword.encoder.old.secretpassword.encoder.keyfactory.algorithmpassword.encoder.cipher.algorithmpassword.encoder.key.lengthpassword.encoder.iterations
제거된 control.plane.listener.name. Kafka는 메타데이터 관리를 ZooKeeper에 의존했지만, 일부 내부 작업(예: 컨트롤러와 브로커 사이의 통신)은 여전히 Kafka의 내부 컨트롤 플레인을 필요로 했어요. KRaft 모드에서 Kafka는 ZooKeeper에 대한 의존성을 없애고 컨트롤 플레인 기능을 Kafka 자체에 완전히 통합해요. 프로세스 역할이 명확히 분리돼요 — 브로커는 데이터 관련 요청을 처리하고, 컨트롤러(quorum controller)는 메타데이터 관련 요청을 관리해요. 컨트롤러는 ZooKeeper 모델과 다르게 동작하는 Raft 프로토콜로 내부 통신을 해요. 컨트롤 플레인 리스너를 구성하려면 다음 파라미터를 사용해요.
controller.listener.nameslistenerslistener.security.protocol.map
제거된 graceful broker shutdown 관련 설정. ZooKeeper 모드에서는 제어된 종료(controlled shutdown)의 최대 재시도 횟수와 재시도 백오프 시간을 정의하는 데 이 설정들을 사용했어요. 계획되지 않은 리더 변경과 데이터 불일치의 위험을 줄일 수 있어요. KRaft 모드에서 Kafka는 Raft 프로토콜로 메타데이터를 관리해요. 브로커 종료 과정은 쿼럼 기반 컨트롤러가 관리한다는 점에서 ZooKeeper 모드와 달라요. 컨트롤러가 처리하는 자동 리더 이전과 메타데이터 갱신 덕분에 종료 과정이 더 안정적이고 효율적이에요.
controlled.shutdown.max.retriescontrolled.shutdown.retry.backoff.ms
제거된 브로커 id 생성 관련 설정. ZooKeeper 모드에서는 브로커 id 자동 생성과 브로커 id 생성 과정을 지정하는 데 이 설정들을 사용했어요. KRaft 모드에서 Kafka는 서버를 식별하기 위해 node id를 사용해요.
reserved.broker.max.idbroker.id.generation.enablenode.id
제거된 브로커 프로토콜 버전 관련 설정. ZooKeeper 모드에서는 브로커 간 통신 프로토콜 버전을 정의하는 데 이 설정을 사용했어요. KRaft 모드에서 Kafka는 metadata.version으로 클러스터의 기능 수준을 제어하며, bin/kafka-features.sh로 관리할 수 있어요.
inter.broker.protocol.version
제거된 ZooKeeper에 의존하던 동적 구성. KRaft 모드에서 이 구성을 변경하려면 브로커/컨트롤러를 재시작해야 해요.
advertised.listeners
제거된 ZooKeeper에서만 쓰던 리더 불균형 설정. leader.imbalance.per.broker.percentage는 ZooKeeper에서 preferred leader election 빈도를 제한하는 데 사용했어요.
leader.imbalance.per.broker.percentage
제거된 ZooKeeper 관련 설정.
zookeeper.connectzookeeper.session.timeout.mszookeeper.connection.timeout.mszookeeper.set.aclzookeeper.max.in.flight.requestszookeeper.ssl.client.enablezookeeper.clientCnxnSocketzookeeper.ssl.keystore.locationzookeeper.ssl.keystore.passwordzookeeper.ssl.keystore.typezookeeper.ssl.truststore.locationzookeeper.ssl.truststore.passwordzookeeper.ssl.truststore.typezookeeper.ssl.protocolzookeeper.ssl.enabled.protocolszookeeper.ssl.cipher.suiteszookeeper.ssl.endpoint.identification.algorithmzookeeper.ssl.crl.enablezookeeper.ssl.ocsp.enable
동적 로그 레벨 (Dynamic Log Levels)
동적 로그 레벨 기능은 실행 중인 브로커나 컨트롤러 프로세스를 재시작하지 않고 log4j 설정을 바꿀 수 있게 해요. 브로커에 동적 로그 레벨을 설정하는 커맨드라인 문법은 KRaft 모드에서 바뀌지 않았어요. 브로커에 로그 레벨을 설정하는 예시예요.
./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 1 \
--alter \
--add-config org.apache.kafka.raft.KafkaNetworkChannel=TRACE
브로커가 아닌 컨트롤러의 로그 레벨을 바꾸는 경우라도 entity-type은 반드시 broker-loggers로 지정해야 해요.
브로커와 컨트롤러 역할을 모두 가진 결합(combined) 노드의 로그 레벨을 바꿀 때는 --bootstrap-servers 또는 --bootstrap-controllers 중 아무거나 사용할 수 있어요. 결합 노드는 단일 로그 레벨 집합만 가져요 — 프로세스의 브로커 부분과 컨트롤러 부분에 서로 다른 로그 레벨은 없어요.
동적 컨트롤러 구성 (Dynamic Controller Configurations)
일부 Kafka 구성은 프로세스를 재시작하지 않고 동적으로 바꿀 수 있어요. 브로커에서 동적 로그 레벨을 설정하는 커맨드라인 문법은 KRaft 모드에서 바뀌지 않았어요. 브로커의 IO 스레드 수를 설정하는 예시예요.
./bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type brokers \
--entity-name 1 \
--alter \
--add-config num.io.threads=5
컨트롤러는 적용 가능한 모든 클러스터 레벨 동적 구성을 적용해요. 예를 들어 다음 커맨드라인은 클러스터의 모든 브로커와 모든 컨트롤러의 max.connections 설정을 바꿔요.
./bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--entity-type brokers \
--entity-default \
--alter \
--add-config max.connections=10000
버전 4.3 이전에는 정적 쿼럼(static quorum)을 사용하지 않으면 동적 구성 갱신이 지원되지 않았어요.
지표 (Metrics)
ZooKeeper와 관련된 다음 지표가 제거됐어요. ControlPlaneNetworkProcessorAvgIdlePercent는 네트워크 프로세서가 유휴 상태인 시간의 평균 비율을 모니터링하고, ControlPlaneExpiredConnectionsKilledCount는 모든 프로세서에 걸쳐 연결이 끊긴 총 연결 수를 모니터링해요. KRaft 모드에서 Kafka는 네트워크 프로세서와 만료 연결을 모니터링하는 지표도 제공해요. 네트워크 프로세서와 만료 연결을 모니터링하려면 다음 지표를 사용해요.
ControlPlaneNetworkProcessorAvgIdlePercentControlPlaneExpiredConnectionsKilledCountNetworkProcessorAvgIdlePercentExpiredConnectionsKilledCount
ZooKeeper 모드에서만 쓰이던 지표가 제거됐어요. 여기에는 kafka.controller:type=ControllerChannelManager,name=QueueSize 같은 컨트롤러 채널·이벤트·통계 지표, kafka.controller:type=KafkaController,name=ActiveControllerCount 같은 컨트롤러 상태 지표, kafka.server:type=DelayedOperationPurgatory,...ElectLeader, kafka.server:type=SessionExpireListener,...ZooKeeper*, kafka.server:type=ZooKeeperClientMetrics,name=ZooKeeperRequestLatencyMs 같은 ZooKeeper 세션·지연 지표들이 포함돼요. 구체적인 지표 이름은 다음과 같아요.
kafka.controller:type=ControllerChannelManager,name=QueueSizekafka.controller:type=ControllerChannelManager,name=RequestRateAndQueueTimeMskafka.controller:type=ControllerEventManager,name=EventQueueSizekafka.controller:type=ControllerEventManager,name=EventQueueTimeMskafka.controller:type=ControllerStats,name=AutoLeaderBalanceRateAndTimeMskafka.controller:type=ControllerStats,name=ControlledShutdownRateAndTimeMskafka.controller:type=ControllerStats,name=ControllerChangeRateAndTimeMskafka.controller:type=ControllerStats,name=ControllerShutdownRateAndTimeMskafka.controller:type=ControllerStats,name=IdleRateAndTimeMskafka.controller:type=ControllerStats,name=IsrChangeRateAndTimeMskafka.controller:type=ControllerStats,name=LeaderAndIsrResponseReceivedRateAndTimeMskafka.controller:type=ControllerStats,name=LeaderElectionRateAndTimeMskafka.controller:type=ControllerStats,name=ListPartitionReassignmentRateAndTimeMskafka.controller:type=ControllerStats,name=LogDirChangeRateAndTimeMskafka.controller:type=ControllerStats,name=ManualLeaderBalanceRateAndTimeMskafka.controller:type=KafkaController,name=MigratingZkBrokerCountkafka.controller:type=ControllerStats,name=PartitionReassignmentRateAndTimeMskafka.controller:type=ControllerStats,name=TopicChangeRateAndTimeMskafka.controller:type=ControllerStats,name=TopicDeletionRateAndTimeMskafka.controller:type=KafkaController,name=TopicsIneligibleToDeleteCountkafka.controller:type=ControllerStats,name=TopicUncleanLeaderElectionEnableRateAndTimeMskafka.controller:type=ControllerStats,name=UncleanLeaderElectionEnableRateAndTimeMskafka.controller:type=ControllerStats,name=UncleanLeaderElectionsPerSeckafka.controller:type=ControllerStats,name=UpdateFeaturesRateAndTimeMskafka.controller:type=ControllerStats,name=UpdateMetadataResponseReceivedRateAndTimeMskafka.controller:type=KafkaController,name=ActiveBrokerCountkafka.controller:type=KafkaController,name=ActiveControllerCountkafka.controller:type=KafkaController,name=ControllerStatekafka.controller:type=KafkaController,name=FencedBrokerCountkafka.controller:type=KafkaController,name=GlobalPartitionCountkafka.controller:type=KafkaController,name=GlobalTopicCountkafka.controller:type=KafkaController,name=OfflinePartitionsCountkafka.controller:type=KafkaController,name=PreferredReplicaImbalanceCountkafka.controller:type=KafkaController,name=ReplicasIneligibleToDeleteCountkafka.controller:type=KafkaController,name=ReplicasToDeleteCountkafka.controller:type=KafkaController,name=TopicsToDeleteCountkafka.controller:type=KafkaController,name=ZkMigrationStatekafka.server:type=DelayedOperationPurgatory,name=PurgatorySize,delayedOperation=ElectLeaderkafka.server:type=DelayedOperationPurgatory,name=PurgatorySize,delayedOperation=topickafka.server:type=DelayedOperationPurgatory,name=NumDelayedOperations,delayedOperation=ElectLeaderkafka.server:type=DelayedOperationPurgatory,name=NumDelayedOperations,delayedOperation=topickafka.server:type=SessionExpireListener,name=SessionStatekafka.server:type=SessionExpireListener,name=ZooKeeperAuthFailuresPerSeckafka.server:type=SessionExpireListener,name=ZooKeeperDisconnectsPerSeckafka.server:type=SessionExpireListener,name=ZooKeeperExpiresPerSeckafka.server:type=SessionExpireListener,name=ZooKeeperReadOnlyConnectsPerSeckafka.server:type=SessionExpireListener,name=ZooKeeperSaslAuthenticationsPerSeckafka.server:type=SessionExpireListener,name=ZooKeeperSyncConnectsPerSeckafka.server:type=ZooKeeperClientMetrics,name=ZooKeeperRequestLatencyMs
동작 변경 참고 (Behavioral Change Reference)
이 문서는 ZooKeeper 모드와 KRaft 모드 사이의 기능적·운영적 차이를 목록으로 정리해요.
- 구성 값 크기 제한: KRaft 모드는 구성 값을 최대
Short.MAX_VALUE크기로 제한해요. 이 때문에 append 작업으로 더 큰 구성 값을 만드는 건 불가능해요. - 정책 클래스 배포: KRaft 모드에서
CreateTopicPolicy와AlterConfigPolicy플러그인은 브로커가 아니라 컨트롤러에서 실행돼요. 사용자는 컨트롤러에 정책 클래스 JAR 파일을 배포하고 컨트롤러에 파라미터(create.topic.policy.class.name과alter.config.policy.class.name)를 구성해야 해요. 참고: ZooKeeper 모드에서 마이그레이션하는 경우 정책 JAR을 브로커에서 컨트롤러로 옮겼는지 확인해요. KafkaPrincipalBuilder의 커스텀 구현: KRaft 모드에서KafkaPrincipalBuilder의 커스텀 구현은 반드시KafkaPrincipalSerde도 구현해야 해요. 그렇지 않으면 브로커가 요청을 컨트롤러로 전달하지 못해요.
더 알아보기 (Learn more)
- KRaft 모드는 ZooKeeper 없이 Raft 프로토콜로 메타데이터를 관리해서, 컨트롤러가 리더 이전과 메타데이터 갱신을 더 안정적으로 처리해요.
- ZooKeeper 모드에서 옮길 때는 제거된 설정(
zookeeper.*,inter.broker.protocol.version등)을 정리하고node.id·controller.listener.names로 구성해야 해요.