Hyesung Oh

Kafka 내부 동작 원리 이해하기 (1) Replication 본문

Data Engineering/Apache Kafka

Kafka 내부 동작 원리 이해하기 (1) Replication

혜성 Hyesung 2022. 5. 31. 08:57
반응형

시작하기에 앞서 이번 시리즈는 고승덕님의 실전 카프카 개발부터 운영까지를 스터디하며 이해한 내용을 내 나름대로 정리한 포스팅임을 밝힌다.
양질의 저서를 출판해주신 고승덕님께 깊이 감사의 말씀을 전합니다.


EDA(Event Driven Architecture)에서 Kafka는 SPOF(Single Point of Failure)가 될 수 있다. 따라서 Kafka는 물론이고 Hadoop 등의 분산 시스템에서는 애플리케이션의 HA(High Availability)를 위해 내부적으로 replication 동작을 수행하게 된다. Kafka에서 Topic 생성시 replication factor 지정은 필수이다. 그 외에도 많은 기업에선 클러스터간 미러링, 서버의 지리적 분산 등의 기본적인 조치를 취하게 된다. 

분산시스템에서의 CAP Theorem (개인적으로는 PACELC Theorem이 더 명확한 정리라는 생각이 든다. 자세한 내용은 여기를 참고)에 따르면, 장애상황(Network partitioned state)을 배제한 정상 상황을 가정할 때 가용성(Availabilty)은 성능(Latency)과 trade off 관계에 있다. 이를 보완하기 위해 카프카는 최대한 성능에 영향을 주지 않도록 replication 동작이 설계되었다고 한다.

리더와 팔로워
Replication은 토픽 단위로 동작하는 것이 아닌 파티션 단위로 동작한다는 것을 꼭 기억해야한다. 각 파티션의 복제본들은 서로 다른 브로커에 위치한다. 파티션의 원본을 리더, 복제본을 팔로워라고 부르며 각 파티션 마다 존재한다. 필자는 Kafka replication 개념을 처음 공부할 때, 3개의 파티션이 있을 때 (0, 1, 2) 0번 파티션의 팔로워가 1, 2번 파티션이라고 헷갈리곤 했는데, 0, 1, 2는 서로 독립적이며 모두 각자의 팔로워를 가진다는 것을 꼭 기억하자.

만약 N개의 노드, N개의 replication이 있다고 가정하면, N-1까지의 브로커 장애에도 tolerant 하다. 이를 가능케 하는 것이 리더와 팔로워이다. 

Kafka에서는 동일한 복제본들을 리더와 팔로워로 구분함으로서, 서로 다른 역할을 분담시킨다. 
복제본 중 하나가 리더로 선출되며, 모든 읽기와 쓰기는 리더를 통해서만 가능하다. 다시말해, 프로듀서와 컨슈머는 파티션 리더로 부터만 읽기와 쓰기를 요청하는 것이다.

출처: https://jack-vanlightly.com/blog/2018/9/2/rabbitmq-vs-kafka-part-6-fault-tolerance-and-high-availability-with-kafka

ISR
리더와 팔뤄워는 ISR(In SyncReplica)라는 논리적 그룹으로 묶여 있다. 이러한 이유는 해당 그룹 안에 속한 팔로워들만이 리더 부재 시 새로운 리더의 자격을 가질 수 있도록 하기 위함이다. ISR 내 리더와 팔로워는 지속적으로 데이터 동기화를 위해 통신을 하게 되고, 이러한 과정에서 네트워크 오류, 브로커 장애 등의 이유로 리플리케이션 받지 못한 팔로워들을 트래킹하며 뒤처진 팔로워를 ISR에서 추방하게 된다. 이러한 모든 역할은 리더가 하게 된다.

참고로 ISR 리스트에 대한 정보는 주키퍼에 저장되는데, 올해 초 릴리즈된 Kafka without Zookeeper에서는 어떤 식으로 해당 정보를 관리하는지 궁금하다. 

high water mark
ISR내 모든 팔로워의 복제가 완료되면, 리더는 내부적으로 커밋되었다는 표시를 남기기 위해 하이워터마크(high water mark)라는 것을 남기게 된다. 

출처: https://i-beam.org/2017/12/20/kafka-replication-deep-dive/

Kafka에서는 커밋되지 않은 메세지를 컨슈머가 읽을 수 없게 함으로서 일관성을 유지한다. 참고로 해당 커밋에 대한 정보는 /data/kafka-logs/replication-offset-checkpoint 경로에서 확인 할 수 있다.

성능과 안정성 두 마리 토끼를 잡은 비밀
Kafka는 리더와 팔로워 사이의 replication 완료 여부에 대한 ack 통신을 제거하였다. 초반부에서, Kafka는 replication을 통한 높은 가용성과 안정성을 유지하면서도 성능을 유지할 수 있게 설계되었다고 언급하였는데, 비밀은 바로 여기에 있다. 전통적인 메세징 큐 시스템인 래빗MQ의 트랜잭션 모드에서는 모든 미러(팔로워)가 메세지를 복제하였는지 확인하는 ACK를 리더에게 응답하여야 하지만, Kafka는 이 과정을 제거함으로서 모든 읽기, 쓰기 통신을 담당하는 파티션 리더의 부하를 덜 수 있게 된 것이다.

