Post

Kafka 입문6

Kafka 입문6

주키퍼 사용에 따른 제약사항

컨슈머의 오프셋 저장소 전환

카프카 2.0 이전 버전에서는 컨슈머 그룹의 저장소로 주키퍼의 지노드를 이용했음

점점 메시지 수가 늘어나면서 오프셋도 커지고 컨슈머의 숫자도 늘어남에 따라 주키퍼에 대한 부담이 높아짐

결국 2016년에 출시된 카프카 0.10 버전부터 컨슈머의 오프셋 저장소를 카프카의 내부 토픽인 __consumer_offests로 이전하면서 컨슈머의 주키퍼 의존성은 완전히 제거함

각기 다른 애플리케이션 운영에 대한 부담

  • 각기 다른 API 사용에 따른 부담
  • 운영의 어려움
  • 보안 문제
  • 메타데이터 관리 문제
    • 메타데이터가 커졌을 경우
      • 주키퍼에는 카프카에서 사용하는 모든 메타데이터가 저장되어 있음 (카프카의 토픽에 대한 메타데이터도 포함)
      • 카프카의 사용량이 높은 경우 사용하는 토픽의 수도 많을 것이며 메타데이터도 매우 클 것
      • 컨트롤러의 주 역할은 파티션의 리더를 선출하고 토픽의 메타데이터를 캐시하는 일
      • 이 컨트롤러에 문제가 발생해 다운된다면, 카프카 클러스터 내의 다른 브로커 중 하나가 컨트롤러 역할을 맡게 되고, 이떄 주키퍼로부터 모든 메타데이터를 읽어옴
      • 만약 토픽이 매우 많다면, 그만큼 컨트롤러가 액티브 상태가 되기까지 지연이 발생할 것
    • 주키퍼와 컨트롤러 간의 불일치
      • 주키퍼가 카프카의 메타데이터를 저장하지만, 주키퍼와 카프카는 서로 다른 애플리케이션이므로, 변경사항이 발생하는 경우 길게는 수 초 동안 컨트롤러와 주키퍼 간의 메타데이터가 불일치하는 상황이 발생함

위 이슈는 카프카와 주키퍼를 따로 관리 해야 하는 것에 대한 운영 리소스가 2배가 되는 것

주키퍼 의존성을 제거한 카프카

기존 주키퍼 의존성 있는 카프카 구성도

주키퍼 활용 카프카 이미지

  • 주키퍼를 통해 선정된 브로커 중 하나의 컨트롤러가 주키퍼와의 통신을 담당하며 메타데이터 업데이트 등의 역할을 수행함
  • 컨트롤러는 이러한 메타데이터들을 나머지 브로커들에게 푸시하는 일을 함

주키퍼에 대한 의존성을 제거한 카프카 구성도

주키퍼 의존성 제거 카프카 이미지

  • 주키퍼의 역할을 대체하는 컨트롤러가 3대 생김
  • 주키퍼에서도 리더와 팔로워가 존재하듯이 컨트롤러 노드에는 하나의 리더가 존재함
  • 카프카 클러스터 내 나머지 모든 브로커는 컨트롤러가 푸시하는 방식이 아니라 컨트롤러부터 당겨오는 풀 방식으로 변경됨

Kraft 합의 프로토콜

