카프카 컨슈머 그룹 리밸런싱 (Kafka Consumer Group Rebalancing)

Jim
여기어때 기술블로그
8 min readDec 22, 2022

--

안녕하세요. 결제정산개발팀 Jim 입니다.

최근 카프카 컨슈머 그룹 리밸런싱에 대한 고민이 필요하여, 내용을 간단하게 정리해봤습니다.

카프카 컨슈머 그룹 리밸런싱이란?

각각의 카프카 컨슈머는 토픽의 각 파티션에 대해 메세지를 처리합니다. 그런데 만약, 특정 컨슈머에 문제가 생겨 더 이상 메세지를 처리할 수 없다면, 파티션의 소유권을 다른 컨슈머에게 이관해야 합니다. 이러한 조정 작업을 리밸런싱이라고 합니다.

카프카 컨슈머 그룹 리밸런싱은 언제 일어날까?

  1. 컨슈머의 생성 / 삭제
    컨슈머가 생성/삭제 되는 가장 일반적인 상황은 배포 할 때 입니다. 배포 과정에서 기존 어플리케이션이 종료되고, 새 어플리케이션이 다시 동작하게 됩니다. 결국 이때 리밸런싱이 최소 두 번 일어나게 됩니다. 기존 컨슈머가 삭제되고, 새로운 컨슈머가 생성되기 때문입니다.
  2. 시간안에 Poll 요청 실패
    컨슈머는 “max.poll.records” 설정의 개수만큼 메세지를 처리한 뒤 Poll 요청을 보내게 됩니다. 하지만, 메세지들의 처리 시간이 늦어져서 “max.poll.interval.ms” 설정 시간을 넘기게 된다면 컨슈머에 문제가 있다고 판단하여 리밸런싱이 일어납니다.
  3. 컨슈머 문제 발생
    컨슈머가 일정 시간 동안 하트비트를 보내지 못하면, 세션이 종료되고 컨슈머 그룹에서 제외됩니다. 이때 리밸런싱이 진행됩니다.

카프카 리밸런싱의 리스크는?

  1. 컨슈머 처리 중단 (파티션 읽기 작업 중단)
    리밸런싱이 완료되기 전에는 컨슈머가 동작하지 않습니다.
  2. 메세지 중복 컨슈밍
    예시를 들어보겠습니다.
    만약, 컨슈머에 별다른 설정을 하지 않았다면 아래처럼 동작하게 됩니다.
- auto commit 사용 (enable.auto.commit=true)
- 5초 주기로 커밋 가능(auto.commit.interval.ms=5000)
- max.poll.records 개수의 메세지를 모두 처리한 뒤,
poll 요청을 보내고 커밋이 가능하다면 메세지 offset 변경 사항을 커밋

일반적인 상황에서는 아무 문제가 없습니다.

1) 메세지 lag이 10개 쌓임
2) 메세지 10개 처리(max.poll.records=500)
3) poll 요청
4) offset 커밋!!

하지만 만약 리밸런싱이 발생한다면 문제가 발생할 수 있습니다.

1) 메세지 lag이 500개 쌓임
2) 메세지 500개 처리(max.poll.records=500)
3) 메세지 500개중 400개만 처리 완료
4) max.poll.interval.ms 시간이 지남(5분)
5) 리밸런싱 발생
6) offset 커밋이 되지 않음!!
7) 다른 컨슈머에서 마지막으로 커밋된 메세지부터 처리(중복 메세지 처리)

리밸런싱 리스크를 해결하기 위해서는?

A) max.poll.records 조정하기

max.poll.records를 줄이면 아래 장점이 있습니다. (기본값 500)

  1. 리밸런싱 시간 단축
    리밸런싱 과정에서 컨슈머들은 Poll요청을 통해 조인을 하게 됩니다. 그리고 모든 컨슈머에서 조인을 하게 되면, 리밸런싱이 완료됩니다. max.poll.records 값이 작을수록 Poll 요청을 빠르게 보낼 수 있습니다. 처리해야 할 데이터가 적기 때문입니다. 즉, max.poll.records 값을 작게 할수록 리밸런싱 작업이 빠르게 완료됩니다.
  2. Poll 지연에 의한 리밸런싱 발생 가능성 감소
    일정 시간(max.poll.interval.ms)안에 Poll 요청을 보내지 않으면 리밸런싱이 일어납니다. 하지만 max.poll.records 값이 작을수록 Poll 요청을 빠르게 보내게 되어 리밸런싱이 발생할 가능성이 줄어듭니다.
  3. 메세지 중복 컨슈밍 가능성 감소
    만약 Poll 요청에 의해 커밋 되는 전략(Ack Mode)을 사용 중이라면 Poll 요청의 빈도가 늘게 되어, 중복 컨슈밍의 가능성도 감소하게 됩니다. 관련 전략으로는 BATCH(기본 전략), MANUAL이 있습니다.