ACK를 응답받지 않으면 리더는 팔로워들이 제대로 복제하였는지 알 수 없기 때문에 신뢰도가 떨어질 것만 같다. 예시를 통해 조금 더 자세히 애해해보자.
1. 리더는 프로듀서로 부터 message1 (offset0)을 전달받았다. 
2. 팔로워들은 오프셋0에 대한 복제를 마친 상황이다.
3. 다음으로 리더는 message2 (offset1)을 받았다.
4. 팔로워들은 다음 오프셋1에 대한 요청(fetch)를 리더에게 보낸다.
5. 리더는 팔로워들로부터 요청받은 offset 번호를 통해 offset0 복제가 완료되었음을 인지하고, 오프셋 0에 대해 커밋 표시를 한 후 하이워터마크 값을 1 증가시킨다.  *오프셋0에 대한 복제가 완료되지 않았다면 팔로워들은 다시 오프셋0에 대한 요청을 리더에게 보내기 때문에 리더는 하이워터마크 값을 증가시키지 않을 것이다.
6. 오프셋1에 대한 요청을 받은 리더는 이에 대한 응답으로 오프셋0이 커밋되었다는 사실을 팔로워들에게 응답한다. 팔로워들은 이 값으로 리더와 동일하게 커밋을 표시할 수 있게 된다. 

위 과정을 반복하며 동일한 파티션 내에서 리더와 팔로워 간 메세지의 최신 상태를 유지할 수 있게 된다.

이제 리더와 팔로워 사이의 ACK 통신을 제거하면서도 복제 신뢰도를 유지할 수 있는 원리가 명확히 이해하게 되었다.

Pull 방식의 복제 동작
그런데 성능과 신뢰성 두 마리 토끼를 모두 잡을 수 있는 데에는 한 가지 더 비밀이 있다. 
리더와 팔로워들의 리플리케이션 동작 방식이 리더 -> 팔로워 Push 방식이 아닌 리더 <- 팔로워 Pull 방식으로 동작한다는 것이다. 이를 통해 리더의 부하를 추가적으로 줄일 수 있었다고 한다. 알면 알수록 Kafka 개발자들에 존경심을 가지게 된다.

LeaderEpoch
Kafka 장애 상황시 파티션들을 복구 할 때 메세지의 일관성을 유지하기 위해 리더에포크(LeaderEpoch)라는 동작을 수행하게 된다. 리더에포크는 컨트롤러에 의해 관리되는 32비트의 숫자로 표현된다. 이 정도로만 정리하고 리더에포크의 자세한 동작 방식에 대해서는 책에 아주 잘 설명이 되어있으니 꼭 읽어보길 바란다. 

Controller
마지막으로 리더와 팔로워를 선출하는 컨트롤러에 대해 정리하고 이 글을 마치겠다.
카프카 클러스터 중 하나의 브로커가 컨트롤러 역할을 하게 되며, 파티션의 ISR 리스트 중에서 리더를 선출한다. ISR 리스트는 주키퍼에 저장된다. 현재, 주키퍼를 사용하는 카프카의 경우 새로운 리더가 선출 될 시, 해당 정보를 주키퍼에 기록하고 변경된 정보를 모든 브로커에 전달한다. 

파티션 리더가 없는 상황은, 모든 쓰기 읽기 요청을 할 수 없게 되는 상황이므로 가능한 빠르게 리더 재선출 과정이 완료되어야 할 것이다. 프로듀서와 컨슈머는 지정된 재시도 횟수만큼 재시도 후에도 요청이 불가하면 실패를 하게 되기 때문이다.

리더 재선출 상황 예시를 통해 좀 더 자세히 이해해보자.
1. 파티션 0번의 리더가 있는 브로커 1번의 장애 상황
2. 1번 브로커와 단절 이후 0번 파티션의 ISR에 변화가 생겼음을 감지
3. 컨트롤러는 주키퍼 워치를 통해 0번 파티션의 변화를 감지하고, ISR 리스트 중 2번을 새로운 리더로 선출. 
4. 컨트롤러는 0번 파티션의 새로운 리더가 2라는 사실을 주키퍼에 업데이트
5. 해당 사실을 활성화된 모든 브로커에 전달.

카프카 버전 1.1.0 부터는 리더 선출 작업 속도가 빨라졌다고 한다. 불필요한 로깅 제거 및 비동기 API 적용을 통해 이루어낸 성과라고 한다. 

이 외에 실패나 장애 상황이 아닌, 관리자에 의한 의도적인 브로커 종료 상황일 때 리더 재선출 과정에 대한 내용은 책에서 확인 바란다.

 

반응형
Comments