카프카 마스터 노트 시리즈 4편. Consumer Group이 Kafka 병렬 처리의 단위가 되는 원리, 그룹 코디네이터의 역할, 4가지 파티션 할당 전략(Range·Round-Robin·Sticky·Cooperative Sticky), 리밸런싱이 발생하는 5가지 시점과 그 비용, Heartbeat·Session Timeout 설정, Static Membership으로 리밸런싱 회피까지.
이 글은 카프카 마스터 노트 시리즈의 네 번째 편입니다. 3편(Producer/Consumer)에서 단일 컨슈머를 봤다면, 이번엔 여러 컨슈머가 협력하는 단위 — Consumer Group.
Consumer Group이 Kafka의 모든 병렬 처리·확장성·고가용성의 핵심. 동시에 리밸런싱은 운영 환경의 가장 큰 함정 — 한 인스턴스 추가만 해도 전체가 잠시 멈춥니다.
처음 Consumer Group이 어렵게 느껴지는 이유
처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, "같은 group.id면 한 그룹"이라는 단순 룰이 강력해서. 그게 모든 동작을 결정하는데, 단순해 보여서 디테일을 놓치기 쉽습니다. 둘째, 리밸런싱이 어떻게 일어나는지 그림이 잘 안 잡힙니다. 한 컨슈머 죽으면 누가 결정해서 누가 새 파티션을 받는가?
해결법은 한 가지예요. "같은 영업팀의 영업사원들" 비유로 묶는 것. Consumer Group = 영업팀, Consumer = 영업사원, Partition = 담당 구역, Group Coordinator = 팀장. 영업사원 한 명 그만두면 팀장이 구역을 재분배. 이 그림이 잡히면 모든 동작이 따라옵니다.
Consumer Group — 단순한 약속
같은 group.id = 같은 그룹.
props.put("group.id", "order-processor");
그룹 안에서:
- 각 파티션은 1개 컨슈머에만 할당
- 컨슈머는 여러 파티션 받을 수 있음
order-events 토픽 (3 파티션)
group=order-processor (3 컨슈머)
├── consumer-1 → partition 0
├── consumer-2 → partition 1
└── consumer-3 → partition 2
컨슈머 추가 시:
group (4 컨슈머, 3 파티션)
├── consumer-1 → partition 0
├── consumer-2 → partition 1
├── consumer-3 → partition 2
└── consumer-4 → 놀음 (파티션 X)
여기서 정말 중요한 시험 함정 — 컨슈머 수 > 파티션 수면 일부 컨슈머는 놀게 됨. 파티션이 병렬도의 상한. 더 많은 병렬이 필요하면 파티션 수 늘려야.
다중 그룹 — 독립적 소비
order-events 토픽
├── group=order-processor (3 컨슈머)
├── group=analytics-team (2 컨슈머)
└── group=audit-system (1 컨슈머)
각 그룹은 독립적으로 모든 메시지 소비. 같은 메시지를 3 그룹이 각자 처리.
이게 Kafka의 강점 — N개 시스템이 같은 데이터를 자기 페이스로 처리.
Group Coordinator — 그룹 관리자
각 그룹에 하나의 코디네이터 브로커 할당. 역할:
- 그룹 멤버 추적 (누가 살아있나)
- 파티션 할당 결정
- Offset commit 저장 (
__consumer_offsets) - 리밸런싱 트리거
코디네이터 결정:
hash(group.id) % numPartitionsOf(__consumer_offsets) → 그 파티션의 Leader
여기서 시험 함정이 하나 있어요. 코디네이터 다운 시 자동 재선출. __consumer_offsets 토픽이 복제됨 → 다른 브로커가 코디네이터 인계.
파티션 할당 전략 4종
Consumer Group이 결정. partition.assignment.strategy 옵션.
1. Range Assignor (기본, 옛 방식)
3 파티션, 2 컨슈머:
consumer-1 → [partition 0, 1]
consumer-2 → [partition 2]
토픽 단위로 연속 할당. 단순하지만 불균형 위험 (다중 토픽 시).
2. Round-Robin Assignor
3 파티션, 2 컨슈머:
consumer-1 → [partition 0, 2]
consumer-2 → [partition 1]
전체 파티션을 round-robin. 더 균등.
3. Sticky Assignor
Round-Robin과 비슷하지만, 리밸런싱 시 기존 할당 최대한 유지:
Before: c1=[0,1], c2=[2,3]
c2 죽음 → 리밸런싱
After: c1=[0,1,2,3] (1만 받음, 0,1은 그대로)
장점 — 처리 컨텍스트 유지·캐시 활용.
4. Cooperative Sticky Assignor (권장, Kafka 2.4+)
Sticky의 무중단 버전. 리밸런싱 중에도 영향 없는 컨슈머는 계속 처리.
여기서 정말 중요한 시험 함정 — Cooperative Sticky가 신규 표준. Kafka 2.4+. 기존엔 모든 컨슈머가 stop-the-world 리밸런싱. 이젠 점진적 협력.
props.put("partition.assignment.strategy",
"org.apache.kafka.clients.consumer.CooperativeStickyAssignor");
리밸런싱 — 그룹 변경 시 재할당
트리거 5가지
- 새 컨슈머 추가
- 컨슈머 종료 (
unsubscribe()또는 정상 종료) - 컨슈머 죽음 (heartbeat 안 옴)
- 토픽 파티션 수 변경 (늘리기)
- 구독 토픽 변경
비용
[stop-the-world 리밸런싱 (옛 방식)]
모든 컨슈머가 멈춤
→ 코디네이터에 join 요청
→ 새 할당 받음
→ 처리 재개
= 수십 ms ~ 수 분 (그룹 크기·파티션 수 비례)
대규모 그룹에선 체감 가능한 지연. 운영의 핵심 함정.
여기서 정말 중요한 시험 함정 — 리밸런싱 = 일시 정지. 한 컨슈머 추가만 해도 전체 그룹이 멈춤. 무중단 배포 어려움. Cooperative Sticky로 완화.
Heartbeat·Session Timeout
컨슈머 생존 확인 메커니즘.
heartbeat.interval.ms = 3000 # 3초마다 heartbeat
session.timeout.ms = 45000 # 45초 이상 안 오면 죽었다고 판단
컨슈머가 정해진 시간 내 heartbeat 안 보내면 → 죽음 판정 → 리밸런싱.
max.poll.interval.ms — 별도 시계
max.poll.interval.ms = 300000 # 5분
처리가 너무 오래 걸리면도 죽음 판정. Heartbeat은 백그라운드 스레드에서 잘 가도, poll() 호출이 5분 이상 멈추면 추방.
여기서 시험 함정이 하나 있어요. 무거운 처리 = max.poll.interval.ms 늘리기. 한 메시지 처리에 10분 걸리면 600000(10분)으로. 또는 메시지를 별도 큐에 넘겨 비동기 처리.
Static Membership — 리밸런싱 회피
Kafka 2.3+. 컨슈머에 고정 ID.
props.put("group.instance.id", "consumer-prod-1");
이 컨슈머가 잠깐 재시작해도 같은 ID로 다시 들어오면 리밸런싱 X. 일시적 재시작에 강건.
session.timeout.ms 안에 같은 group.instance.id로 복귀
→ 같은 파티션 그대로 받음
운영의 무중단 배포·rolling restart에 필수.
Offset Commit 위치
__consumer_offsets 내부 토픽:
key: (group.id, topic, partition)
value: offset, metadata, timestamp
50개 파티션 (기본). Compaction enabled — 같은 key 옛 값 자동 정리.
# 그룹 상태 확인
kafka-consumer-groups --bootstrap-server localhost:9092 \
--group order-processor --describe
# 결과:
# TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
# order-events 0 1234 1300 66
# order-events 1 2345 2350 5
# ...
Lag — 컨슈머 모니터링 핵심
LAG = LOG-END-OFFSET - CURRENT-OFFSET
지속 증가 = 컨슈머가 따라가지 못함. 처리량 ↓ 또는 트래픽 ↑.
대응:
- 컨슈머 인스턴스 추가 (파티션 수까지)
- 처리 로직 최적화
- 파티션 수 늘리기
여기서 시험 함정이 하나 있어요. Lag 모니터링은 운영 필수. Burrow·Kafka Lag Exporter 같은 도구로 알람 설정.
subscribe() vs assign()
// 동적 — Consumer Group + 자동 할당
consumer.subscribe(List.of("order-events"));
// 정적 — 직접 파티션 지정 (Consumer Group X)
consumer.assign(List.of(
new TopicPartition("order-events", 0),
new TopicPartition("order-events", 1)
));
assign은 Consumer Group 안 씀. 리밸런싱 X. 특수 용도.
정확한 Offset Commit 패턴
안전 패턴
while (running) {
var records = consumer.poll(Duration.ofMillis(100));
for (var record : records) {
try {
process(record);
} catch (Exception e) {
// 에러 처리: 재시도 또는 DLQ (7편)
handleError(record, e);
}
}
consumer.commitSync(); // 처리 완료 후
}
핵심 — 처리 완료 후 commit. 처리 실패 시 commit 안 됨 → 다음 poll에서 재시도.
위험 패턴
while (running) {
var records = consumer.poll(...);
consumer.commitSync(); // X — 처리 전 commit
for (var record : records) {
process(record); // 실패해도 commit 됨 → 손실
}
}
여기서 정말 중요한 시험 함정 — At-least-once vs At-most-once. Commit 위치가 결정. 처리 후 commit = at-least-once (재처리 가능, 중복 위험), 처리 전 commit = at-most-once (손실 위험). 대부분 선택은 at-least-once + 멱등 처리.
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 4편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- Consumer Group = 같은 group.id
- 그룹 안 — 각 파티션은 1 컨슈머에만
- 컨슈머 수 > 파티션 수 = 일부 놀음
- 다중 그룹 = 독립 소비 (각자 모든 메시지)
- Group Coordinator = 그룹 관리 브로커
- 코디네이터 다운 시 자동 재선출
- Offset commit →
__consumer_offsets내부 토픽 (50 파티션) - 4 할당 전략 — Range / Round-Robin / Sticky / Cooperative Sticky
- Cooperative Sticky가 신규 표준 (Kafka 2.4+)
- 리밸런싱 트리거 — 추가·종료·죽음·파티션 수 변경·구독 변경
- 리밸런싱 = 일시 정지 (옛 방식 stop-the-world)
- Cooperative Sticky로 완화
- Heartbeat 3초마다 / Session timeout 45초
max.poll.interval.ms= 처리 시간 한계 (5분 기본)- 무거운 처리는 늘리거나 비동기로
- Static Membership (
group.instance.id) = rolling restart 무중단 - Lag = LOG-END-OFFSET - CURRENT-OFFSET
- 지속 증가 = 컨슈머가 따라가지 못함
- 도구 — Burrow·Kafka Lag Exporter
subscribe()동적 /assign()정적 (Group X)- At-least-once 권장 = 처리 후 commit + 멱등 처리
- 처리 전 commit = at-most-once (손실 위험)
시리즈 다른 편
- 1편 — EDA·Kafka 기초·KRaft
- 2편 — Topic·Partition·Offset
- 3편 — Producer·Consumer 동작
- 4편 — Consumer Group·리밸런싱 (현재 글)
- 5편 — Reactor Kafka
- 6편 — Cluster·HA·Best Practices
- 7편 — 배치·에러·트랜잭션
- 8편 — Spring Kafka·테스트·보안
- 9편 — Spring Cloud Stream 기초
- 10편 — StreamBridge 동적 라우팅
- 11편 — Fan-Out / Fan-In
- 12편 — SCS Tips & Tricks
- 13편 — Saga 코레오그래피
- 14편 — Saga 오케스트레이터
- 15편 — Transactional Outbox
공식 문서: Kafka Consumer Group Protocol 에서 더 깊이.
다음 글(5편)에서는 Reactor Kafka — KafkaReceiver·KafkaSender·리액티브 파이프라인·백프레셔까지 풀어 갑니다.