# max.poll.records를 줄이면 혹시 성능 이슈가 있을까?

max.poll.records : The maximum number of records returned in a single call to poll(). Note, that max.poll.records does not impact the underlying fetching behavior. The consumer will cache the records from each fetch request and returns them incrementally from each poll.

참고 : https://kafka.apache.org/documentation/

관련 다큐먼트를 보면, max.poll.records는 패치 동작에 영향을 미치지 않는다고 합니다.

단순 로그만 찍는 컨슈머 로직에 대해 메세지 1000개 처리시간 테스트를 진행해 보겠습니다. 아래는 테스트의 결과입니다.

저의 로컬 PC 기준 아래와 같이 소요됐습니다.

max.poll.records=1일 때 메세지 하나당 처리시간은 약 25ms
max.poll.records=500일 때 메세지 하나당 처리시간은 약 0.1ms

실제 서비스의 컨슈머 로직에서는 IO 작업이 포함되어 있어 기본적으로 테스트한 컨슈머보다 더 오랜 시간이 필요할 것입니다. 만약 관리하는 서비스의 컨슈머 로직이 기존에 1000ms가 걸렸다면 max.poll.records=1로 설정하는 것이 더 장점이 많을 수 있습니다. 하지만 기존 컨슈머 로직이 10ms가 걸렸다면 max.poll.records=100으로 하는 것이 더 나을 수 있습니다.

그러므로, max.poll.records 값에 따른 트레이드오프를 고려해서 서비스에 맞게 조정이 필요합니다.

하지만 대부분의 서비스에서는 max.poll.records=1로 해도 이슈가 없을 거라 생각됩니다.

B) 수동 커밋 사용하기

중복 컨슈밍을 최대한 막기 위해, 서비스에 적절한 전략을 사용해야 합니다. 기본 설정은 Poll 요청 때마다 커밋을 하는 AckMode.BATCH입니다. 만약, max.poll.records=1로 설정하고, 기본 전략인 BATCH를 사용한다면 단일 메세지 단위로 커밋이 될 것이라고 기대할 수 있습니다.

C) 중복 컨슈밍 방어 로직 작성하기

카프카 설정만으로 중복 컨슈밍에 대한 리스크를 완벽하게 관리하는 것은 쉽지 않은 일이라고 생각됩니다. 중복 컨슈밍이 크리티컬한 서비스라면 어플리케이션 레벨에서 방어 로직을 반드시 작성하여야 합니다.

D) 카프카 컨슈머 병렬 처리

메세지 Lag이 발생하면, 다음 Poll 요청이 늦어질 가능성이 높아지고, 리밸런싱이 발생할 가능성도 높아지게 됩니다. 따라서 컨슈머 처리 능력을 올리기 위한 몇 가지 방법을 고민해 볼 수 있습니다.

1) 컨슈머 로직을 스레드 풀로 처리하기
@Async, CompletableFuture 등을 이용하여, 컨슈머 로직을 비동기로 처리하면, 메세지를 빠르게 컨슈밍 할 수 있습니다. 어플리케이션의 스레드 풀에 대한 관리 포인트가 생기겠지만, 검토해 볼 수 있는 방법이라고 생각됩니다.

2) ConcurrentKafkaListener 사용 검토하기
서비스 중인 컨슈머의 개수가 파티션의 개수보다 적을 때 사용할 수 있는 방법입니다.

@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> concurrentKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(somethingConsumerFactory());
factory.setConcurrency(3); // 최대 파티션의 개수
return factory;
}

예를 들어, 파티션 개수가 3개고, 배포된 어플리케이션이 2개(컨슈머 2개)라면 이 설정을 통해서 처리능력을 올릴 수 있습니다. 하지만, 파티션 개수가 3개고, 배포된 어플리케이션이 3개(컨슈머 3개)라면 이 설정은 의미 없습니다. 결국 컨슈머 그룹의 병렬처리는 파티션의 개수만큼이기 때문입니다.

마치며…

리밸런싱에 대한 처리가 반드시 필요하다고는 생각하지 않습니다. 하지만, 실시간 비동기 처리가 매우 중요하거나 메세지 중복 처리에 대한 이슈가 존재한다면 고려가 필요합니다. 이 글에서 제시한 방법이 항상 정답은 아니기에, 관리하는 서비스를 잘 이해하고, 적절한 방법을 고민하는 것이 중요할 것 같습니다. 비슷한 고민을 하고 계시거나, 카프카를 사용하시는 분들에게 참고가 되었으면 좋겠습니다.

감사합니다.

--

--