컨슈머 그룹의 구성이 바뀔 때 파티션을 소비자들에게 다시 나눠 주는 과정.
언제 일어나나
- 소비자가 추가되거나 종료될 때
- 소비자가 죽은 것으로 판정될 때 (하트비트 끊김, poll 지연)
- 파티션 수가 바뀔 때
- 구독 토픽이 바뀔 때
무엇이 문제인가 — 전체가 멈춘다
전통적인(eager) 방식에서는
- 그룹 코디네이터가 리밸런싱 시작을 알림
- 2. 모든 소비자가 자기 파티션을 반납 — ← 여기서 소비 전면 중단
- 새 할당을 계산
- 각자 새 파티션을 받아 재개
2~4 사이에 그룹 전체의 소비가 멈춘다(stop-the-world). 소비자가 많고 재시작이 잦으면 이 정지가 반복돼 지연이 눈에 띈다.
가장 흔한 원인 — poll 지연
while (true) {
var records = consumer.poll(Duration.ofMillis(100));
for (var r : records) {
heavyWork(r); // ← 여기가 5분 걸리면?
}
}
max.poll.interval.ms(기본 5분) 안에 다음 poll()을 호출하지 않으면
코디네이터가 이 소비자를 죽은 것으로 판단하고 리밸런싱을 시작한다.
그런데 소비자는 멀쩡히 일하는 중이다. 작업을 마치고 커밋하려 하면 "넌 이미 쫓겨났다"며 실패하고, 그 파티션은 다른 소비자가 처음부터 다시 처리한다. → 중복 처리 + 무한 리밸런싱 루프.
대응
| 방법 | 내용 |
|---|---|
max.poll.records 줄이기 | 한 번에 가져오는 양을 줄여 처리 시간 단축 |
max.poll.interval.ms 늘리기 | 오래 걸리는 작업을 허용 |
| 처리를 별도 스레드로 | poll 루프는 계속 돌리고 작업은 위임 |
| 정적 멤버십 | group.instance.id 지정 → 재시작해도 같은 파티션 유지 |
| 협력적 리밸런싱 | CooperativeStickyAssignor — 전체 중단 없이 필요한 것만 이동 |
하트비트와 poll 간격은 다르다
session.timeout.ms— 하트비트가 끊긴 시간. 별도 스레드가 보내므로 처리가 길어도 유지된다max.poll.interval.ms— poll 호출 간격. 처리 시간이 여기 걸린다
"하트비트는 살아 있는데 쫓겨났다"면 원인은 후자다.
Kafka 4.0의 새 프로토콜 (KIP-848)
2025년 3월 Kafka 4.0에서 차세대 리밸런스 프로토콜이 정식(GA) 이 됐다. 바뀐 핵심은 배정을 누가 계산하느냐다.
-
[기존] — 그룹 멤버 중 리더 컨슈머가 배정을 계산
- 멤버가 하나 들고 날 때마다 그룹 전체가
- 동기화 지점(join/sync 라운드)을 거쳐야 한다
- 멤버가 하나 들고 날 때마다 그룹 전체가
-
[KIP-848] — 브로커의 그룹 코디네이터가 계산해
- 각 컨슈머에게 점진적으로 내려보낸다
- 나머지 컨슈머의 소비를 멈추지 않는다
컨슈머 수가 많은 대규모 그룹일수록 차이가 크다. 클라이언트도 단순해진다 — 배정 로직이 브로커로 옮겨갔기 때문이다.
group.protocol=consumer # 새 프로토콜로 참여 (미지정 시 기존 방식)
면접 포인트: "리밸런싱은 stop-the-world인가요?"라는 질문에 세 단계로 답하면 깊이가 드러난다 — 예전 eager는 전부 멈췄고, 2.4의 cooperative는 옮겨지는 파티션만 멈췄으며, 4.0의 KIP-848은 브로커가 계산해 점진 적용한다.