consumer group — topic을 여러 consumer가 나눠 읽는 팀

orders topic에 주문이 초당 수만 건 쏟아진다. consumer 하나로는 다 못 읽는다. consumer를 여럿 띄워 병렬로 읽고 싶다 — 하지만 같은 메시지를 두 consumer가 동시에 읽으면 중복이 생긴다. 이걸 해결하는 게 consumer group이다. 여러 consumer가 topic의 partition을 서로 겹치지 않게 나눠 읽는 팀이다.

이 글은 consumer group이 뭔지, 어떻게 partition을 나누는지, consumer가 들락날락할 때 일어나는 rebalance와 그 비용, 그리고 실제로 어떻게 구성하고 관리하는지까지 다룬다.

consumer group이란 — partition을 나눠 읽는 consumer 팀

consumer group은 같은 group 이름을 가진 consumer들의 모임이다. 핵심 규칙은 한 줄이다:

한 consumer group 안에서, partition 1개는 정확히 한 consumer가 담당한다.

구성 무슨 일
partition 4개 + consumer 2명 각 consumer가 2 partition씩
partition 4개 + consumer 4명 각 consumer가 1 partition씩 (최대 병렬)
partition 4개 + consumer 6명 4개만 일하고 2개는 놀음(idle)

왜 "1 partition = 1 consumer"인가 — 같은 partition을 두 consumer가 동시에 읽으면 순서가 꼬이고 offset commit이 충돌한다. 그래서 한 partition은 한 consumer만 단독 소유. 병렬성의 상한 = partition 수다(장 02).

consumer group은 어떻게 쓰이나 — worked scenario

orders topic(partition 6개)을 consumer 3개로 확장:

flowchart TB
    T["orders topic<br/>(partition 0 ~ 5)"] --> A["consumer A<br/>(p0, p1)"]
    T --> B["consumer B<br/>(p2, p3)"]
    T --> C["consumer C<br/>(p4, p5)"]
    subgraph GP["group: order-processors"]
        A
        B
        C
    end
  1. consumer 3개를 같은 group(order-processors)으로 띄운다.
  2. Kafka가 partition을 나눠 줌 — A: p0,p1 / B: p2,p3 / C: p4,p5.
  3. 각 consumer가 자기 partition만 읽어 처리 → 3배 병렬, 중복 없음.

다른 group은 독립적: order-processors group과 analytics group은 서로 간섭 없이 같은 orders topic을 전부 각자 읽는다. group마다 자기 offset을 따로 관리한다.

consumer group 설정 — 어떻게 구성하나

consumer 설정 (Java)

# group 식별자 (핵심 — 같은 group.id = 같은 팀)
group.id=order-processors

# broker 연결
bootstrap.servers=localhost:9092

# 역직렬화
key.deserializer=org.apache.kafka.common.serialization.StringDeserializer
value.deserializer=org.apache.kafka.common.serialization.StringDeserializer

# auto-commit (신뢰성 필요시 false, 장 04)
enable.auto.commit=false

# partition 할당 전략
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor

# rebalance 관련 (차세대 프로토콜 사용시 일부 무시)
session.timeout.ms=45000
heartbeat.interval.ms=3000
max.poll.interval.ms=300000

partition 할당 전략

전략 특징
RangeAssignor topic별로 partition을 범위로 나눔 (오랜 기본)
RoundRobinAssignor round-robin으로 순환 분배
StickyAssignor 재할당 시 기존 할당 최대한 유지 (이동 최소화)
CooperativeStickyAssignor sticky + 증분 교환 (KIP-429, 권장)

차세대 rebalance 프로토콜(4.0 GA) 활성 시 partition.assignment.strategy는 사용 불가. 프로토콜이 자체 관리.

rebalance — consumer가 들락날락하면 partition을 다시 나눈다

consumer group의 구성원이 바뀌면 partition을 재분배한다(rebalance):

  • consumer 합류(새 consumer 띄움) 또는 이탈(크래시·종료).
  • 구독 topic/partition 변경.

rebalance의 비용 — stop-the-world

구형(eager) 프로토콜에선 rebalance 시작 시 모든 consumer가 partition을 내려놓고 전체를 다시 나눈다 — 끝날 때까지 아무도 소비 못함. consumer가 많을수록 멈춤이 길어진다. "consumer를 함부로 재시작하면 안 된다"는 이유.

cooperative rebalance — 변경분만 교체

Kafka 4.0에선 차세대 rebalance 프로토콜이 GA됐다 (consumer-rebalance-protocol.md):

  • 유지 가능한 partition은 그대로, 변경이 필요한 partition만 교체.
  • 나머지 consumer는 계속 소비 → 멈춤 최소화.
  • 활성화: group.protocol=consumer.

차세대 프로토콜 활성 시 heartbeat.interval.ms, session.timeout.ms, partition.assignment.strategy 사용 불가.

ConsumerRebalanceListener — 상태 정리 핵심 훅

partition이 할당/해제될 때 콜백을 받아 상태를 정리한다:

