컨슈머 그룹 운영 디테일 — 하트비트, 그룹 코디네이터/리더, 파티션 할당, 중복 처리
50 Kafka Interview Questions for Data Engineers (2026) — (블로그, 발행처 미상 — 데이터 엔지니어링 교육 콘텐츠 사이트) 50 Kafka Interview Questions for Data Engineers (2026), Q12-13, Q15-16, Q35, Q42. Q13(Eager vs Cooperative Sticky)에 KIP-848 최신 프로토콜 맥락을 보강.
이 모듈을 다 읽으면
- 컨슈머 하트비트, session.timeout.ms, max.poll.interval.ms가 각각 무엇을 감지하는지 구분할 수 있다
- Group Coordinator와 Group Leader의 역할 분담을 설명할 수 있다
- RangeAssignor/RoundRobinAssignor/StickyAssignor의 차이와, Eager에서 Cooperative로 리밸런싱 프로토콜이 진화한 이유를 설명할 수 있다
- 컨슈머 쪽에서 멱등한 처리로 중복 메시지를 다루는 실무 패턴을 설명할 수 있다
컨슈머 그룹이 파티션을 어떻게 분배·재분배하는지(하트비트, 코디네이터/리더, 할당 전략), 그리고 재시도로 인해 불가피하게 발생하는 중복 메시지를 컨슈머 쪽에서 어떻게 안전하게 처리하는지를 정리한다.
컨슈머 하트비트 — 두 가지 타임아웃이 감지하는 서로 다른 장애
컨슈머 그룹의 각 멤버는 별도의 하트비트 스레드를 통해 주기적으로 Group Coordinator(해당 그룹을 담당하는 브로커)에게 생존 신호를 보낸다. session.timeout.ms 시간 안에 하트비트가 도착하지 않으면 코디네이터는 그 컨슈머가 죽었다고 판단해 그룹에서 제외하고 리밸런싱을 트리거한다 — 이는 프로세스 크래시나 네트워크 단절처럼 컨슈머가 반응 자체를 못 하는 상황을 감지하기 위한 것이다.
반면 max.poll.interval.ms는 다른 종류의 장애를 감지한다 — 컨슈머 프로세스와 하트비트 스레드는 살아있지만, poll() 호출 사이의 애플리케이션 처리 로직(예: 느린 DB 쓰기, 무한 루프에 가까운 버그)이 너무 오래 걸려 다음 poll()을 제때 호출하지 못하는 상황이다. 하트비트가 별도 스레드에서 도는 최신 클라이언트에서도, poll() 간격이 이 값을 넘기면 코디네이터는 해당 컨슈머가 사실상 멈춰서 더 이상 메시지를 처리하지 못하고 있다고 판단해 그룹에서 내보내고 리밸런싱을 일으킨다. 따라서 처리 로직이 느려 랙이 쌓이는 문제를 max.poll.records를 낮추거나 비동기 처리로 완화하지 않으면, 랙 문제가 불필요한 리밸런싱까지 함께 유발하는 악순환이 생길 수 있다.
핵심 포인트
- session.timeout.ms는 컨슈머 프로세스/네트워크가 죽어 하트비트 자체가 끊긴 상황을 감지한다
- max.poll.interval.ms는 프로세스는 살아있지만 처리 로직이 느려 poll()을 제때 호출하지 못하는 상황을 감지한다
- 처리 지연으로 인한 컨슈머 랙은 방치하면 max.poll.interval.ms 초과로 인한 리밸런싱까지 함께 유발할 수 있다
Group Coordinator와 Group Leader — 조율자와 실행자의 분업
Group Coordinator는 특정 컨슈머 그룹의 상태(멤버십, 파티션 할당, 커밋된 오프셋)를 관리하는 책임을 지는 브로커다. 리밸런싱이 시작되면 코디네이터는 현재 그룹에 속한 모든 멤버의 목록을 모으고, 그중 한 멤버를 Group Leader로 지정한다.
Group Leader는 일반 컨슈머 애플리케이션 프로세스이지만, 이번 리밸런싱에 한해 실제 파티션 할당 계산을 수행하는 역할을 코디네이터로부터 위임받는다 — 즉 "누가 어느 파티션을 가져갈지"를 결정하는 연산 자체는 브로커가 아니라 클라이언트(리더로 뽑힌 컨슈머)의 파티션 할당자(assignor) 로직에서 이뤄지고, 코디네이터는 그 결과를 받아 그룹 전체에 배포하는 역할을 한다. 이런 분업 덕분에 새로운 할당 전략을 추가하거나 바꿀 때 브로커를 업그레이드하지 않고도 클라이언트 라이브러리 버전만 올리면 되는 유연성이 생긴다 — 다만 KIP-848 기반 새 컨슈머 그룹 프로토콜(Kafka 4.0 GA)에서는 이 할당 계산 책임 자체가 서버(그룹 코디네이터) 쪽으로 이동해, 클라이언트 리더가 계산하던 기존 방식과 다른 흐름을 갖는다.
핵심 포인트
- Group Coordinator는 그룹의 멤버십·할당·오프셋 상태를 관리하는 브로커다
- Group Leader는 코디네이터가 지정한 컨슈머 멤버로, 실제 파티션 할당 계산을 수행한다
- 기존 프로토콜에서는 할당 계산이 클라이언트(리더) 책임이었지만, KIP-848 신규 프로토콜은 이를 서버(코디네이터) 쪽으로 옮겼다
파티션 할당 전략 3종과 Eager → Cooperative 리밸런싱의 진화
파티션 할당 전략은 그룹 리더가 "어느 컨슈머가 어느 파티션을 맡을지"를 계산하는 알고리즘이다. RangeAssignor는 토픽별로 파티션을 연속된 범위로 나눠 컨슈머에 순서대로 배정하고(여러 토픽을 동시에 구독하면 같은 컨슈머가 여러 토픽에서 계속 몰릴 수 있다는 단점이 있다), RoundRobinAssignor는 모든 구독 토픽의 파티션을 한데 모아 컨슈머들에게 균등하게 라운드로빈 배정한다. StickyAssignor는 그 위에서 한 걸음 더 나아가, 최대한 균형 잡힌 분배를 유지하면서도 리밸런싱 전후로 파티션 소유권 변경을 최소화(sticky, 가능한 한 같은 컨슈머가 같은 파티션을 계속 갖도록)하는 것을 목표로 한다.
하지만 이 세 전략은 모두 "eager" 리밸런싱 프로토콜 위에서 동작한다는 공통점이 있다 — 리밸런싱이 시작되면 그룹의 모든 컨슈머가 자신이 가진 파티션 전부를 일단 반납하고, 새 할당이 계산·배포될 때까지 처리를 멈춰야 한다. 이는 재배정과 무관한 파티션을 담당하던 컨슈머까지 불필요하게 멈추게 만든다. Cooperative Sticky Rebalancing(CooperativeStickyAssignor, Kafka 2.4+)은 이 문제를 해결한다 — 리밸런싱이 필요할 때 전체 파티션을 반납시키는 대신, 실제로 다른 컨슈머로 옮겨가야 하는 파티션만 점진적(incremental)으로 반납·재배정하고 나머지 컨슈머는 계속 처리를 이어간다. 여기서 더 나아간 KIP-848(Kafka 4.0 GA) 프로토콜은 협력적·점진적 재배정을 기본값으로 삼고 할당 계산 자체를 서버 쪽으로 옮겨, 대규모 그룹에서도 리밸런싱이 더 빠르고 덜 파괴적으로 일어나게 한다.
핵심 포인트
- RangeAssignor는 토픽별 연속 범위 배정, RoundRobinAssignor는 전체 파티션 균등 라운드로빈 배정을 한다
- StickyAssignor는 균형 잡힌 분배를 유지하면서 리밸런싱 전후 파티션 이동을 최소화한다
- 이 세 전략은 모두 eager 프로토콜 기반이라 리밸런싱 시 그룹 전체가 파티션을 반납해야 한다는 공통 한계가 있다
- CooperativeStickyAssignor(2.4+)와 KIP-848(4.0 GA)은 실제로 이동이 필요한 파티션만 점진적으로 재배정한다
컨슈머 쪽 중복 처리 — 멱등한 쓰기로 재처리에 대비하기
프로듀서의 멱등성/트랜잭션과 별개로, at-least-once 전달 하에서는 컨슈머 쪽에서도 같은 메시지를 두 번 처리하게 되는 상황(예: 처리는 끝냈지만 오프셋 커밋 전에 컨슈머가 죽어 재시작 후 같은 메시지를 다시 읽는 경우)이 있을 수 있다. 이를 근본적으로 막을 수 없다면, 대신 같은 메시지를 여러 번 처리해도 결과가 달라지지 않도록(=멱등하도록) 컨슈머 로직을 설계하는 것이 실무에서 가장 흔한 해법이다.
대표적인 패턴은 메시지의 비즈니스 키(예: 주문 ID)를 기준으로 UPSERT(있으면 갱신, 없으면 삽입)를 수행하는 것이다 — 같은 메시지가 두 번 들어와도 두 번째 UPSERT는 이미 같은 값으로 덮어쓸 뿐이라 최종 상태는 동일하다. 이 접근은 Kafka 자체의 트랜잭션 API보다 구현이 단순하고, 외부 시스템(RDB, 검색 인덱스 등)으로 나가는 싱크에서 정확히 한 번 의미론을 흉내 낼 때 특히 유용하다. 다만 UPSERT가 아니라 "누적 합산"처럼 순서/중복에 민감한 연산이라면 별도의 중복 제거 키 추적이 필요하다.
핵심 포인트
- at-least-once 하에서는 컨슈머가 같은 메시지를 두 번 처리하는 상황을 완전히 배제할 수 없다
- 실무 해법은 재처리를 막는 대신 처리 자체를 멱등하게 만드는 것이다
- 비즈니스 키 기반 UPSERT는 중복 처리에도 최종 상태가 같아지는 대표적인 멱등 패턴이다