이번 글에서는 아파치 카프카(Apache Kafka)의 새로운 협의 프로토콜인 KRaft에 대해 다룰 예정입니다. 카프카를 사용하면서 초기에는 최신 버전의 릴리스를 추구했지만, 카프카가 점점 데이터 파이프라인의 중심이 되면서 보다 보수적으로 접근하게 되었습니다. 지금까지 KRaft에 대해 크게 고려하지 않았으나 이제는 KRaft에 대한 준비와 주키퍼 모드로 운영 중인 카프카를 마이그레이션 하는 방법 등에 대해서도 심도 있는 검토가 필요한 생각이 들었습니다. 이번에 새롭게 KRaft에 대한 자료 조사도 하고, 마이그레이션 테스트도 진행하면서 경험한 내용들을 간략히 공유하고자 합니다. 전체 글의 내용은 KRaft의 등장 배경과 중요성, 마이그레이션 전략, 릴리스 노트와 향후 계획 등을 설명하며, 총 2편으로 나누어 작성하겠습니다. 먼저 KRaft의 등장 배경과 중요성에 대해 살펴보겠습니다.
KRaft (Kafka Raft)는 아파치 카프카(Apache Kafka)의 분산 시스템을 관리하기 위해 도입된 새로운 메커니즘입니다. 이전까지 카프카는 아파치 주키퍼(Apache ZooKeeper)를 사용하여 클러스터 메타데이터의 관리와 조정을 담당했습니다. 그러나 주키퍼의 의존성은 카프카의 확장성과 유지보수에 여러 제약을 가져왔고, 이를 해결하기 위해 카프카 자체 내에서 분산 시스템의 상태를 관리하는 방식을 도입하기로 결정하였습니다. 주키퍼 사용 시 이슈가 되는 부분들에 대해 조금 자세히 살펴보겠습니다.
성능적인 부분
주키퍼를 사용하면서 여러 가지 불편, 제한적인 상황이 있었겠지만 카프카에서 새로운 방식의 메커니즘 도입을 결정하는데, 아마도 성능적인 이슈가 가장 큰 영향을 끼치지 않았나 싶습니다. 브로커는 모든 모든 토픽과 파티션에 대한 메타데이터를 주키퍼에서 읽어야 하며, 메타데이터의 업데이터는 주키퍼에서 동기방식으로 일어나고, 브로커에는 비동기방식으로 전달되었습니다. 이 과정에서 메타데이터의 불일치가 발생할 수도 있으며, 컨트롤러 재시작 시 모든 메타데이터를 주키퍼로부터 읽어야 하는 것도 부담이었습니다. 특히 토픽과 파티션이 많은 대규모 카프카 클러스터에서는 오랜 시간이 걸리는 등 병목현상이 발생할 수 있습니다.
관리적인 부분
주키퍼와 카프카는 완전히 서로 다른 애플리케이션으로, 서로 다른 구성 파일, 환경, 서비스 데몬을 가지고 있습니다. 결국 관리자는 동시에 서로 다른 애플리케이션을 운영해야 합니다. 동시에 두 가지 애플리케이션을 운영한다는 것은 관리자에게 큰 부담이 됩니다. 예를 들어, 주키퍼의 릴리스 노트 확인, 버전 업그레이드, 구성 파일 변경과 동시에 카프카의 릴리스 노트 확인, 버전 업그레이드, 구성 파일 변경을 해야 합니다.
모니터링 등
모든 서비스는 모니터링이 필수이며, 주키퍼와 카프카 둘 다 모니터링을 해야 합니다. 두 애플리케이션은 서로 다른 애플리케이션이므로, 모니터링을 적용하는 방법과 각 애플리케이션에서 보여주는 주요 메트릭도 다릅니다. 또한 모니터링에 필요한 필수 메트릭을 이해하고 모니터링하는 방법까지 완전히 다릅니다.
그 외에도 각 애플리케이션에서 빈번하게 발생하는 이슈 또는 장애 상황에 개별적으로 대처해야 하며, 두 애플리케이션 간 통신 이슈라도 발생한다면, 매우 곤혹스러울 것입니다.
KRaft의 주요 목적은 카프카의 구조를 단순화하고 확장성을 향상시키기 위함입니다. 앞에서 언급한 여러 불편한 부분들이 주키퍼를 제거함으로써 설치, 구성, 유지보수가 단순화될 뿐만 아니라 카프카의 성능과 안정성 역시 크게 향상됩니다. KRaft를 카프카와 결합하여 운영 복잡성을 줄이고, 데이터 플랫폼의 충추 역할을 하는 카프카의 전반적인 신뢰성과 관리 용이성을 개선하는데 기여할 수 있습니다. 이러한 변화는 카프카를 더욱 강력하고 유연한 시스템으로 만들어 대규모 데이터 처리 환경에서의 효율성을 높일 수 있습니다.
이제부터는 카프카 클러스터를 주키퍼 모드(주키퍼와 병행해서 사용하는 모드)와 KRaft 모드(KRaft가 적용된 모드)라고 구분하고, 각 모드에 대한 차이점을 살펴보겠습니다. 먼저 주키퍼 모드에 대해 살펴보겠습니다.
주키퍼 모드
주키퍼 모드는 주키퍼 앙상블(Ensemble)과 카프카 클러스터가 존재하며, 카프카 클러스터 중 하나의 브로커가 컨트롤러 역할을 하게 됩니다. 컨트롤러는 파티션의 리더를 선출하는 역할을 하며, 리더 선출 정보를 브로커에게 전파하고 주키퍼에 리더 정보를 기록하는 역할을 합니다. 컨트롤러의 선출 작업은 주키퍼를 통해 이루어지는데, 주키퍼의 임시노드를 통해 이루어집니다. 임시노드에 가장 먼저 연결에 성공한 브로커가 컨트롤러가 되고, 다른 브로커들은 해당 임시노드에 이미 컨트롤러가 있다는 사실을 통해 카프카 클러스터 내 컨트롤러가 있다는 것을 인식하게 됩니다. 이를 통해 한 번에 하나의 컨트롤러만 클러스터에 있도록 보장할 수 있습니다.
KRaft 모드
그림에서 보는 바와 같이 KRaft 모드에서는 주키퍼가 사라진 것을 알 수 있습니다. KRaft 모드는 주키퍼와의 의존성을 제거하고, 카프카 단일 애플리케이션 내에서 메타데이터 관리 기능을 수행하는 독립적인 구조가 되는 것입니다. 주키퍼 모드에서 1개였던 컨트롤러가 3개로 늘어나고, 이들 중 하나의 컨트롤러가 액티브(그림에서 노란색 컨트롤러) 컨트롤러이면서 리더 역할을 담당합니다. 리더 역할을 하는 컨트롤러가 write 하는 역할도 하게 됩니다. 또한 주키퍼 노드에서는 메타 데이터 관리를 주키퍼가 했다면, 이제는 카프카 내부의 별도 토픽을 이용하여 메타 데이터를 관리합니다. 액티브인 컨트롤러가 장애 또는 종료되는 경우, 내부에서는 새로운 합의 알고리즘을 통해 새로운 리더를 선출하게 됩니다. 리더를 선출하는 과정을 간략히 설명드리자면, 후보자들은 적합한 리더를 투표하게 되고 후보자 중 충분한 표를 얻으면, 해당 컨트롤러가 새로운 리더가 됩니다.
KRaft의 주요 성능 개선 중 하나는 파티션 리더 선출의 최적화입니다. 앞에서 잠깐 언급했지만, 컨트롤러의 주요 역할은 파티션의 리더를 선출하는 것입니다. 소수의 파티션에 대한 리더 선출 작업은 카프카 또는 카프카를 사용하는 클라이언트들에게 별다른 영향이 없겠으나, 대량의 파티션에 대한 리더 선출 작업은 다소 시간이 소요되며, 이러한 시간은 대량의 데이터 파이프라인의 역할을 하는 카프카와 클라이언트들에게 매우 크리티컬한 요소일 수 있습니다. 따라서 이러한 지연 시간을 방지하고자 주키퍼 모드의 경우 카프카 클러스터 전체의 파티션 리미트는 약 200,000개 정도였으나, 리더 선출 과정을 개선한 KRaft 모드에서는 훨씬 더 많은 파티션 생성이 가능합니다.
컨플루언트에서 공개한 KRaft 모드와 주키퍼 모드 간의 속도를 비교한 그림을 살펴보면, 복구 소요시간에서 엄청난 차이를 나타내고 있음을 알 수 있습니다. 이렇게 속도차이가 나는 이유는 KRaft모드에서의 컨트롤러는 메모리 내에 메타데이터 캐시를 유지하고 있으며, 주키퍼와의 의존성도 제거해 내부적으로 메타데이터의 동기화와 관리과정을 효율적으로 개선했기 때문입니다. 또한 액티브 컨트롤러 장애 시 최신 메타데이터가 메모리에 유지되고 있으므로, 메타데이터 복제하는 시간도 줄어들어 보다 효율적인 컨트롤러 리더 선출 작업이 일어납니다.
지금까지 KRaft의 등장과 주키퍼를 사용하면서의 몇 가지 단점들에 대해 살펴보고, 주키퍼 모드와 KRaft 모드의 주요 차이점을 살펴봤습니다. 이어지는 다음 글에서는 KRaft 모드를 구성하는 방법, 마이그레이션 전략, 릴리스 노트와 예정된 향후 계획등에 대해 살펴보겠습니다. 긴 글 읽어주셔서 감사합니다.