개발 일지/Kafka

[kafka] 컨슈머 파티션 할당 전략

배발자 2023. 10. 31.
반응형

개요

이전 포스팅에서는 컨슈머 코디네이터를 통해 리밸런싱 동작이 발생하는 것을 확인할 수 있었다. 하지만, 리밸런싱은 무거운 작업이기 때문에 자주 진행하는 것은 문제가 된다고 강조하였다. 이를 해결하기 위해 스태틱 멤버십 기능을 적용하여 일정 시간동안 리밸런싱을 동작하지 않도록 설정할 수 있는 것을 확인하였다. 

 

이전에 학습했던 내용 중 프로듀서와 관련된 포스팅에서 파티셔너라는 기능을 살펴보면서 특정 파티션으로 전송하기 전 배치 작업을 진행하는 내용을 살펴본 적이 있다. 이와 유사하게 컨슈머의 동작에서도 대상 토픽의 어느 파티션으로부터 레코드(메시지)를 받아올지를 결정하는 메커니즘이 존재하는데 어떠한 매칭 전략이 있는지 이번 포스팅을 통해 알아보려고 한다. 즉, 이번 포스팅에서는 컨슈머 파티션 전략을 정리할 것이다.

 

 [파티션 할당 전략 4가지]

레인지
라운드 로빈
스티키
협력적 스티키

 

 

레인지 파티션 할당 전략

 

이 방법은 간단하다. 파티션과 컨슈머를 순서대로 정렬한 후 각 컨슈머가 몇 개의 파티션을 할당해야 하는지 전체 파티션 수를 컨슈머 수로 나누면 된다. 위의 그림에서 각 토픽에 존재하는 파티션수는 총 3개 컨슈머 수는 2개이다. 3/2는 1이다. 즉, 컨슈머는 최소 하나의 파티션을 가져가며 나머지가 존재하기 때문에 컨슈머 1에 남은 파티션을 추가 할당하는 방식이다. 하지만, 위의 그림처럼 컨슈머1은 4개의 파티션을 할당받고 컨슈머2는 2개의 파티션만 할당받는 것을 확인할 수 있다. 즉, 레인지 파티션 전략은 균등하지 못하게 할당받게 된다. 

 

라운드 로빈 파티션 할당 전략

 

라운드 로빈 전략은 CPU 스케줄링이나 로드밸런서의 라우팅 방식 등 흔한 방식 중 하나이다. 하나씩 번갈아가면서 매핑시키는 방법인데, 토픽1에서 파티션0은 컨슈머1로 매핑하고 파티션1은 컨슈 컨슈머2로 매핑한다. 그리고 파티션3은 다시 컨슈머1로 매핑하는 방식으로 균등하게 매핑하는 전략이다. 간단한 전략이지만 다음 예제를 통해서 발생하는 문제점을 확인해보자. 

 

  1. 컨슈머2가 컨슈머 그룹에서 탈퇴
  2. 리밸런싱
  3. 모든 파티션을 순서대로 배치
  4. 모든 컨슈머를 순서대로 배치
  5. 라운드 로빈 전략에 맞게 균등하게 하나씩 매핑

 

그림의 왼쪽을 보면 컨슈머2에 문제가 발생해서 해당 컨슈머가 그룹을 탈퇴했다고 가정해보자. 그러면 리밸런싱이 발생할텐데 이때 라운드로빈 파티션 전략을 활용했을 때 동작흐름은 다음과 같다.   

 

토픽2의 파티션0과 파티션1은 각각 컨슈머3과 컨슈머1로 매핑되어있어서 장애가 발생한 컨슈머2와는 관련이 없다. 하지만 컨슈머2의 탈퇴로 리밸런싱이 발생하게 되면서 파티션0은 컨슈머 1, 파티션1은 컨슈머3으로 관계가 바꼈다. 즉, 특정 컨슈머의 탈퇴가 발생하더라도 영향이 없던 매핑 관계에서도 관계가 끊기고 다른 파티션을 할당받을 수 있다.  

 

이제 이와 비슷한 예제로 스티키 파티션 할당 전략에 대해서 살펴보자. 

 

