카프카를 운영 환경에서 사용하는 것은 단순히 한 대의 서버에 설치하여 사용하는 것을 넘어, 여러 대의 서버를 하나로 묶어 클러스터(Cluster) 형태로 구성하는 것을 의미한다. 카프카가 분산 시스템으로서 가지는 성능과 고가용성(High Availability)이라는 두 가지 핵심 가치를 모두 극대화하기 위한 최적의 구성 방식이 바로 멀티 노드 클러스터인 것이다.
2. 단일 노드 구성의 명확한 한계
만약 카프카를 단 하나의 노드(서버)에만 설치하여 운영한다면, 증가하는 데이터 처리 요구량을 감당하기 위해 서버의 하드웨어 사양 자체를 높이는 스케일 업(Scale-Up) 방식을 고려해야 한다. 즉, 더 빠른 CPU, 더 많은 메모리, 더 큰 용량의 디스크로 교체하는 것이다. 이 방식은 관리할 서버가 하나이므로 구성이 단순하고 안정적으로 보일 수 있다. 하지만 근본적인 한계점을 내포하고 있다. 첫째, 고사양 하드웨어는 비용이 기하급수적으로 증가하며, 물리적으로 확장할 수 있는 성능에는 명확한 한계가 존재한다. 둘째, 더 치명적인 문제는 이 단일 노드가 단일 장애점(SPOF, Single Point of Failure)이 된다는 것이다. 만약 이 서버에 장애가 발생하면 카프카 시스템 전체가 완전히 중단되는 결과를 초래한다.
3. 분산 구성, 스케일 아웃의 개념
위의 단일 노드 방식의 한계를 극복하기 위해 카프카는 여러 대의 노드를 수평적으로 추가하여 클러스터를 구성하는 스케일 아웃(Scale-Out) 방식을 채택했다. 이는 고가의 장비 한 대에 의존하는 대신, 여러 대의 보편적인 사양을 가진 서버들을 묶어 하나의 거대한 시스템처럼 동작하게 만드는 사상이다. 물론, 여러 대의 노드를 사용한다는 것은 관리의 복잡성과 개별 노드의 장애 가능성이 높아진다는 단점을 수반한다. 분산 시스템에서는 단 하나의 노드에서 발생한 장애가 전체 데이터의 정합성을 깨뜨릴 수 있기 때문이다. 따라서 카프카는 이러한 분산 환경의 단점을 극복하고 성능과 안정성을 모두 확보하기 위한 정교한 내부 동작 원리를 가지고 있다.
4. 멀티 노드 카프카 클러스터의 핵심 원리 (⭐)
카프카 클러스터는 성능과 가용성이라는 두 마리 토끼를 잡기 위해 파티션 분산과 데이터 복제라는 두 가지 핵심 전략을 사용한다.
4.1. 성능 향상의 비결: 파티션의 분산
카프카는 하나의 토픽을 여러 개의 파티션(Partition)으로 나누어 저장한다. 클러스터 환경에서는 이 파티션들을 여러 노드에 분산하여 배치할 수 있다. 예를 들어, 3개의 파티션을 가진 토픽이 있다면, 3대의 브로커가 각각 하나의 파티션을 전담하여 처리하는 것이다. 이를 통해 데이터 처리 부하가 여러 노드로 분산되어, 노드를 증설하는 만큼 전체 처리량이 거의 선형적으로 증가하는 성능적 이점을 얻게 된다.
4.2. 가용성 보장의 비결: 데이터 복제 (Replication)
분산 시스템의 취약점인 개별 노드의 장애 문제는 데이터 복제를 통해 해결한다. 카프카는 각 파티션에 대해 하나의 리더(Leader)와 여러 개의 팔로워(Follower) 복제본을 생성하여 서로 다른 노드에 저장한다. 모든 데이터의 읽기와 쓰기는 리더를 통해서만 이루어지며, 팔로워들은 리더의 데이터를 실시간으로 복제하여 항상 동기화된 상태를 유지한다.
만약 리더 파티션이 있는 노드에 장애가 발생하더라도, 클러스터는 즉시 다른 노드에 있던 팔로워 중 하나를 새로운 리더로 선출한다. 이 과정을 통해 서비스는 장애를 인지하지 못한 채 중단 없이 데이터 처리를 계속할 수 있다. 이것이 데이터 유실 없이 최적의 가용성을 보장하는 카프카 클러스터의 핵심적인 동작 방식인 것이다.
5. 카프카 리플리케이션(Replication)
카프카의 핵심 안정성 기능은 리플리케이션(Replication, 복제)이다. 카프카 클러스터 운영 시 개별 브로커(서버)에 장애가 발생할 수 있으나, 리플리케이션이 구성되어 있다면 데이터 유실 없이 서비스를 지속할 수 있다.
5.1. 리더(Leader)와 팔로워(Follower)
리플리케이션의 동작을 이해하기 위한 핵심 개념은 리더와 팔로워이다.
👑 리더(Leader) 파티션: 오직 리더만이 데이터의 읽기(Read)와 쓰기(Write)를 담당하는 원본 파티션이다. 모든 데이터 처리는 리더를 통해서만 이루어진다.
👥 팔로워(Follower) 파티션: 리더의 모든 데이터를 그대로 복제하는 백업 파티션이다. 평상시에는 데이터 처리에 관여하지 않고 리더의 데이터를 동기화하는 역할만 수행한다.
🤝 ISR (In-Sync Replicas): 'In-Sync', 즉 리더와 완벽하게 동기화된 것으로 간주되는 신뢰할 수 있는 복제본 그룹이다. 이 그룹은 리더 자신과, 리더의 최신 데이터를 모두 가진 팔로워들로 구성된다. 리더에 장애가 발생하면, 카프카는 오직 ISR에 속한 팔로워 중에서만 새로운 리더를 선출한다.
Leader: 2: 0번 파티션의 리더는 2번 브로커에 존재한다. 이 파티션의 모든 데이터 읽기/쓰기는 2번 브로커를 통해 처리된다.
Replicas: 2,3,1: 0번 파티션의 모든 복제본은 2번, 3번, 1번 브로커에 하나씩 분산되어 있다.
Isr: 2,3,1: 현재 세 복제본 모두 리더와 동기화가 잘 된 상태(In-Sync)이다. 만약 리더인 2번 브로커에 장애가 발생하면, ISR 그룹에 속한 3번 또는 1번 브로커가 새로운 리더로 선출된다.
나머지 줄도 동일한 구조이다.
1번 파티션의 리더는 3번 브로커, 2번 파티션의 리더는 1번 브로커에 있다. 이처럼 카프카는 리더의 역할을 여러 브로커에 분산시켜 특정 브로커에 부하가 집중되는 것을 방지한다.
5.4. 핵심 요약
리플리케이션은 데이터 유실 방지와 고가용성 확보를 위한 카프카의 핵심 기능이다.
모든 읽기와 쓰기는 리더 파티션을 통해서만 이루어지며, 팔로워 파티션은 이를 복제한다.
replication-factor는 운영 중인 브로커의 개수보다 클 수 없다.
리더에 장애가 발생하면 ISR 그룹 내의 팔로워 중 하나가 새로운 리더가 되어 서비스 중단을 막는다.
6. 카프카 Replication의 Leader와 Follower
6.1. 카프카 리플리케이션의 리더와 팔로워 동작 방식
카프카의 모든 데이터 읽기/쓰기 및 복제 과정은 리더(Leader) 파티션을 중심으로 엄격한 규칙에 따라 동작한다.
6.2. 데이터 처리의 중심: 리더 파티션
모든 읽기와 쓰기는 리더를 통한다. 프로듀서가 메시지를 보내거나 컨슈머가 메시지를 읽을 때, 해당 작업은 반드시 파티션의 리더에게만 요청되어야 한다. 팔로워는 이 과정에 직접적으로 관여하지 않는다.
리더 브로커의 두 가지 역할 특정 파티션의 리더를 호스팅하는 브로커는 두 가지 중요한 임무를 동시에 수행한다.
클라이언트 요청 처리: 프로듀서의 쓰기 요청과 컨슈머의 읽기 요청을 처리한다.
복제 데이터 제공: 자신을 따르는 팔로워 파티션들의 데이터 동기화(복제) 요청에 응답한다.
6.3. 복제 흐름의 비밀: Push가 아닌 Pull 방식
개념적 흐름과 실제 구현의 차이 데이터 복제는 리더에서 팔로워 방향으로 일어나는 것이 맞다. 개념적으로는 리더가 팔로워에게 데이터를 밀어주는(Push) 것처럼 보일 수 있다.
실제 구현은 Pull 방식이다. 내부 구현을 보면, 팔로워가 주기적으로 리더에게 새로운 메시지가 있는지 물어보고 가져오는(Pull/Fetch) 방식으로 동작한다. 이는 각 팔로워가 자신의 상태와 능력에 맞게 데이터를 복제할 수 있게 하여, 특정 팔로워의 지연이 전체 시스템에 영향을 미치는 것을 방지하는 더 안정적인 설계이다.
6.4. 단일 파티션의 Replication(복제)
6.5. 멀티 파티션 환경에서의 동작
각 파티션은 독립적으로 동작한다. 토픽에 여러 파티션이 존재하더라도, 위에서 설명한 리더 중심의 원칙은 모든 파티션에 개별적으로, 그리고 동일하게 적용된다.
프로듀서의 동작 예시 프로듀서가 파티션 1을 타겟으로 메시지를 보내면, 프로듀서는 카프카 클러스터로부터 파티션 1의 리더가 어떤 브로커에 있는지 정보를 얻는다. 그 후, 해당 브로커로 직접 메시지를 전달한다. 메시지를 받은 리더 브로커는 데이터를 저장하고, 이 파티션을 복제하는 다른 팔로워 브로커들이 데이터를 가져갈 수 있도록 준비한다.
보내는 메시지가 Partition 1이면 Partition 1을 Leader로 가지는 브로커로 전달된 후, 나머지 Follow Partition에게 복제한다.
Kafka 2.4 부터 Consumer는 follower fetching이 가능하다.
7. Java Producer Clients에서 Multi Brokers 접속
Java 프로듀서가 여러 브로커로 구성된 카프카 클러스터에 접속하는 과정은 '초기 접속을 통한 메타데이터 획득'과 '리더 파티션에 대한 직접 접속' 두 단계로 나뉜다.
이때 주의해야 할 점은 이게 이 주소의 포트로 각각 동시에 연결한다는 게 아니다. 정확히는, 이 주소 목록은 프로듀서가 클러스터에 접속하기 위한 '초기 진입점(Entry Point) 후보 리스트'라는 의미이다. 즉, "아 내가 브로커 클러스터에 접근할 때 이것들 중 하나로 가면 되는구나"의 역할을 하는것이다.
프로듀서는 목록의 첫 번째 주소(:9092)로 접속을 시도하고, 만약 성공하면 더 이상 다른 주소로는 접속하지 않는다. 만약 첫 번째 주소의 브로커가 다운된 상태라면, 그때서야 다음 주소(:9093)로 접속을 시도한다. 이 과정의 목적은 목록에 있는 어느 브로커든 살아있는 하나와 연결하여, 클러스터 전체의 지도 정보인 메타데이터를 받아오는 것이다. 따라서 이 설정은 초기 접속 시의 장애 대비를 위한 안전장치이다.
메타데이터 자체를 가져오는건 어떤 브로커에 접근하든 가져올 수 있다. 다시말해서, 9092로 접속하더라도 각 브로커의 메타데이터(Broker #1의 Leader Partition은 무엇인지, Broker #2의 Leader Partition이 무엇인지)를 가져올 수 있다.
8. Zookeeper
주키퍼(Zookeeper)는 카프카와 같은 분산 시스템에서 필수적으로 사용되는 코디네이션(coordination) 시스템이다. 여러 대의 서버(브로커)가 동시에 동작하는 클러스터 환경에서는, 각 노드의 상태를 일관되게 관리하고 중요한 결정을 내릴 수 있는 체계가 필요하다. 주키퍼는 바로 이러한 역할을 수행한다.
비유하자면, 카프카 클러스터라는 오케스트라가 있다면, 각 브로커는 악기를 연주하는 연주자이고, 주키퍼는 지휘자이다. 지휘자는 어떤 연주자가 연주 가능한지 확인하고, 연주자가 빠지면 즉시 다른 연주자에게 파트를 맡기며, 전체 연주가 끊기지 않도록 흐름을 조율한다.
Q. 왜 주키퍼가 등장했는가? A. 카프카 초창기에는 브로커들끼리만으로는 안정적인 클러스터 관리가 어려웠다. 어떤 브로커가 죽었는지 실시간으로 감지하기 힘들다. 리더 선출 같은 중요한 결정을 동기화하기 어렵다. 토픽과 파티션의 메타데이터를 일관되게 유지하기 힘들다. 이 문제를 해결하기 위해 카프카는 외부 시스템인 주키퍼를 이용했다. 다만, 최근 버전(카프카 2.8 이상)에서는 KRaft(Kafka Raft) 모드가 도입되면서 주키퍼 없이도 클러스터를 운영할 수 있게 발전하고 있다.
8.1. Zookeeper의 기본 개념
주키퍼는 단순한 상태 저장소가 아니라, 분산 환경에서 발생하는 복잡한 동기화 문제를 해결해 주는 시스템이다.
클러스터 내 개별 노드의 중요한 상태 정보를 저장하고 관리한다.
리더 노드(여기서의 리더는 파티션 리더가 아니라, 전체 클러스터를 관리하는 Controller Broker)를 선출한다.
분산 락(Distributed Lock) 기능을 제공하여 여러 노드가 동시에 동일한 리소스를 갱신할 때 충돌을 방지한다.
데이터는 트리 구조의 ZNode라는 단위로 저장된다.
예: /brokers/ids/1 → 브로커 1의 상태 정보
예: /controller → 현재 컨트롤러 브로커 정보
노드(브로커)는 이 ZNode를 지속적으로 모니터링하며, 값이 변경되면 Watch Event가 발생하여 즉시 변경 사실을 알 수 있다.
주키퍼 자체도 클러스터링이 가능하여 고가용성을 보장한다.
즉, 카프카는 주키퍼 덕분에 브로커의 생존 여부를 실시간으로 확인하고, 브로커의 장애 발생 시 신속하게 리더 선출과 데이터 재분배를 할 수 있게 된다.
8.2. Kafka 클러스터에서 Zookeeper의 역할
카프카 클러스터에서 주키퍼는 크게 세 가지 일을 한다.
Controller Broker 선출
여러 브로커 중 하나를 컨트롤러로 지정한다.
컨트롤러는 각 파티션의 리더 브로커를 선출하고, 브로커 다운 시 새로운 리더를 정하는 핵심 역할을 담당한다.
Broker Membership 관리
현재 클러스터에 어떤 브로커가 참여하고 있는지 관리한다.
새로운 브로커가 들어오거나 기존 브로커가 나가면, 그 사실을 모든 노드에 통보한다.
Topic 메타데이터 관리
토픽의 파티션 개수, 레플리카 수, 파티션 분배 정보 등을 저장한다.
모든 브로커는 이 정보를 기반으로 메시지를 어디에 저장하고 복제할지 판단한다.
8.3. Zookeeper와 Broker의 상호작용 흐름
브로커는 주기적으로 주키퍼와 통신하며 자신이 살아있다는 것을 알린다. 이를 세션 하트비트(Heartbeat)라고 한다.
브로커는 zookeeper.session.timeout.ms 이내에 하트비트를 보내야 한다.
만약 주키퍼가 해당 시간 안에 하트비트를 받지 못하면, 브로커가 죽은 것으로 간주한다.
그러면 주키퍼는 해당 브로커의 정보를 삭제하고, 컨트롤러에게 “이 브로커가 다운됐다”라는 이벤트를 알린다.
컨트롤러는 즉시 다운된 브로커가 담당하던 파티션에 대해 새로운 리더를 선출하고, 다른 브로커들에게 변경된 메타데이터를 전달한다.
만약 다운된 브로커가 컨트롤러라면? → 주키퍼는 가장 먼저 접속한 다른 브로커를 새로운 컨트롤러로 선출한다.
즉, 브로커의 갑작스러운 종료 → 하트비트 미응답 → 주키퍼 이벤트 발생 → 컨트롤러 조치 → 전체 브로커에 반영, 이런 흐름으로 클러스터가 안정적으로 동작하게 된다.
8.4. Controller Broker의 역할
8.4.1. 개요
Zookeeper에 가장 처음 접속을 요청한 Broker가 Controller가 된다.
Controller는 파티션에 대한 Leader Election을 수행한다.
Controller는 Zookeeper로 부터 broker 추가/Down등의 정보를 받으면 해당 broker로 인해 영향을 받는 파티션들에 대해서 새로운 Leader Election을 수행한다.
8.4.2. Controller의 Leader Election 수행 프로세스
1. Broker #3이 Shutdown 되고 Zookeeper는 session 기간동안 Heartbeat이 오지 않으므로 해당 브로커 노드 정보 갱신
2. Controller는 Zookeeper를 모니터링 하던 중 Watch Event로 Broker#3에 대한 Down 정보를 받음.
3. Controller는 다운된 브로커가 관리하던 파티션들에 대해 새로운 Leader/Follower 결정 (사진처럼 Broker #2가 partion #3을 Leader로 설정되었다고 가정)
4. 결정된 새로운 Leader/Follower 정보를 Zookeeper에 저장하고 해당 파티션을 복제하는 모든 브로커들에게 새로운 Leader/Follower 정보를 전달하고 새로운 Leader로 부터 복제 수행할 것을 요청
5. Controller는 모든 브로커가 가지는 Metadatacache를 새로운 Leader/Follower 정보로 갱신할 것을 요청
8.5. ISR(In-Sync Replicas)
8.5.1. ISR 개요
Follower들은 누구라도 Leader 가 될 수 있지만, 단, ISR 내에 있는 Follower들만 가능.
파티션의 Leader 브로커는 Follower 파티션의 브로커들이 Leader가 될 수 있는지 지속적으로 모니터링 수행하 여 ISR을 관리
Leader 파티션의 메시지를 Follower가 빠르게 복제하지 못하고 뒤쳐질 경우 ISR에서 해당 Follower는 제거되며 Leader가 문제가 생길 때 차기 Leader가 될 수 없음.
8.5.2. ISR 조건
주키퍼 연결: 팔로워 브로커가 주키퍼와의 세션을 유지해야 한다 (session.timeout.ms).
복제 지연 없음: 팔로워가 리더의 최신 오프셋과의 차이(lag)가 replica.lag.time.max.ms(기본 10초) 동안 임계값을 넘지 않아야 한다. 즉, 주기적으로 리더에게 Fetch 요청을 보내 데이터를 복제해야 한다.
Leader Partition #1에 기록된건 999이기 때문에 1과 충돌이 난다. 따라서 "Broker #2의 Follower Partition #1이 잘 따라오지 못하고 있구나" 라고 판단하여 Follower Partition #1을 ISR 그룹에서 제외시킨다.
8.6. ISR 중요 파라미터
ISR 중요 파라미터로는 min.insync.replicas가 있는데, 해당 내용을 학습하기 위해서는 acks=all에서의 send 방식에 대해 알고 있어야 한다. 다시 한번 더 복습해보자.
8.6.1. akcs=all 에서의 send 방식
Producer는 Leader broke가 메시지 A를 정상적으로 받은 뒤 모든 Replicator에 복제를 수행한 뒤에 보내는 Ack 메시지를 받은 후 다음 메시지인 메시 지 B를 바로 전송. 만약 오류 메시지를 브로커로 부터 받으면 메시지 A를 재전송.
메시지 A가 모든 Replicator에 완벽하게 복사되었는지의 여부까지 확인후에 메시지 B를 전송.
메시지 손실이 되지 않도록 모든 장애 상황을 감안한 전송 모드이지만 Ack를 오래 기다려야 하므로 상대적으로 전송속도가 느림.
8.6.2. min.insync.replicas
min.insync.replicas는 Producer가 `acks=all` 옵션으로 메시지를 보낼 때, 반드시 응답해야 하는 ISR의 최소 브로커 수를 의미한다.
Producer가 메시지를 성공적으로 보냈다는 응답(ack)을 받기 위해, 메시지가 최소한 몇 개의 복제본(리더 + 팔로워)에 쓰여져야 하는지를 정의한다. 예를 들어, 복제 계수(replication factor)가 3이고 min.insync.replicas=2라면, 최소 2개의 복제본(리더 + 팔로워 1개)에 데이터가 저장되어야 메시지 전송이 성공한다.
만약 ISR의 크기가 이 값보다 작아지면(예: 브로커 1대가 죽어 ISR 크기가 1이 됨) Producer의 acks=all 전송은 더 이상 메시지를 보낼 수 없게 된다. (Exception 발생) 이는 가용성보다 데이터 무손실을 우선시하는 안전장치이다.
위의 사진을 예시로 들자면, 메시지 A가 Broker #1(Leader Partition)으로 전송 되었다면, Broker #2와 Broker #3에 있는 각각의 Follow Partition에 복제가 된 후 Producer에게 ACK 응답을 준다.
그럼 만약 Broker #3의 Follow Partition이 종료되면 어떻게 될까? 아직 Broker #1과 Broker #2에 Partition이 있기 때문에 min.insync.replicas=2의 조건에 충족된다. 따라서 ACK를 응답해줄 수 있다.
반면 Broker #2와 Broker #3 둘 다 종료되면 어떻게 될까? 현재 남아있는 Partition이 Broker #1에 있는거 하나밖에 없기 때문에 조건을 충족하지 못하고 Error(Exception)이 발생한다.
9. Leader Election
9.1. Preferred Leader Election
case 1)
파티션 별로 최초 할당된 Leader/Follower Broker설정을 Preferred Broker로 그대로 유지
Broker가 shutdown후 재 기동될 때 Preferred Leader Broker를 일정 시간 이후에 재선출