Kafka

KRaft

재심 2023. 11. 28. 13:56

목차

    [KRaft?]

     Actually, the problem is not with ZooKeeper itself but with the concept of external metadata management.
    (사실 문제는 ZooKeeper 자체가 아니라 외부 메타데이터 관리 개념에 있습니다.)

     

    카프카 클러스터를 구성하기 위해서는 메타데이터 관리를 하는 코디네이터 서비스가 필요하고, 거의 대부분이 주키퍼를 통해서 코디네이터 서비스를 구축하고 있다.

    다만 주키퍼를 통한 카프카 외부에서 메타데이터 관리 하다보니 데이터 중복 또는 브로커의 메타데이터와 주키퍼의 메타데이터의 불일치, 시스템 복잡성 증가, 서버나 시스템이 추가로 더 필요하거나 더 많은 자바 프로세스 실행 필요와 같은, 더 많은 자원의 소모 등의 문제점 등이 발생하기 시작했다고 한다. 

    카프카 자체가 아닌 외부에서 메타데이터를 관리하기 때문에 카프카 입장에서 주키퍼 사용시 제약사항이나 한계성 등을 느끼게 되며, 이로 인해 kafka의 확장성에 제한이 되는 부분이 있다고 Confluent는 판단하였다고 한다. 

     

    결국 이러한 여러가지 문제와 고민에 의해서 2019년에 이러한 종속성을 깨고 새로운 메타데이터 관리를 Kafka 자체에 도입할 계획을 만들게 되었다고 한다. 

    (KIP-500을 통해 제안되었다.) 

    https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum

     

    현재 Kafka는 ZooKeeper를 사용하여 파티션 및 브로커에 대한 메타데이터를 저장하고 브로커를 Kafka 컨트롤러로 선택합니다.
     
    우리는 ZooKeeper에 대한 이러한 종속성을 제거하고 싶습니다.
     
    이를 통해 더 확장 가능하고 강력한 방식으로 메타데이터를 관리할 수 있어 더 많은 파티션을 지원할 수 있습니다.
     
    또한 Kafka의 배포 및 구성도 단순화됩니다.

     

    새롭게 메타데이터 관리를 위해서 만들어진 것이 KRaft 모드. KRaft 모드는 이전 컨트롤러를 대체하고 Raft 합의 프로토콜의 이벤트 기반 변형을 사용하는 Kafka의 새로운 쿼럼 컨트롤러 서비스를 사용.

     

    [KRaft Architecture]

    Confluent Blog

     

    전/후 차이 

    AS-IS

    브로커 중 Active Controller가 주키퍼와 커뮤니케이션하여 메타데이터 관리 등을 수행. (하지만 실제로는 다른 브로커들도 주키퍼랑 통신한다고 한다)

    TO-BE

    브로커이지만 주키퍼 역할을 하는 Quorum Controller를 구성하여 주키퍼를 대체한다.  

    • Controller Node: 메타데이터를 관리하는 노드들을 지칭함
    • Broker Node: 기존 브로커 노드를 지칭함

    제안된 아키텍처에서는 3개의 컨트롤러 노드가 3개의 ZooKeeper 노드를 대체

    컨트롤러 노드는 주황색으로 표시된 메타데이터 파티션에 대한 단일 리더를 선택 

     

    컨트롤러가 업데이트를 브로커에 푸시하는 대신 브로커는 이 리더에서 메타데이터 업데이트를 가져옵니다. 이것이 바로 화살표가 컨트롤러를 향하지 않고 컨트롤러를 가리키는 이유입니다.

     

    메타데이터 처리

    Confluent Blog

     

    메타데이터가 더 이상 Zookeeper가 아닌 카프카 내부 토픽으로 관리되기 시작한다고 한다. 

    리더 컨트롤러 노드는 최신 메타데이터를 내부 토픽에 쓰고, 팔로워들은 그걸 읽어서 보관한다.

    (메타데이터는 더 이상 외부에 있지 않게된다.)

    Controller Quorum

    컨트롤러 노드는 메타데이터 로그를 관리하는 Raft 쿼럼으로 구성된다고 한다. 

    이 로그에는 클러스터 메타데이터의 각 변경 사항에 대한 정보가 포함되어 있고, 토픽, 파티션, ISR, config 등과 같이 현재 ZooKeeper에 저장되어 있는 모든 것이 이 로그에 저장된다고 한다.

     

    Raft 알고리즘을 사용하여 컨트롤러 노드는 외부 시스템에 의존하지 않고 자체적으로 리더를 선출

    메타데이터 로그의 리더를 활성 컨트롤러라고 한다. 팔로어 컨트롤러는 활성 컨트롤러에 기록된 데이터를 복제하고 활성 컨트롤러에 장애가 발생할 경우 상시 대기 역할을 하게 된다.

    이제 컨트롤러가 모두 최신 상태를 추적하므로 컨트롤러 장애 조치 시 모든 상태를 새 컨트롤러로 전송하는 긴 다시 로드 기간이 필요하지 않다고 한다. (Raft 알고리즘?)

    ZooKeeper와 마찬가지로 Raft도 계속 실행하려면 쿼럼 구성이 필요하다. (ex: 3개의 컨트롤러 노드라면 1개까지 버틸 수 있음. 5개라면 2개까지 버틸 수 있다. (n/2 + 1))

    컨트롤러는 주기적으로 메타데이터의 스냅샷을 디스크에 기록한다고 함. 

     

    Broker Metadata Management

    1. 브로커노드는 캐시된 메타데이터를 가지고 있는 상태에서 컨트롤러 노드에 변경점을 요청한다. (만약 없으면 다 가져올 것)
    2. 컨트롤러 노드는 변경점을 준다. 
    3. 브로커노드는 받은 정보를 캐싱한다. 

     

    그리고 컨트롤러 노드는 브로커노드에서 요청하는 것뿐만아니라 클라이언트의 메타데이터 요청도 함께 처리할 수 있는 듯 하다. 

     

    KRaft의 장점

    Confluent Blog

     

    갑자기 노드가 다운되었을 때 복구되는 시간이 빠르다는 점이 가장 큰 이점이라고 한다. 이외 아래 장점들이 있다. 

    • Kafka 클러스터는 새로운 메타데이터 관리로 향상된 컨트롤 플레인 성능을 통해 수백만 개의 파티션으로 확장할 수 있습니다.
    • 안정성을 개선하고, 소프트웨어를 간소화하며, Kafka를 보다 쉽게 모니터링, 관리 및 지원할 수 있습니다.
    • Kafka가 전체 시스템에 대한 단일 보안 모델을 가질 수 있도록 합니다.
    • Kafka를 시작하기 위한 간단한 단일 프로세스 방법 제공
    • 컨트롤러 장애 조치(failover)를 거의 즉각적으로 만듭니다.

    Isolated Mode vs Combined Mode

    Confluent Blog

     

    • Isolated Mode: Quorum Controller를 별도로 구성하여 주키퍼 역할을 대체함. (추천)
    • Combined Mode: 브로커가 메시지처리도 하고 Controller 역할도 함. 

     

    Confluent Platform의 cp-ansible을 통한 마이그레이션도 Isolated Mode만 지원한다고 한다. Combined Mode는 운영환경에서는 아직 추천하지 않는다고 함. 

     

     

    KRaft에 대한 주요 FAQ

    Q: 마이그레이션은 어떻게 되나요?

    A: 이미 존재하는 클러스터의 업그레이드는 Confluent Platform 7.6부터 지원 (마이그레이션)

     

    Q: 성능차이가 있을까요?

    A: 성능의 경우 TPS같은건 그대로이지만 메타데이터를 처리하거나 failover에서 개선점이 크다고 한다. 

     

    Q: Isolated Mode와 Combined Mode 중 무엇을 써야 하나요?

    A: Isolated Mode와 Combined Mode (브로커가 KRaft 역할도 함께 수행하는 것)가 있는데 아직 Combined Mode는 운영수준에서 지원하지는 않고 있다고 한다.

    Isolated Mode의 브로커 스펙은 기존 zookeeper 스펙과 비슷하게 잡으면 된다고 한다. 

     

    [Raft 알고리즘?]

    https://raft.github.io/

    https://yoongrammer.tistory.com/50

    Raft : 분산 합의 알고리즘, 여러 서버들 중 일부 서버에 장애가 발생하더라도 기능을 유지하도록 하는 내결함성을 갖고있다. 

     

    분산 합의 알고리즘

    • 합의란 클라이언트와 서버가 동일한 데이터를 공유하는 상태( = 동기화 ) , 하나의 서버와 하나의 클라이언트라면 쉽게 동기화가 가능하다. 
    • 분산 합의란 분산 서버들이 클라이언트와 데이터를 공유하는 상태를 말한다. 
    • 분산 합의 문제 ( distributed consensus problem ) : 분산 서버에서 합의할 때 발생하는 문제 => raft 알고리즘을 통해 해결한다. 

     

    Raft 작동방식

    raft 상에서 서버 노드는 세가지 상태 중 하나를 갖게 된다. 

    • Follower state : leader로부터 AppendEntry메시지를 받아 처리하는 상태. 일정 시간이 이상 AppendEntry 메시지를 받지 못하면 상태를 cadidate로 변경
    • Candidate state : leader로 선출될 수 있는 후보군으로 Candidate 노드는 투표 요청 메시지(Vote request)를 다른 노드에 보내서 과반수 이상의 노드들로부터 투표를 받게 되면 새로운 리더가 된다
    • Leader State :  leader로 선출된 상태, 변경 내역은 leader를 통해서만 follower에게 전달하여 반영어느 시점에서든 최대 하나의 리더만 있을 수 있다.

    yoongrammer님 블로그

     

    term 

    • Leader 선출 시에 할당되는 1씩 증가하는 Sequence번호로써 Leader의 ID역할
    • Term은 Leader가 변경 될 때까지 유지되고 서버 간의 통신 중에 전달된다.
    • Term은 리더 선출(leader election) 때 투표의 중복을 피하기 위해 사용된다.
    • Term이 다른 서버의 term보다 작은 경우 서버는 term을 업데이트한다.
    • Candidate 또는 Leader의 term이 다른 노드보다 작다면 Follower가 된다. 
    • 오래된 term으로 요청이 오면 해당 요청은 거부된다. 

    RPC Protocol 

    Raft는 두 가지 유형의 RPC 프로토콜을 사용 

    • RequestVotes - Candidate 노드에 의해 전달되고 선거기간 동안 투표를 얻기 위해 사용 (candidate → follwer로 전송 )
    • AppendEntries - Leader 노드에 의해 전달되고 로그 항목을 복제하기 위해 사용되거나 하트비트 메커니즘으로도 사용 (하트비트 메커니즘으로 사용될 땐 빈 AppendEntries를 사용) (leader → follower 로 전송)

     

    리더선출

    1. 모든 노드는 follower 상태에서 시작. election timeout 후 follower가 candidate가 되어 새로운 선거 기간(Term)을 시작한다. 
      • election timeout - 팔로워가 후보자가 될 때까지 기다리는 시간 ( 150ms~ 300ms사이로 노드마다 랜덤으로 부여 됨 )
    2. Candidate 노드는 자신에게 투표하고 RequestVotes RPC를 follower에게 전송
    3. Follower가 현재 term내에 투표한 적이 없다면 곧바로 투표 요청을 Candidate에게 응답(Vote)
      • 모든 노드는 term당 한번의 투표만 가능
      • 응답을 한 Follower는 election timeout을 초기화
    4. 과반수 이상의 투표를 받은 Candidate는 Leader가 된다.

     

    리더 선출 후, Leader는 Follower에게 AppendEntries 메시지 전송.( Heartbeat timeout 에 지정된 간격으로 전송 )
    Follower는 election timeout을 초기화하고 각 AppendEntries 메시지에 응답. 이 선거 기간(Term)은 Follower가 Heartbeat 수신을 중단하고 Candidate가 될 때까지 계속된다.

     

    yoongrammer님 블로그

     

    Re-Election

    1. Leader 노드가 죽어 더 이상 AppendEntries메시지 보내지 못하게 되면 election timeout 후 Follower가 Candidate가 되어 새로운 선거 기간(Term)을 시작한다.
    2. Candidate 노드는 자신에게 투표하고 RequestVotes RPC를 Follower에게 보낸다.
    3. Follower가 현재 term내에 투표한 적이 없다면 곧바로 투표 요청을 Candidate에게 응답(Vote)
    4. 과반수 이상의 투표를 받은 candidate는 leader가 된다.

    yoongrammer님 블로그

     

    두 노드가 동시에 Candidate가 되는 상황

    1. election timeout 후 동시에 두 개의 Follower가 Candidate가 되어 새로운 선거 기간(Term)을 시작
    2. Candidate 노드 각각은 자신에게 투표하고 RequestVotes RPC를 각 노드에 전송
    3. Candidate 노드 각각은 다른 하나보다 먼저 단일 Follower 노드에 도달한다. 
    4. Follower가 현재 term내에 투표한 적이 없다면 곧바로 투표 요청을 Candidate에게 응답(Vote)
    5. 과반수를 얻지 못해 Leader를 선출하지 못했다면 election timeout 후 다시 새로운 선거 기간(Term)을 시작
    6. 하나의 노드만 Candidate가 되어 투표를 진행
    7. 이전 선거에서 Candidate였던 노드 D는 더 높은 Term의 RequestVotes 메시지를 받았기 때문에 Follower로 상태를 변경, Candidate에게 응답(Vote).
    8. 과반수 이상의 투표를 받은 candidate는 leade로 선출

    Leader election은 하나의 Leader를 선출할 때까지 1~4를 계속 반복.

     

    yoongrammer님 블로그

     

    로그 복제 (리더 선출 후) 

    이제 시스템에 대한 모든 변경 사항은 Leader를 통해서 진행. 각 변경 사항은 노드의 로그 엔트리(Log Entries)에 추가되고 다른 노드(Follower)에 복제되며 이는 AppendEntry 메시지를 사용하여 수행된다.
    Leader는 Heartbeat timeout 주기마다 한 번씩 AppendEntries 메시지를 Follower에게 전달

     

    로그 엔트리 구성

    • Term - Leader 선출 시에 할당되는 1씩 증가하는 Sequence 번호
    • Index - Log가 저장될 때마다 1씩 증가하는 Sequence 번호 (인덱스는 1부터 시작함)
    • Data

    로그 복제과정

    1. 클라이언트가 Leader에게 변경 사항 전송
    2. 변경 사항은 Leader의 Log Entry에 저장
    3. Leader는 다음 heartbeat에 log를 AppendEntry 메시지 형태로 Follower에게 전달
    4. AppendEntry 메시지를 받은 Follower는 새로운 Log entry를 저장하고 AppendEntryResponse메시지로 성공 응답 전송
    5. 과반수 응답을 받았다면 Leader는 자신의 entry를 commit 하고 Client에게 응답 메시지를 전송
    6. 그리고 Follower들에게도 변경 사항이 commit 되었음을 알리고 알림을 받은 Follower들은 커밋

     

    yoongrammer님 블로그

     

    [KRaft Internals]

    https://developer.confluent.io/courses/architecture/control-plane/ 

     

    Kafka Control Plane: ZooKeeper, KRaft, and Managing Data

    Learn about the shift from ZooKeeper, the legacy Kafka control plane, to KRaft, which leverages a built-in consensus service inside the Kafka cluster based on the Raft protocol.

    developer.confluent.io

     Control Plane과 Data Plane

    Confluent Blog

     

    카프카는 Control Plane과 Data Plane으로 나뉜다. 

    • Control Plane: 기존 주키퍼의 기능. 메타데이터 관리 등을 수행
    • Data Plane: 브로커의 역할. 실제 데이터를 컨트롤 한다. 

    Confluent Blog

    기존 주키퍼 모드에서는 브로커 중 한대가 Controller로 선출되고, Controller와 Zookeeper가 커뮤니케이션하여 클러스터와 메타데이터를 관리하는 방식이었다. 

     

    KRaft Metadata

    Confluent Blog

    KRaft는 KIP-500에서 언급된 Raft 프로토콜의 "합의"를 기반으로 동작한다고 한다.

    클러스터 메타데이터는 __cluster_metadata 라는 토픽 내부에 저장된다. (단일 파티션이라고 한다)

    이 토픽의 파티션 리더가 Controller가 된다. 그래서 해당 토픽에 메시지가 쓰여지면 다른 컨트롤러들은 이를 복제해오는 방식으로 메타데이터의 상태를 유지한다. 

    (즉, 브로드캐스팅 방식이 아니다)

    Confluent Blog

    클러스터 메타데이터는 Kafka 토픽에 저장되므로 데이터를 복제하듯이 메타데이터도 복제된다.

    활성 컨트롤러는 메타데이터 토픽의 Single 파티션의 리더이며 모든 쓰기에 대한 내용을 받게된다고 한다. 

    다른 컨트롤러는 Follower로 해당 변경 사항을 계속해서 복제한다.

     

    그러나 리더를 선출해야 하는 경우 이는 동기화된 복제 세트가 아닌 쿼럼을 통해 수행된다고 한다. 

    따라서 메타데이터 복제에는 ISR이 포함되지 않는다고 한다. 또 다른 차이점은 메타데이터 레코드가 각 노드의 로컬 로그에 기록되는 즉시 디스크에 플러시된다는 차이도 있다고 한다. 

     

     

    리더 선출 과정

    Confluent Blog

     

    리더가 다운되거나 작업중일 경우 새로운 리더를 뽑아야 한다. 그래서 리더 컨트롤러를 선출해야 하는 경우 다른 컨트롤러가 새로운 리더 선출에 참여하게 된다.

    일반적으로 새로운 리더의 필요성을 처음 인식한 컨트롤러는 다른 컨트롤러에 VoteRequest를 보내고, 이 요청에는 후보의 마지막 오프셋과 해당 오프셋과 관련된 에포크가 포함된다. 

    또한 해당 에포크를 증가시키고 이를 후보 에포크로 전달한다. 후보 컨트롤러는 해당 에포크에 대해 스스로 투표할 수도 있다.

     

    Confluent Blog

     

    팔로어 컨트롤러가 VoteRequest를 수신하면 후보자가 전달한 것보다 더 높은 에포크가 표시되었는지 확인한다.

    동일한 에포크의 다른 후보에게 투표했거나 이미 투표한 경우 요청을 거부한다.

    그렇지 않으면 후보자가 전달한 최신 오프셋을 살펴보고 자신의 오프셋과 같거나 높으면 투표를 하게 된다.

    해당 후보 컨트롤러는 이제 자신의 투표권과 방금 부여받은 투표권의 두 가지 투표권을 가진다고 한다. 

    결과적으로 다수의 표를 얻은 첫 번째 컨트롤러가 새로운 리더가 된다.

     

    Confluent Blog

     

    후보자가 과반수 표를 얻은 후에는 자신을 리더로 간주하지만 여전히 이를 다른 컨트롤러에게 알려야 한다.

    이를 위해 새 리더는 새 에포크를 포함한 BeginQuorumEpoch 요청을 다른 컨트롤러에 보내고, 선거가 끝난다. 

    기존 리더 컨트롤러가 다시 온라인 상태가 되면 새 에포크의 새 리더를 따르고 자체 메타데이터 로그를 리더와 함께 최신 상태로 유지하게 된다. 

     

    Confluent Blog

     

    리더 선택이 완료된 후 복제를 덜해오거나 에포크가 안맞는 경우가 있을 수 있다.

    이 경우 팔로어와 리더 모두의 에포크와 오프셋을 사용하여 팔로어는 커밋되지 않은 레코드를 자르고 리더와 동기화하여 데이터를 맞추게 된다. 

     

    Metadata Snapshot

    Confluent Blog

     

    클러스터 메타데이터가 더 이상 필요하지 않다는 것을 알 수 있는 명확한 지점은 없지만 메타데이터 로그가 끝없이 커지면 안된다.

    그래서 이를 스냅샷을 통해 해결한다고 한다. 

    주기적으로 각 컨트롤러와 브로커는 메모리 내 메타데이터 캐시의 스냅샷을 찍는다. 

     

    Confluent Blog

     

    메타데이터 스냅샷이 사용되는 두 가지 주요 상황은 (1)브로커 재시작과 (2)온라인 상태가 되는 새 브로커가 투입되었을 때이다.

     

    기존 브로커가 다시 시작되면

    (1) 최신 스냅샷을 메모리에 로드한다. 그런 다음 스냅샷의 EndOffset부터 시작해서

    (2) 로컬 __cluster_metadata 로그에서 사용 가능한 레코드를 추가한다. 그 다음

    (3) Active 컨트롤러에서 레코드를 가져오기 시작합니다. 가져온 레코드 오프셋이 Active 컨트롤러 LogStartOffset보다 작은 경우 컨트롤러 응답에는 최신 스냅샷의 스냅샷 ID가 포함된다고 한다.

    (4) 이 스냅샷을 가져와 메모리에 로드한 다음 다시 한 번 __cluster_metadata 파티션 리더(Active 컨트롤러)에서 레코드를 계속 가져온다.

     

    새 브로커가 시작되면

    (3) Active 컨트롤러에서 처음으로 레코드를 가져오기 시작한다.  일반적으로 이 오프셋은 Active 컨트롤러 LogStartOffset보다 작으며 컨트롤러 응답에는 최신 스냅샷의 스냅샷 ID가 포함된다고 한다.

    (4) 신규 브로커는 이 스냅샷을 가져와 메모리에 로드한 다음 다시 한 번 __cluster_metadata 파티션 리더(활성 컨트롤러)에서 레코드를 계속 가져온다.

     

     

    [마이그레이션 방법]

    Confluent Blog

     

    1. kraft contoller 생성
    2. 주키퍼에서 메타데이터를 받음 
    3. rolling하며 Kraft와 붙임
    4. 종료 후 주키퍼 노드 제거 

     

    'Kafka' 카테고리의 다른 글

    Kafka Cruise-Control 사용해보기  (0) 2023.08.31
    MirrorMaker2 Basic  (0) 2023.07.24
    MirrorMaker2 - 테스트  (0) 2023.07.24
    클러스터간 메시지 복제  (0) 2023.07.24
    Kafka KRaft Protocol 정리  (0) 2023.05.28