백엔드 데이터 인프라 111편 — Kafka Consumer Rebalance Protocol (KIP-848 새 모델)

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 111편. Kafka Consumer Rebalance Protocol — 옛 eager rebalance 의 stop-the-world 문제, Cooperative Sticky 의 점진적 rebalance, KIP-848 의 broker-side assignor + 새 Consumer Group Protocol (Kafka 4.0+) 까지 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 111편 — Kafka Consumer Rebalance Protocol (KIP-848 새 모델)

이 글은 백엔드 데이터 인프라 시리즈 130편 중 111편이에요. 110편 까지 binary format 을 잡았다면, 이번 111편은 Consumer Group 의 핵심Rebalance Protocol. 87편에서 잠깐 본 rebalance 의 깊이KIP-848 의 새 설계.

Rebalance Protocol이 어렵게 느껴지는 이유

옛 (eager) 모델은 stop-the-world — 작업이 다 멈춘다는 뜻이에요. 큰 cluster 나 상태 큰 application 에서는 수 초에서 수 분까지 멈춥니다.

세대를 거치며 진화했는데, v1은 Eager Rebalance (classic) 로 모든 consumer 가 멈춘 뒤 다시 할당받는 방식이고, v2는 Cooperative Sticky (Kafka 2.4+) 로 점진적으로 일부만 멈춰요. v3는 KIP-848 (Kafka 4.0+) — broker-side assignor 가 들어오면서 heartbeat (consumer 가 살아있다고 알리는 신호) 도 가벼워졌습니다.

각 세대의 이유와 차이 를 풀어봅니다.

옛 모델 — Eager Rebalance

동작

Consumer A, B, C 가 partition 0-9 처리
  ↓
Consumer D 가 그룹에 join
  ↓
1. 모든 consumer (A, B, C) 가 *모든 partition revoke*
2. Broker (coordinator) 가 새 assignment 계산: A=0-2, B=3-5, C=6-7, D=8-9
3. 모든 consumer 가 새 assignment 받고 *모든 partition 다시 받음*
4. 처리 재개

문제

Stop-the-world — 모든 partition 이 잠시 멈춰요. 100대 넘는 큰 cluster 면 수 초에서 수 분까지 멈춥니다. Kafka Streams 처럼 상태 큰 application 은 state 를 다시 로드하고 warm up 까지 시간이 또 듭니다.

87편에서 본 rebalance storm 의 원인이 여기예요.

Cooperative Sticky — Kafka 2.4+ 도입

핵심 아이디어

변경되는 partition 만 revoke 하고 나머지는 그대로 둡니다.

동작

Consumer A, B, C 가 partition 0-9 처리
  ↓
Consumer D join
  ↓
1. Coordinator 가 새 assignment 계산: A=0-2, B=3-5, C=6-7, D=8-9
   (A, B, C 는 거의 그대로, 일부만 D 로 이동)
2. *이동할 partition 만* revoke (예: B 의 6-7 만)
3. 나머지 partition = *계속 처리*
4. Revoke 된 partition = 다른 consumer 가 받음
5. 짧은 lag, 부분 멈춤

활성

partition.assignment.strategy=\
    org.apache.kafka.clients.consumer.CooperativeStickyAssignor

Kafka 2.4+ 부터 권장이고, Spring Boot 기본도 권장 변경 진행 중.

효과

Stop-the-world 가 사라지니까 전체 처리는 안 멈춰요. 영향 받는 partition 만 잠시 쉬는 정도라서 큰 cluster 에서 차이가 큽니다.

KIP-848 — Next Generation Consumer Group Protocol (Kafka 4.0+)

옛 protocol 의 한계

옛 방식은 client-side assignor 였어요 — consumer 가 직접 partition 분배를 계산했습니다. heartbeat 마다 cluster 메타데이터를 다 주고받아서 무겁고, Static Membership (instance ID 를 고정해 재참가 시 rebalance 회피) 도 한계가 있었어요.

새 모델

이제 broker-side assignor 로 broker 가 partition 할당을 직접 결정 합니다. heartbeat 도 변경된 부분만 통신해서 가볍고, incremental rebalance 가 기본 동작 이에요. Static Membership 의 fencing (중복 instance 차단) 도 더 강해졌습니다.

활성

group.protocol=consumer       # 새 protocol (Kafka 4.0+)
# 또는
group.protocol=classic        # 옛 protocol

Remote Assignor

group.remote.assignor=range
# 또는
group.remote.assignor=uniform

Broker 가 지정된 assignor 알고리즘 으로 할당합니다. 클라이언트 코드를 바꾸지 않고도 assignment 전략을 갈아끼울 수 있어요.

Rebalance 전체 흐름

Consumer 의 입장

1. Consumer 가 그룹 join (JoinGroupRequest)
2. Coordinator 가 그룹 멤버 정보 수집
3. (옛) 한 consumer 가 leader 로 지정 → assignment 계산
   (새) Broker 의 remote assignor 가 계산
4. Coordinator 가 각 consumer 에게 assignment 전달
5. (Cooperative) 변경된 partition 만 revoke
6. 새 partition 받기·처리 시작

Rebalance 트리거

Consumer 가 join (시작·재시작) 하거나 leave (정상 종료) 할 때, session timeout (heartbeat 실패) 이 나거나 max.poll.interval.ms 를 초과할 때 트리거됩니다. Topic 의 partition 수가 바뀌거나 subscription 이 바뀌어도 발생해요.

운영 영향

옛 (Eager) — Critical 영향

30 second pause (rebalance)
↓
Lag 폭증
↓
모든 consumer 가 *기존 처리 컨텍스트 잃음*
↓
Stream state 재로드 (수 분~수 시간)