Raft

  • 내부적으로 합의를 통해 정보를 유지하는데, 이러한 합의를 위해서는 여러 서버들의 동의를 얻어야 함
  • 일반적인 합의는 과반수 찬성 방식을 따르기 때문에 3대의 클러스터에서 1대가 고장 나거나 또는 5대의 클러스터에서 2대가 고장나더라도 클러스터는 중지하지 않고 안정적인 서비스를 할 수 있음
  • 래프트의 리더 선출 과정을 살펴보면 노드들은 팔로워, 후보자, 리더 라는 총 세가지 상태를 갖게 되면 처음 시작할 때 모든 노드는 팔로워임
  • 노드들은 리더로부터 하트비트를 받아 활성 상태를 체크하는데, 타임아웃 시간 동안 하트비트를 받지 못하면 팔로워 상태의 노드는 후보자 상태로 바뀌고 다른 노드들에게 투표를 요청함
  • 후보자 상태의 노드가 다른 노드들로부터 과반수 이상의 투표를 받으면 새로운 리더 노드로 변경됨
  • 컨트롤러 끼리는 리더가 팔로워에게 데이터를 주는 푸시 기반 처리 방식
  • 인덱스와 텀
    • 텀(term): 리더가 선출될 때마다 1씩 증가하는 임기 번호. 한 텀에는 최대 하나의 리더만 존재할 수 있으며, 후보자는 자신의 텀을 올려 투표를 요청함. 노드는 자신보다 낮은 텀을 가진 리더의 요청을 오래된(stale) 것으로 판단해 거부함
    • 인덱스(index): 복제되는 로그에서 각 항목(entry)의 위치를 나타내는 순번. 카프카 토픽 파티션의 오프셋과 유사한 개념으로, 로그에 항목이 추가될 때마다 1씩 증가함
    • 로그의 각 항목은 (텀, 인덱스) 쌍으로 식별됨. 리더 선출 시 후보자는 자신의 로그가 얼마나 최신인지(마지막 항목의 텀과 인덱스)를 투표 요청에 포함시키고, 다른 노드는 이를 비교해 자신의 로그보다 최신이거나 같은 후보에게만 투표함 → 이를 통해 여러 노드의 로그가 서로 어긋나지 않고 일관되게 복제되도록 보장함

컨트롤러 노드와 브로커 구성 방식

컨트롤러 노드와 브로커 이미지

  • 동일한 노드에서 브로커 프로세스와 컨트롤러 프로세스를 실행하는 방법
    • 컨트롤러 노드를 위한 별도의 노드나 서버가 필요하지 않으므로 서버를 절약할 수 있다는 장점이 있음
  • 클러스터 내 실행되는 브로커 노드와 컨트롤러 노드를 분리해 별도의 서버에 구성하는 방법
    • 노드의 장애가 발생할 경우 문제가 되는 노드에서 실행되고 있는 프로세스만 종료되어 더욱 높은 안정성을 확보할 수 있으며, 각 노드에 할당된 리소스를 단독으로 사용할 수 있는 장점이 있음

컨트롤러/브로커 분리가 주키퍼 구성과 다른 점

컨트롤러 노드와 브로커 노드를 별도 서버로 분리하면 언뜻 기존 주키퍼 + 카프카 구성과 비슷해 보일 수 있으나, 핵심 차이는 “서버가 몇 대냐”가 아니라 “서로 다른 시스템이냐 아니냐”에 있음

  • 주키퍼 시절에는 주키퍼와 카프카가 완전히 별개의 애플리케이션이었음
    • 서로 다른 API, 다른 클라이언트 프로토콜
    • 다른 보안(인증/ACL) 체계
    • 다른 합의 알고리즘(ZAB)과 다른 데이터 모델(znode)
    • 그래서 배포, 모니터링, 업그레이드, 장애 대응을 각각 따로 해야 했고, 이 때문에 메타데이터 불일치 문제(위 30~31라인)까지 발생함
    • 즉 이때의 “2배 부담”은 서버 대수가 아니라 관리해야 할 시스템 종류가 2개라는 데서 비롯됨
  • KRaft에서는 컨트롤러도 브로커와 동일한 카프카 프로세스이며, 별도 시스템이 아님
    • 같은 바이너리, 같은 프로토콜, 같은 보안/ACL 체계, 같은 모니터링 도구로 관리됨
    • 합의 알고리즘도 카프카 자체 로그 복제와 동일한 계열(Raft)이라 별도 시스템의 데이터 모델을 이해할 필요가 없음
    • 메타데이터가 하나의 로그(단일 소스)로 관리되므로 주키퍼-컨트롤러 간 불일치 문제 자체가 사라짐
    • 컨트롤러 장애 시에도 별도 시스템에서 전체 메타데이터를 다시 읽어오는 지연이 없음 (같은 로그 복제 메커니즘 안에서 팔로워가 이미 대부분 따라잡은 상태이기 때문)
  • 결론적으로 컨트롤러/브로커를 물리적으로 나누는 것은 장애 격리와 리소스 독립을 위한 선택일 뿐이며, 줄어드는 것은 서버 개수가 아니라 이종 시스템을 따로 배우고 운영해야 하는 부담
This post is licensed under CC BY 4.0 by the author.

Trending Tags