ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Consistency Core 정리
    Development/Architecture 2026. 5. 3. 15:54

     강한 일관성을 보장하려면 노드 간 합의(consensus)가 필요하고, 합의는 노드 수가 늘어날수록 비용이 폭증한다. 그렇다고 일관성을 포기하면 데이터 정합성이 깨지고, 합의 비용을 감수하면 확장성이 천장에 부딪힌다.

     이 딜레마에 대한 정형화된 해법이 Consistency Core(일관성 코어) 패턴이다. 이미 우리가 쓰고 있는 Kafka, Kubernetes, HDFS 같은 시스템들이 모두 이 패턴의 구체적인 구현체다.

     이 글에서는 Consistency Core 가 무엇이고, 왜 필요하며, Kafka KRaft 를 중심으로 실제로 어떻게 구현되는지 정리해본다.

    문제 정의: 합의 비용은 노드 수에 비례하지 않는다

     분산 합의 알고리즘(Raft, Paxos)은 quorum, 즉 과반수 노드의 동의가 있어야 진행된다. 노드 수가 늘어나면 quorum 확보 비용이 어떻게 변하는지 보자.

    노드 수 Quorum 합의 특성

    3 2 빠르고 안정적
    5 3 운영하기 좋은 균형점
    7 4 합의 속도 저하 시작
    100 51 사실상 불가능

     100대 노드가 모든 결정을 합의로 처리한다고 상상해보자. 매 결정마다 51대 이상의 응답을 기다려야 하고, 한 노드만 느려도 전체가 대기한다. 네트워크 round trip이 노드 수에 비례해 늘어나고, 장애 복구는 더 오래 걸린다.

     그렇다고 강한 일관성을 포기할 수도 없다. 클러스터 멤버십, 리더 정보, 설정 같은 정보는 모든 노드가 같은 view를 가져야 시스템이 안전하게 동작한다.

     

    핵심 아이디어: Consistency Core

    강한 일관성이 정말로 필요한 부분만 작은 클러스터로 분리하고, 나머지는 그 코어를 참조하면서 약한 일관성으로 운영한다.

                      ┌──────────────────────┐
                      │  Consistency Core    │
                      │  (3~5 노드, Raft)    │
                      │  - 강한 일관성       │
                      │  - 적은 데이터       │
                      │  - 모든 결정의 진실  │
                      └──────────┬───────────┘
                                 │ (메타데이터 조회/구독)
                  ┌──────────────┼──────────────┐
                  │              │              │
             ┌────▼────┐    ┌────▼────┐    ┌────▼────┐
             │ Worker  │    │ Worker  │    │ Worker  │
             │ Node 1  │    │ Node 2  │    │ ... N   │
             └─────────┘    └─────────┘    └─────────┘
                (대량 데이터, 약한 일관성, 수평 확장)
    

     코어는 작게 유지해서 합의 비용을 낮추고, 워커는 코어의 결정을 따르면서 자유롭게 수평 확장한다. 책임이 명확하게 분리되는 게 핵심이다.

     

    무엇이 코어에 들어가고 무엇이 워커로 빠지는가

    코어가 책임지는 것 (강한 일관성 필요, 양은 적음)

    • 클러스터 멤버십: 어떤 노드가 살아있는가
    • Leader/Owner 정보: 각 파티션의 리더는 누구인가
    • 설정/스키마: 토픽 설정, 권한
    • 글로벌 카운터: producer ID, transaction ID
    • Lock/Lease: 분산 락, 임시 소유권

     이 데이터들의 공통점은 양은 적지만 모두가 동일한 view를 가져야 안전하다는 점이다. 두 노드가 동시에 자기를 leader 라고 믿으면 split-brain 이 발생하고, 같은 producer ID 가 두 곳에서 발급되면 데이터 정합성이 깨진다.

     

    워커가 책임지는 것 (양은 많고, 약간의 stale read 허용 가능)

    • 실제 메시지/데이터
    • 사용자 트래픽 처리
    • 대용량 저장

     반대로 이런 데이터들은 양이 압도적으로 많지만 노드별로 잠시 다른 view 를 가져도 큰 문제가 안 된다. 정확히 말하면 다른 메커니즘(예: leader-follower replication)으로 일관성을 따로 보장한다.

     

    실제 사례

    Kafka (KRaft)

    Kafka 의 KRaft 가 Consistency Core 패턴의 교과서적 구현이다.

    KRaft Controllers (3~5대) ← Consistency Core
      - 토픽/파티션 메타데이터
      - Leader election
      - Broker 등록 정보
      - Configuration
    
    Brokers (수십~수백대) ← Workers
      - 실제 메시지 데이터
      - Producer/Consumer 처리
    

     컨트롤러 quorum은 Raft로 강한 일관성을 보장하지만, broker는 그렇지 않다. Broker는 컨트롤러로부터 metadata를 fetch해서 자기 메모리에 캐시하고, 그 정보를 바탕으로 자기 partition의 데이터를 처리한다.

     KRaft 이전에는 ZooKeeper가 외부 Consistency Core 역할을 했다. KRaft는 그걸 Kafka 내부로 흡수한 것이다. 외부 의존성을 줄이고, 메타데이터 처리 경로를 단순화한 결과물이다.

     

    Kubernetes

    etcd (3~5대) ← Consistency Core
      - Pod, Service, ConfigMap 정의
      - 클러스터 상태
      - Lease (leader election용)
    
    kubelet (수천 노드) ← Workers
      - 실제 컨테이너 실행
      - 사용자 워크로드
    

     API server는 etcd를 거쳐 모든 결정을 내리고, kubelet은 그 결정을 받아서 자기 노드에서 실행한다. 수천 대의 워커 노드가 있어도 etcd는 3~5대로 유지된다.

     

    HDFS

    NameNode (1~2대 HA) ← Consistency Core
      - 파일/디렉터리 메타데이터
      - 블록 위치 정보
    
    DataNode (수백~수천대) ← Workers
      - 실제 파일 블록 저장
    

     NameNode가 모든 메타데이터의 진실의 근원이고, DataNode는 실제 데이터를 저장한다. NameNode가 SPOF가 되는 문제 때문에 HA 구성이 필수가 되었고, 이것도 결국 작은 코어를 더 안전하게 만드는 방향으로 진화한 사례다.

     

    비교: Cassandra의 다른 선택

     Cassandra는 Consistency Core를 의도적으로 두지 않는다. Gossip 프로토콜로 모든 노드가 P2P로 상태를 교환한다. 이건 다른 trade-off다. Single point of bottleneck은 없지만, 강한 일관성을 보장하기 어렵고 운영 복잡도가 올라간다. Metadata-heavy하지 않고 write-heavy한 워크로드엔 더 적합한 모델이다.

     이게 시사하는 바는, Consistency Core가 만능이 아니라 특정 trade-off를 선택한 결과라는 점이다. 워크로드 특성에 따라 다른 선택지가 더 나을 수 있다.

     

    KRaft에서 본 두 layer의 leader election

    Consistency Core 패턴을 깊이 이해하려면 Kafka KRaft가 leader election을 어떻게 처리하는지 보는 게 좋다. 여기에는 두 층위의 leader election이 있는데, 이 둘을 구분하지 못하면 동작 모델이 흐려진다.

    Layer 1: Controller Quorum 내부의 Raft Leader Election

    • 누가: 컨트롤러 노드들끼리 (3~5대)
    • 무엇을: 컨트롤러 quorum 의 active controller 결정
    • 방법: Raft 알고리즘 (term 증가 → vote 요청 → quorum 동의)
    • 언제: 컨트롤러 기동, active controller 장애

     Term을 올리고 표를 받고 과반을 확보하는 Raft의 정통 election 절차를 따른다.

    Layer 2: 각 Topic Partition 의 Leader 결정

    • 누가: Active controller가 결정
    • 무엇을: 각 partition의 leader broker와 ISR
    • 방법: 선거가 아니라 active controller의 단독 결정 (assignment)
    • 언제: Topic 생성, broker 장애, partition reassignment

     Broker는 컨트롤러에게 "내가 leader 할게요"라고 요청하지 않는다. 컨트롤러가 메타데이터와 정책을 보고 일방적으로 결정하고, broker들은 metadata log를 fetch하다가 자기 역할 변경을 인지한다.

     

     Broker 장애 시의 흐름을 보면 이 모델이 명확해진다.

    1. Broker 1이 죽음 (이 broker가 partition-0의 leader였음)
    
    2. Active Controller가 broker 1의 죽음을 감지
       - Heartbeat 타임아웃으로 fenced 상태 전환
    
    3. Active Controller가 영향받는 partition 스캔
       - ISR이었던 [1,2,3] 중 broker 1 제외한 [2,3]에서 새 leader 선택
       - preferred replica 순서로 → broker 2
    
    4. 새 결정을 metadata log에 record로 append
       PartitionChangeRecord(
         topic=UUID-A, partition=0,
         leader=2,           ← 새 leader
         isr=[2,3],
         leaderEpoch=1       ← epoch 증가
       )
    
    5. Controller quorum 내부 Raft 합의 → commit
    
    6. 모든 broker가 변경을 fetch하고 자기 역할 인지
       - Broker 2: "이제 내가 partition-0의 leader구나"
       - Broker 3: "leader가 broker 2로 바뀌었네, 거기로 fetch해야지"
    

     

    왜 임명 모델인가

     왜 Layer 2는 선거가 아니라 임명일까. 두 가지 이유가 있다.

     첫째, Consistency Core가 이미 권위 있는 진실의 근원이다. Controller 는 모든 broker 의 상태, ISR, leader 정보를 알고 있다. 이미 가진 정보로 결정할 수 있는 일을 굳이 broker 들끼리 분산 합의로 풀 이유가 없다.

     둘째, ISR 정책이 결정론적이다. Kafka 는 leader 후보 선택에 명확한 규칙이 있다. ISR에 속한 broker만 후보가 되고, preferred replica order 에 따라 선택된다. 같은 metadata 상태에서 controller가 계산하면 항상 같은 답이 나온다. 결정론적인 일에 굳이 vote 메커니즘을 쓸 필요가 없다.

     일관성 코어가 권위를 갖고 결정하면, 워커 입장의 복잡한 분산 합의를 피할 수 있다. 덕분에 수만 개의 partition에 대한 leader 결정을 모두 broker 끼리 선거를 할 필요가 없다.

     

    패턴의 trade-off

    얻는 것

    • 워커 노드는 거의 무한 수평 확장 가능
    • 코어는 작아서 합의가 빠름
    • 책임 분리가 명확 (메타데이터 vs 데이터)
    • 운영 모델이 단순 (한 곳만 보면 시스템 상태를 알 수 있음)

    잃는 것

    • 코어가 critical path에 있음 → 장애 시 전체 영향
      • HA 구성 필수 (3~5대 quorum)
    • 코어 용량이 시스템 전체의 천장이 됨
      • 메타데이터를 무한정 코어에 넣을 수 없음
    • 워커는 코어 정보를 캐시해서 쓰므로 약간의 stale read 발생
      • eventual consistency를 받아들이는 설계 필요
    • 모든 메타데이터 변경이 코어를 거쳐야 하므로 throughput 한계 존재

     

    관련 패턴들

    Consistency Core는 단독으로 쓰이지 않고, 다음 패턴들과 조합되어 등장한다.

    • Replicated Log: 코어 내부에서 합의를 이루는 메커니즘 (Raft)
    • Lease: 코어가 워커에게 임시 권한을 부여하는 방식
    • Generation Clock / Epoch: leader 변경을 안전하게 처리
    • Heartbeat: 코어가 워커의 생사 확인
    • Gossip Dissemination: 코어 결정을 워커들에게 전파

    Raft, etcd, KRaft가 모두 이 패턴들의 조합으로 만들어진 것이고, 그래서 Patterns of Distributed Systems 카탈로그를 통독하면 분산 시스템의 큰 그림이 잡힌다.

     

    참고

    반응형

    댓글

Designed by Tistory.