consumer.subscribe(Arrays.asList("orders"), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
        // partition을 잃기 전에 처리 중인 offset을 commit (유실 방지)
        consumer.commitSync();
        // state store 정리 등
    }

    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
        // 새 partition 할당 시 초기화 (예: offset seek, 캐시 갱신)
    }
});

이 훅이 rebalance 중 데이터 유실/중복을 막는 핵심이다. 멱등 처리와 함께 써야 at-least-once를 지킨다.

Kafka 4.3 실습 — 직접 확인하기

사전: KRaft 단일 노드, grp-demo topic(--partitions 4), 메시지 다수 사전 produce.

group 상태와 할당 확인

# Kafka 4.3, KRaft 단일 노드
bin/kafka-consumer-groups.sh --describe --group g-state --state --bootstrap-server localhost:9092
COORDINATOR (ID)     ASSIGNMENT-STRATEGY   STATE     #MEMBERS
localhost:9092 (0)   range                 Stable    4

확인할 것: STATE(Stable/PreparingRebalance/CompletingRebalance), 멤버 수.

partition 할당 분포

bin/kafka-consumer-groups.sh --describe --group g-state --members --verbose --bootstrap-server localhost:9092

다중 consumer 병렬 실습

터미널 3개를 열어 같은 group으로 consumer를 각각 띄운다:

# 터미널 1
bin/kafka-console-consumer.sh --topic grp-demo --group demo-group --bootstrap-server localhost:9092
# 터미널 2 (같은 group)
bin/kafka-console-consumer.sh --topic grp-demo --group demo-group --bootstrap-server localhost:9092
# 터미널 3 (같은 group)
bin/kafka-console-consumer.sh --topic grp-demo --group demo-group --bootstrap-server localhost:9092

메시지를 produce하면 3개 consumer가 partition을 나눠 각자 다른 메시지를 받는다. 한 consumer를 종료(Ctrl+C)하면 → rebalance → 남은 consumer가 그 partition을 이어받는다.

차세대 rebalance 프로토콜 활성화

# consumer 설정에 추가
group.protocol=consumer

이 설정으로 4.0 차세대 프로토콜 활성 → rebalance 멈춤 최소화.

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"consumer를 늘리면 무조건 빨라진다" partition 수가 상한. 초과분은 idle
"다른 consumer group은 메시지를 나눠 갖는다" 각 그룹이 독립적으로 전체 소비
"rebalance는 순식간이라 무시해도 된다" 구형에선 stop-the-world. 빈번 재시작은 처리량 저하
"partition.assignment.strategy는 항상 설정해야" 차세대 프로토콜 활성 시 사용 불가

더 깊이

  • static membership(KIP-345): group.instance.id로 고정 identity. 재시작 시 rebalance 최소화 → 상태 저장 앱에 유리.
  • assignment 전략 선택: CooperativeStickyAssignor(권장)가 가장 적은 이동으로 rebalance.
  • rebalance 리스너와 at-least-once: onPartitionsRevoked에서 commit하지 않으면, partition 이관 시 미처리 메시지가 중복/유실.

요약 — 이 글의 결론

  • consumer group = 같은 group 이름의 consumer 모임. partition을 겹치지 않게 나눠 읽음.
  • 핵심 규칙: partition 1개 = consumer 1명. 병렬성 상한 = partition 수.
  • group 간 독립: 각 group이 전체 topic을 따로 소비(자기 offset 관리).
  • 설정: group.id가 팀 식별자. partition.assignment.strategy로 할당 알고리즘 선택.
  • rebalance: 멤버십 변화 시 재분배. 구형=stop-the-world, 차세대(4.0 GA, group.protocol=consumer)=변경분만 교체.
  • ConsumerRebalanceListener: onPartitionsRevoked에서 commit+정리 → rebalance 중 유실/중복 방지.

생각해 볼 문제

  1. consumer group이 "partition을 겹치지 않게 나눠 읽는 팀"이라는 것이 왜 필요한가?
  2. partition 6개에 consumer 4개. 각 consumer는 몇 partition? 8개로 늘리면?
  3. 주문 처리 group과 분석 group이 같은 topic을 읽을 때 서로 간섭이 없는 이유는?
  4. rebalance가 일어나는 상황 세 가지. 구형에서 왜 "stop-the-world"가 문제인가?
  5. 차세대 rebalance 프로토콜(4.0)을 활성화하는 설정은?
  6. ConsumerRebalanceListener의 onPartitionsRevoked에서 해야 할 일은?
  7. CooperativeStickyAssignor가 기본 RangeAssignor보다 나은 점은?

참고

'Tech Artifacts > Kafka' 카테고리의 다른 글

Kafka - 07. log retention  (0) 2026.07.09
Kafka - 06. replication  (0) 2026.07.09
Kafka - 04. consumer  (0) 2026.07.09
Kafka - 03. producer  (0) 2026.07.09
Kafka - 02. topic and partition  (0) 2026.07.09