스티키 파티션 할당 전략

 

라운드 로빈 할당 전략에서 예시로 들었던 상황을 다시 한 번 생각해보자. 컨슈머2가 문제가 발생해서 해당 컨슈머가 그룹을 탈퇴하면 파티션과 컨슈머의 매핑 관계를 모두 끊어버리고 다시 파티션 할당을 진행하였다. 하지만 스티키 파티션 할당 전략에서는 컨슈머1과 컨슈머3에 이미 할당된 파티션은 유지되어 있다. 이렇게 이상적으로 동작하는 이유는 다음과 같은 규칙에 따라서 재할당 동작을 수행하기 때문이다. 

 

  컨슈머들의 최대 할당된 파티션의 수의 차이는 1
  기존에 존재하는 파티션 할당은 최대한 유지함
  재할당 동작 시 유효하지 않은 모든 파티션 할당은 제거함
  할당되지 않은 파티션들은 균형을 맞추는 방법으로 컨슈머들에 할당

 

협력적 스티키 파티션 할당 전략 

해당 전략은 스티키 전략과 동일한 방식이지만 컨슈머 그룹 내부의 리밸런싱 동작이 한층 더 고도화된 전략이다. 이전 전략에서는 EAGER라는 리밸런스 프로토콜을 사용하는데 해당 프로토콜은 리밸런싱 동작 시 컨슈머에 할당된 모든 파티션을 항상 취소하게 된다. 그 이유는 그룹 내에서는 둘 이상의 컨슈머가 동일한 파티션을 소유할 수 없기 때문에  특정 파티션을 넘겨줄 때는 어떤 컨슈머도 소유권을 갖고 있어서는 안된다. 두번째는 새로운 파티션 할당 작업이 동시에 이뤄져야하므로 단순하게 구현하기 위해서이다. 이렇게 리밸런싱에서 모든 파티션 할당을 취소하는 동작은 리소스를 많이 사용하는 컨슈머 그룹에서 큰 문제가 된다. 즉, 컨슈머들의 다운 타임으로 오버헤드가 크다는 것을 의미한다. 

 

  1. BAE02 컨슈머가 다운됨을 감지
  2. 컨슈머에게 할당된 모든 파티션을 제거
  3. 컨슈머에게 할당된 파티션이 없으므로 다운타임 발생
  4. 프로듀서는 해당 토픽을 타깃으로 지속적으로 메시지 전송, LAG 증가
  5. 재시작되면서 파티션이 컨슈머들에게 재할당. 
  6. 다운타임 종료

 

*LAG : 지연 

 

협력적 스티키 파티션 할당 전략 이전에는 위의 그림과 같이 다운 타임이 발생하게 된다. 이전 포스팅에서 스태틱 멤버십의 기능을 통해 불필요한 리밸런싱 동작이 일어나지 않게 할 수는 있지만, 불가피하게 리밸런싱이 일어나면 여전히 컨슈머의 다운타임이 발생한다. 이를 개선하고자 카프카 2.3 버전부터 카프카 커넥트에 새로운 협력적 스티키 파티션 할당 전략이 적용됐고, 2.4 버전부터 컨슈머 클라이언트에도 적용되어있다. 협력적 스티키는 내부 리밸런싱 프로토콜인 EAGER가 아닌 COOPERATIVE 프로토콜을 적용하기  시작했고, 이 프로토콜은 리밸런싱이 동작하기 전의 컨슈머 상태를 유지한다. 즉, 되도록 동작 중인 컨슈머들에게 영향을 주지 않는 상태에서 몇 차례에 걸쳐 리밸런싱이 이뤄진다. 다음 그림은 협력적 스티키 파티션 할당 전략을 도입했을 때 흐름이다. 

 

  1. 컨슈머 그룹에 bae01이 합류하면서 리밸런싱이 트리거된다. 
  2. 컨슈머 그룹 내 컨슈머들은 그룹 합류 요청과 자신들의 컨슘하는 토픽의 파티션 정보(소유권)를 그룹 코디네이터로 전송(감지단계)
  3. 그룹 코디네티어는 해당 정보를 조합해 컨슈머 그룹의 리더에게 전송(감지단계)
  4. 컨슈머 그룹의 리더는 현재 컨슈머들이 소유한 파티션 정보를 활용해 제외해야 할 파티션 정보를 담은 새로운 파티션 할당 정보를 컨슈머 그룹 멤버들에게 전달한다. (첫번째 리밸런싱 단계)
  5. 새로운 파티션 할당 정보를 받은 컨슈머 그룹 멤버들은 현재의 파티션 할당 전략과 차이를 비교해보고 필요 없는 파티션을 골라 제외한다. 이전의 파티션 할당 정보와 새로운 파티션 할당 정보가 동일한 파티션들에 대해서는 어떤 작업도 수행할 필요가 없다. (첫 번째 리밸런싱 단계) 
  6. 제외된 파티션 할당을 위해 컨슈머들은 다시 합류 요청을 한다. 여기서 두 번째 리밸런싱이 트리거된다. (두 번째 리밸런싱 단계) 
  7. 컨슈머 그룹의 리더는 제외된 파티션을 적절한 컨슈머에게 할당한다. (두 번째 리밸런싱 단계) 

 

