백엔드 데이터 인프라 87편. Kafka Consumer 설계 — Push vs Pull 의 선택 이유, Long Polling 최적화, Consumer Position (offset) 관리, Consumer Group 분담 모델, Static Membership 으로 rebalance 회피까지 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 130편 중 87편이에요. 86편 까지 Producer 설계를 풀었다면, 이번 87편은 Consumer 측의 핵심 설계 결정. Push vs Pull·Consumer Group·Offset 관리·Rebalance·Static Membership 다섯 가지 영역.
Kafka Consumer 설계가 어렵게 느껴지는 이유
Consumer 가 겉으로는 단순 — "메시지 받기". 하지만 4가지 결정 이 숨어 있어요.
첫째, 왜 Pull 모델인가. 다른 메시지 시스템들의 Push 와 다르게 Kafka 는 Consumer 가 broker(메시지 중계 서버) 에 fetch 요청. 이 결정의 정확한 trade-off.
둘째, Offset 관리의 미묘함. offset(파티션 안 메시지 위치 번호) 으로 "어디까지 처리했는지" 추적하는 방식이 전통 메시지 큐와 완전히 다름. Consumer 가 자기 offset 관리.
셋째, Consumer Group 의 rebalancing 함정. 같은 그룹 안 consumer 가 추가·제거되면 partition(토픽을 쪼갠 단위) 재분배. rebalance 가 자주 일어나면 운영 사고.
Push vs Pull — Kafka 의 선택
Push 모델 (Flume·Scribe 등)
Broker → Consumer (즉시 push)
문제:
- Broker 가 전송 속도 결정 → consumer 가 따라가지 못하면 DoS(서비스 거부 공격) 비슷한 상태
- 다양한 consumer (빠른·느린) 처리가 어려움
Pull 모델 (Kafka 의 선택)
Consumer → "fetch from offset N" → Broker → 데이터 chunk 반환
장점:
- Consumer 가 자기 속도대로 처리 — 느려져도 그냥 뒤처짐
- aggressive batching(한 번에 묶어 가져오기) 가능 — 현재 offset 부터 가능한 모든 데이터 한 번에
- Multiple consumer 가 각자 자기 속도
Pull 모델의 함정 — Busy Polling
데이터 없을 때 tight loop 으로 polling?
해법 — Long Polling:
Consumer → "fetch from offset N, max wait 500ms, min bytes 1024"
→ Broker 가 데이터 들어올 때까지 대기 (최대 500ms)
→ 데이터 들어오면 응답, 없으면 timeout 응답
설정:
fetch.max.wait.ms=500— 최대 대기fetch.min.bytes=1024— 최소 데이터 양 (작으면 더 기다림)
tight loop 없고 즉시 응답성도 보장.
왜 producer 는 push 인가?
여기서 시험 함정 — Kafka 가 pull 만 쓰는 건 아님. Producer → Broker 는 push, Broker → Consumer 만 pull.
이유 — producer 가 수천 개 일 수 있어 각각이 로컬 디스크 운영 은 비현실적. 직접 broker 에 push 가 자연스러움.
한 줄 정리 — Producer push (단순) + Consumer pull (속도 조절·batching) = Kafka 의 비대칭 모델.
Consumer Position — Offset 만 추적
전통 메시지 큐의 함정
대부분 메시지 시스템에서는 broker 가 각 메시지의 ACK(수신 확인 응답) 상태 추적 하고, 메시지마다 sent / acknowledged / consumed 같은 다양한 상태 + 별도 메타데이터 를 들고 있어요.
문제는 두 가지. ACK 전 consumer 가 죽으면 처리 두 번 이 되고, broker 가 수많은 상태 메타데이터 를 관리해야 해서 성능·확장 한계 에 부딪힘.
Kafka 의 단순 모델
핵심 — 각 partition 은 한 consumer 만 (consumer group 안에서) 처리.
따라서:
- Consumer position = 단일 정수 (offset)
- partition 하나당 숫자 하나
- 주기적으로 checkpoint (commit) 만 하면 됨
장점 — 완전 가벼움, broker 부담 0.
Rewind 가능 — 부가 이점
Consumer 가 임의의 offset 으로 되감기 가능. 버그 수정 후 재처리·새 시스템 학습용 과거 데이터 등.
전통 큐는 consume 시 메시지 삭제 라 재처리 불가능. Kafka 는 retention(메시지 보존 기간) 안에서 언제든 재처리.
Consumer Group — 분담 모델
모델
Topic "orders" (10 partitions)
│
↓
Group "order-workers" (3 consumers)
Worker A → partitions 0, 1, 2, 3
Worker B → partitions 4, 5, 6
Worker C → partitions 7, 8, 9
같은 그룹 안 consumer = partition 을 나눠 받음. partition 한 개는 한 consumer 에게만.
여러 그룹 = 독립
Topic "orders"
├─ Group "real-time-analytics" → 모든 메시지 처리
├─ Group "ml-training" → 모든 메시지 처리
└─ Group "data-warehouse" → 모든 메시지 처리
각 그룹이 독립적. 같은 메시지를 여러 시스템이 다른 목적으로 처리.
핵심 규칙
- partition ≥ consumer — partition 보다 많은 consumer 는 놀고 있음
- partition < consumer — 일부 consumer 가 여러 partition
- partition 안 메시지는 한 consumer 가 순차 처리 → 순서 보장 (partition 안에서만)
Rebalance — 운영의 까다로운 영역
트리거
다음 이벤트가 생기면 모든 consumer 가 잠시 멈춤 + 다시 분배 가 발동돼요. consumer 가 그룹에 추가 / 제거 되거나, consumer 가 죽었을 때 (heartbeat(생존 신호) timeout), 토픽의 partition 수 변경, consumer 가 지정 topic 변경 같은 경우.
비용
여기서 시험 함정이 하나 있어요 — Rebalance 동안 모든 consumer 가 멈춤 ("stop-the-world"). 수 초~수십 초 가능. 빈번하면 consumer lag(처리 지연 누적) 폭증.
큰 환경에서 rebalance storm — 한 consumer 죽음 → rebalance → 또 다른 consumer 타임아웃 → 또 rebalance → 무한 루프.
완화
session.timeout.ms충분히 크게 (기본 45초, 운영 환경 60~90초)heartbeat.interval.ms적당히 (session timeout 의 1/3)max.poll.interval.ms— poll 사이 최대 간격 (기본 5분, 처리 길면 늘림)- Static Membership (아래 깊이)
Static Membership — Rebalance 회피
Kafka 2.3+ 도입. consumer instance 가 persistent ID 가짐.
spring:
kafka:
consumer:
properties:
group.instance.id: "worker-1" # 고정 ID
장점:
- 재시작·rolling deploy 시 같은 ID 로 재참가 → rebalance 안 일어남
- partition 이 그대로 재할당 → 상태 큰 application 의 복구 시간 0
- 큰 Kafka Streams 앱 에서 매우 유리
설정 시 유의 — 모든 consumer 에 unique group.instance.id 를 부여하고, 중복되면 FencedInstanceIdException 으로 강제 종료돼요.
대규모·상태 큰 환경 = 반드시 Static Membership.
Long Polling 동작
Consumer.poll(Duration.ofMillis(1000))
- 데이터 있으면 즉시 반환
- 데이터 없으면 1초까지 대기 후 빈 결과 반환
내부 동작 = fetch.max.wait.ms (broker 측 long poll) + poll() (consumer 측 wait).
자주 헷갈리는 자리 — poll(timeout) 의 timeout 은 consumer 측 최대 대기. 실제 데이터 가져오기는 broker 측 long poll 도 거침.
Offline Data Load — Batch Consumer
Kafka 의 영구 저장 덕분에 batch consumer 가 자연스러워요. Hadoop(분산 처리 프레임워크) 의 map task 가 각 partition 을 병렬 처리 하고, 실패해도 원래 offset 부터 재시작 이라 중복이 없음.
Real-time + Batch 동일 Kafka 에서 처리.
한계·실무 함정
1. partition 수 = consumer 한계
partition 10개 = consumer 최대 10명. 더 늘려도 효과 없음. 처음 partition 수 설계 가 중요.
2. Rebalance Storm
위에서 강조. session.timeout 크게 + Static Membership.
3. Offset commit 타이밍
84편의 at-least-once vs at-most-once — process 전 commit (loss) vs process 후 commit (duplicate). at-least-once + 멱등성 이 일반적.
4. partition 안 순서만 보장
전체 topic 순서가 필요하면 partition 1개 — 처리량 X. 비즈니스 key 별 순서 만 필요하면 같은 key 사용.
5. Consumer 가 slow processing 으로 lag 폭증
처리 시간 > 메시지 들어오는 속도 → lag 무한 증가. consumer 수 늘리기 + partition 늘리기 또는 batch 처리 효율화.
시험 직전 한 번 더 — Kafka Consumer 함정 압축 노트
- Kafka 는 Producer push + Consumer pull 비대칭 모델
- Pull 장점 = consumer 자기 속도, aggressive batching
- Pull 함정 = busy polling → Long Polling 으로 해결
fetch.max.wait.ms(broker) +poll(timeout)(consumer) 조합- 전통 메시지 큐 = broker 가 각 메시지 ACK 상태 관리 → 무거움
- Kafka = consumer position = 단일 offset 정수 per partition
- 주기적 checkpoint (commit) 만으로 충분
- Rewind 가능 = 임의 offset 으로 되감기, 재처리·과거 학습
- Consumer Group = 같은 그룹 안 partition 나눠 받음
- 다른 그룹 = 독립적으로 모든 메시지
- partition ≥ consumer 가 원칙 (partition < consumer = 놀고 있음)
- partition 안 순서만 보장
- Rebalance 트리거 = consumer 추가/제거/사망, partition 변경
- Rebalance 동안 = stop-the-world (수 초~수십 초)
- Rebalance Storm = 잦은 rebalance 무한 루프
- 완화 =
session.timeout.ms·heartbeat.interval.ms·max.poll.interval.ms적절히 - Static Membership (Kafka 2.3+) =
group.instance.id고정 - 재시작·rolling deploy 시 = rebalance 안 일어남
- 대규모·상태 큰 환경 = Static Membership 필수
- Batch consumer 도 자연스러움 (Hadoop·DW)
- 함정 — partition 수가 consumer 한계
- 함정 — Rebalance Storm
- 함정 — Offset commit 타이밍 (process 전 vs 후)
- 함정 — slow processing 으로 lag 폭증
공식 문서: Kafka Design — The Consumer 에서 자세한 사양을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 82편 — Kafka Quickstart (5분 hands-on · topic·producer·consumer)
- 83편 — Kafka Design: Motivation (왜 이렇게 설계됐나)
- 84편 — Kafka Design: Persistence (디스크가 빠르다)
- 85편 — Kafka Design: Efficiency (Zero-Copy · Batch 압축)
- 86편 — Kafka Design: Producer (Partition 선택·ACK·Idempotent)
다음 글:
- 88편 — Kafka Message Delivery Semantics (at-most·at-least·exactly-once)
- 89편 — Kafka Replication (ISR · Leader Election · Unclean)
- 90편 — Kafka API 5종 종합 (Producer · Consumer · Streams · Connect · Admin)
- 91편 — Kafka Producer API 깊이 (Serializer · Callback · Interceptor)
- 92편 — Kafka Consumer API 깊이 (Commit · Rebalance · Seek)