대규모 환경이면 서비스 영향이 큽니다.

Cooperative Sticky — 약함

짧은 partial pause (수 백 ms~수 초)
↓
영향 받는 partition 만 lag
↓
나머지 partition = 정상

KIP-848 — 가장 가벼움

거의 zero-downtime
↓
gradual reassignment
↓
state 재로드 최소

Rebalance 시각화 — Metrics

JMX (Java 모니터링 표준) 로 뽑는 지표는 시간당 rebalance 횟수를 보여주는 rebalance-rate-per-hour, 평균 시간을 보여주는 rebalance-latency-avg, 최대 시간을 보여주는 rebalance-latency-max 입니다.

좋은 환경은 시간당 1 미만, latency 5초 미만. 나쁜 환경은 시간당 10 이상 이면 rebalance storm 이고, latency 30초 이상 이면 처리에 큰 영향이 옵니다.

Rebalance 회피·완화 패턴

1. Static Membership (Kafka 2.3+)

87편에서 본 내용:

spring:
  kafka:
    consumer:
      properties:
        group.instance.id: ${HOSTNAME}

같은 instance.id 로 재참가 하면 rebalance 가 안 일어나요. rolling deploy 나 정기 재시작 에서 차이가 큽니다.

2. Cooperative Sticky Assignor

partition.assignment.strategy=\
    org.apache.kafka.clients.consumer.CooperativeStickyAssignor

대부분 환경에서 권장이에요.

3. KIP-848 (Kafka 4.0+)

group.protocol=consumer

가장 가벼운 방법인데, consumer 와 broker 가 모두 4.0+ 이어야 합니다.

4. Session Timeout 적절히

session.timeout.ms=60000          # 60초 (긴 처리 환경 90~180초)
heartbeat.interval.ms=20000        # session/3
max.poll.interval.ms=300000        # 처리 길면 늘림

너무 짧으면 GC pause 나 네트워크 hiccup 으로 false rebalance 가 나요.

5. Consumer 수 안정화

자주 scale up/down 하면 rebalance 가 폭증합니다. consumer 수를 안정적으로 유지 하거나 Static Membership 로 가요.

ConsumerRebalanceListener — 코드 측 대응

92편에서 본 패턴 — ConsumerRebalanceListener 는 rebalance 시점에 콜백을 받는 인터페이스예요.

consumer.subscribe(Arrays.asList("orders"), new ConsumerRebalanceListener() {
    @Override
    public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
        // partition 빼앗기 전 — commit + cleanup
        consumer.commitSync(currentOffsets);
    }

    @Override
    public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
        // 새 partition 받음 — 초기화
    }

    @Override
    public void onPartitionsLost(Collection<TopicPartition> partitions) {
        // (Cooperative 만) 비정상 손실 — cleanup
    }
});

onPartitionsLost 는 Cooperative Sticky 에서 exceptional case — 비정상 손실 때만 불려요.

한계·실무 함정

1. 옛 (Eager) 의 stop-the-world

큰 환경에서는 사고로 이어져요. Cooperative Sticky 로 전환 을 권장합니다.

2. KIP-848 호환성

새 protocol 은 broker 와 consumer 가 모두 4.0+ 이어야 해요. 옛 client 와 섞으면 안 됩니다.

3. Static Membership ID 중복

FencedInstanceIdException (instance.id 중복 차단 예외) 으로 종료돼요. unique 보장 과 모니터링을 같이 가져갑니다.

4. ConsumerRebalanceListener 누락

partition revoke 전에 commit 안 하면 중복 처리가 폭증 해요. 반드시 listener 를 구현 합니다.

5. session.timeout 너무 짧음

GC pause 나 네트워크 hiccup 이 false rebalance 로 이어집니다. 60~90초 가 일반적이에요.

시험 직전 한 번 더 — Rebalance Protocol 함정 압축 노트

  • 3가지 세대 = Eager (옛) · Cooperative Sticky (2.4+) · KIP-848 (4.0+)
  • Eager = stop-the-world, 모든 partition revoke → 큰 환경 사고
  • Cooperative Sticky = 점진적, 변경된 partition 만 revoke
  • 권장 = partition.assignment.strategy=CooperativeStickyAssignor
  • KIP-848 = broker-side assignor, 가벼운 heartbeat, incremental 기본
  • 활성 = group.protocol=consumer + group.remote.assignor=range/uniform
  • Kafka 4.0+ 권장
  • Rebalance 트리거 = consumer join/leave·session timeout·max.poll.interval 초과·partition 변경·subscription 변경
  • JMX 메트릭 = rebalance-rate-per-hour (< 1 좋음) · rebalance-latency-avg (< 5s)
  • Rebalance Storm = 시간당 > 10 → 운영 사고
  • 회피 패턴 — Static Membership · Cooperative Sticky · KIP-848 · session.timeout 적절히 · consumer 안정화
  • Static Membership = group.instance.id 고정, 재시작 시 rebalance 회피
  • ConsumerRebalanceListener = onPartitionsRevoked (commit + cleanup) · onPartitionsAssigned (초기화) · onPartitionsLost (Cooperative 비정상)
  • 함정 — Eager 의 stop-the-world (큰 환경)
  • 함정 — KIP-848 호환성 (4.0+ 만)
  • 함정 — Static Membership ID 중복 = FencedInstanceIdException
  • 함정 — ConsumerRebalanceListener 누락 → 중복 폭증
  • 함정 — session.timeout 너무 짧음 → false rebalance

공식 문서: KIP-848 Next Gen Consumer Group Protocol 에서 자세한 설계를 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!