현재 bae03, bae02 컨슈머는 각각 파티션 1, 2를 할당받고 있고 추가적으로 bae02는 파티션 3을 할당받고 있다. 여기서 bae01이라는 새로운 컨슈머가 합류할 때 리밸런싱 과정을 살펴보면, 협력적 스티키 파티션 할당 전략에서는 리밸런싱 동작이 한 번에 완료되지 않았지만 현재 동작하고 있는 컨슈머에게는 아무런 영향을 주지 않는다. 

 

협력적 스티키 전략은 아파치 카프카 2.5 버전에서 안정화됐고, 최신 버전의 카프카를 사용하는 컨슈머들은 리밸런싱으로 인한 다운타임을 최소화하며 컨슈머들은 균형 잡힌 분산을 하게 되었다. 

 

정확히 한 번 컨슈머 동작 

이전 포스팅에서 정확히 한 번 메시지 전송 동작 과정을 자세히 살펴본 적이 있다. 프로듀서의 경우 트랜잭션 코디네이터와 통신하면서 해당 트랜잭션이 정확하게 처리되는 것을 보장했지만, 컨슈머는 트랜잭션 코디네이터와 통신하는 부분이 없으므로 정확하게 메시지를 한 번 가져오는지는 보장할 수 없다. 즉, 트랜잭션 컨슈머라고 해서 정확히 한 번만 가져오는 것이 아니다. 또한, 컨슈머가 정확하게 한 번 메시지를 가져왔다고 가정하더라도, 컨슈머가 가져온 메시지를 다른 애플리케이션에 저장하는 과정에서 중복 처리되는 경우가 있다. 하지만 컨슈머는 메시지들이 중복 저장되는 결과를 알 수 없으므로 정확히 한 번 저장을 보장할 수 없다는 것이다. 

 

정확히 한 번 처리가 가능해지려면 '컨슘-메시지 처리-프로듀싱' 동작이 모두 하나의 트랜잭션으로 처리돼야 한다. 즉, 하나의 트랜잭션에서 원자성을 지키도록 묶어버리는 방식이다. 만약 이 처리 과정에서 트랜잭션 실패가 발생하면, 해당 컨슈머 그룹의 커밋 오프셋이 증가하지 않게 함으로써 실패한 트랜잭션을 다시 시작해야 한다. 

 

마치며

이번 포스팅에서는 다양한 파티션 전략이 있다는 것을 알게 되었고, 카프카를 깊게 공부하면서 느끼는거지만 개발자들은 기존 시스템을 개선하기 위해 많은 노력을 하는거 같다. 이러한 노력으로 기존 방식의 EAGER 프로토콜에서 COOPERATIVE 프로토콜 방식의 성능 비교 결과, 후자의 방식이 더 빠른 시간 안에 리밸런싱을 완료할 수 있다고 한다. 카프카의 버전이 올라갈수록 얼마나 더 개선될 것인지 궁금하다. 

 

* 참고서적 : 실전 카프카 개발부터 운영까지 - 고승범

반응형

댓글