Kafka CommitFailedException 무한 리밸런스 5대 원인별 진단·해결 가이드
새벽에 알람이 울립니다. 컨슈머 랙이 폭증하고, 로그에는 같은 메시지가 끝없이 찍힙니다.
org.apache.kafka.clients.consumer.CommitFailedException:
Commit cannot be completed since the group has already rebalanced
and assigned the partitions to another member. This means that the
time between subsequent calls to poll() was longer than the configured
max.poll.interval.ms, which typically implies that the poll loop is
spending too much time message processing."분명 어제까지 멀쩡하던 컨슈머가 왜?" 이 글은 지금 장애 중인 엔지니어가 위에서부터 따라 하면 복구되도록 액션 중심으로 정리했습니다.
도대체 무슨 일이 벌어지고 있나: 무한 루프의 정체
CommitFailedException은 단독 에러가 아니라 악순환의 한 단면입니다. 흐름을 그려보면 이렇습니다.
poll() 호출 → 한 배치 가져옴(예: 500건)
↓
처리 지연(DB·외부 API 등으로 max.poll.interval.ms 초과)
↓
코디네이터가 "이 멤버 죽었네" 판단 → 그룹에서 추방
↓
처리 끝나고 commit 시도 → "이미 리밸런스 됨" → CommitFailedException
↓
오프셋 커밋 실패 → 같은 메시지를 다른 멤버가 다시 받음
↓
재할당(리밸런스) 발생 → 다시 poll → 또 처리 지연 → 반복 ♾️핵심은 커밋이 안 되니 오프셋이 전진하지 못하고, 같은 구간을 계속 다시 처리하면서 랙이 줄기는커녕 더 쌓인다는 점입니다. 리밸런스가 도는 동안 컨슈머는 메시지를 거의 처리하지 못하므로, 처리량은 0에 수렴하고 랙 그래프는 우상향합니다.
리밸런스를 부르는 5대 원인: 내 상황은 어디인가
대부분의 사고는 아래 5가지로 좁혀집니다.
| # | 원인 | 이런 증상이면 이 원인 |
|---|---|---|
| ① | 처리 시간 초과 — max.poll.interval.ms 안에 다음 poll() 못 부름 | 에러 메시지에 "longer than max.poll.interval.ms" 문구가 그대로 등장 |
| ② | poll 배치 과다 — max.poll.records가 커서 한 배치가 오래 걸림 | 한 번에 수백~수천 건씩 가져오고, 배치당 처리 시간 들쭉날쭉 |
| ③ | 세션 타임아웃 — session/heartbeat 미스매치로 하트비트 끊김 | Attempt to heartbeat failed, 처리량은 멀쩡한데 간헐적 추방 |
| ④ | 멤버십 변동 — 배포·오토스케일·OOM 재시작 | 배포 직후나 스케일 이벤트 시점에만 리밸런스 집중 |
| ⑤ | 스태틱 멤버십 미사용 — 롤링 재배포마다 전체 리밸런스 | 무중단 배포할 때마다 전 파티션이 한 번에 흔들림 |
①②는 "처리가 무겁다", ③은 "설정 미스매치", ④⑤는 "멤버십 운영" 문제로 묶으면 머릿속이 정리됩니다.
원인을 데이터로 특정하기: 진단 3단계
추측하지 말고 로그와 메트릭으로 범인을 지목합니다.
1단계 — 컨슈머 로그 키워드 검색
Member ... sending LeaveGroup → 클라이언트가 스스로 나감(처리 지연 의심, ①②)
was removed from the group → 코디네이터가 추방(세션/처리 초과, ①③)
Attempt to heartbeat failed → 하트비트 끊김(③)
Revoke previously assigned ... → 리밸런스 실제 발생 시점 확인2단계 — 핵심 메트릭
records-lag-max(consumer-lag): 랙이 우상향인지 확인rebalance-rate-per-hour: 시간당 리밸런스 횟수. 한 자릿수 이상이면 비정상rebalance-latency-avg: 한 번의 리밸런스가 얼마나 오래 멈추는지commit-rate: 커밋이 실제로 일어나는지(0에 가까우면 커밋 실패 중)
3단계 — 처리 시간 분포 측정
한 배치 처리의 p99 지연을 측정해 max.poll.interval.ms와 비교합니다. p99가 인터벌의 절반을 넘으면 위험 신호입니다.
MSK는 CloudWatch의
MaxOffsetLag,EstimatedMaxTimeLag지표와 브로커 로그(필요 시 오픈 모니터링/Prometheus)로, Confluent Cloud는 Control Center의 Consumer Lag·Rebalance 패널에서 동일 지표를 봅니다.
진단 흐름은 이렇게 정리됩니다.
랙 우상향? → 예 → commit-rate ≈ 0? → 예 → 에러에 max.poll.interval.ms 문구?
├ 예 → 처리 p99 > 인터벌? → 예: 원인① / 아니오: 배치 큼 → 원인②
└ 아니오 → heartbeat failed 로그? → 예: 원인③
배포/스케일 직후에만? → 원인④ / 롤링마다 전체 흔들림? → 원인⑤원인별 복붙 가능한 처방
①② 처리 시간·배치 과다
max.poll.interval.ms는 한 배치 최악 처리 시간(p99)에 여유 2배로 잡는 게 공식입니다.
# 산정 공식: max.poll.interval.ms ≈ (배치 p99 처리시간) × max.poll.records × 2
# 예) 건당 p99=20ms, 100건 → 2초 × 2 = 4초 여유 → 넉넉히 300000(5분) 유지하되 배치를 줄임
max.poll.records=100 # 500 → 100으로 단계적 축소
max.poll.interval.ms=300000 # 처리가 정말 길면 늘리되, 좀비 멤버 잔류 주의
fetch.max.bytes=5242880배치를 무작정 키우기보다 줄여서 한 사이클을 가볍게 만드는 게 정석입니다.
③ 세션·하트비트 설정
session.timeout.ms는 heartbeat.interval.ms의 약 3배가 권장 비율입니다.
heartbeat.interval.ms=3000 # 하트비트 주기
session.timeout.ms=10000 # heartbeat의 약 3배 (group.min/max.session.timeout.ms 범위 내)④⑤ 스태틱 멤버십 + CooperativeStickyAssignor
롤링 배포 때마다 전체 리밸런스가 도는 문제는 스태틱 멤버십과 협력적 리밸런싱으로 해결합니다.
# 인스턴스마다 고정·유일한 ID (Pod명/ordinal 등)
group.instance.id=consumer-order-0
# 스태틱 멤버는 세션 타임아웃 안에 재접속하면 리밸런스 생략 → 약간 넉넉히
session.timeout.ms=45000
# Eager → Cooperative 전환: 멈춤 없는 점진적 재할당
partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignorCooperativeStickyAssignor 롤링 마이그레이션 절차 (Eager에서 한 번에 바꾸면 안 됩니다):
- 1차 배포:
partition.assignment.strategy에 두 개를 동시 등록 →[CooperativeStickyAssignor, RangeAssignor]형태로 기존 전략을 함께 둠 - 모든 인스턴스가 1차 배포 완료될 때까지 대기(혼합 상태 안전)
- 2차 배포: 리스트에서 기존 Range/Sticky를 제거하고 Cooperative만 남김
- 이후부터는 리밸런스 시 전체 멈춤 없이 변경된 파티션만 이동
멱등 처리 + 수동 커밋 (commitSync)
오토커밋은 "처리 안 됐는데 커밋"되는 유실 위험이 있습니다. 수동 커밋 + 멱등 처리가 안전합니다.
Properties props = new Properties();
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 100);
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(List.of("orders"));
try {
while (running) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
for (ConsumerRecord<String, String> record : records) {
// 멱등 처리: 이미 처리한 키면 skip (예: 처리 이력 테이블/Redis)
if (alreadyProcessed(record.key(), record.offset())) continue;
handle(record); // 비즈니스 로직
markProcessed(record); // 처리 마킹
}
// 배치 끝에 동기 커밋 → 리밸런스와 충돌 시 즉시 인지
consumer.commitSync();
}
} catch (CommitFailedException e) {
log.warn("이미 리밸런스됨 → 다음 배치에서 멱등 재처리로 흡수", e);
} finally {
consumer.close(); // 스태틱 멤버라도 graceful close로 깔끔하게
}무거운 처리는 poll 루프 밖으로
가장 효과 좋은 근본 처방은 무거운 작업을 별도 워커 스레드/큐로 분리해 poll 루프를 항상 가볍게 유지하는 것입니다. 처리가 느려지면 consumer.pause(partitions)로 백프레셔를 걸고, 워커가 비면 resume() 하세요. 이렇게 하면 처리 시간과 poll 주기가 분리되어 ①②가 구조적으로 사라집니다.
실무 경험 한 토막
제가 겪은 사고의 70%는 "랙 났으니 max.poll.records를 더 키우자"는 반대 방향 처방에서 악화됐습니다. 배치를 키우면 한 사이클이 더 길어져 리밸런스를 더 부릅니다. 항상 배치는 줄이고, 무거운 일은 루프 밖으로 빼는 게 정답이었습니다. 그리고 Kafka 3.x 이후 CooperativeStickyAssignor 전환과 스태틱 멤버십만 적용해도 배포 시 멈춤이 체감상 90% 줄었습니다. 차세대 프로토콜인 KIP-848(브로커 주도 리밸런싱)이 4.x에서 안정화되면 클라이언트가 떠안던 리밸런스 비용이 더 줄어들 전망이니, MSK Serverless/Confluent Cloud 같은 매니지드 환경을 쓴다면 지원 프로토콜 버전을 미리 확인해 두세요.
결론: 리밸런스 루프 탈출 5단계 체크리스트
- 긴급 완화:
max.poll.records를 절반으로 낮춰 재배포 → 한 사이클 단축 - 진단: 로그 키워드 +
rebalance-rate-per-hour·commit-rate로 원인 ①~⑤ 특정 - 설정 정합화:
session.timeout.ms = heartbeat.interval.ms × 3, 처리 p99 기반 인터벌 산정 - 구조 개선: 수동 커밋 + 멱등 처리, 무거운 작업 워커 분리
- 근본 안정화: 스태틱 멤버십 + CooperativeStickyAssignor 롤링 적용,
rebalance-rate알람 설정
자주 묻는 질문 (FAQ)
Q. 오토커밋(enable.auto.commit=true)만 켜면 해결되나요? A. 아니요. 오토커밋은 CommitFailedException을 숨길 뿐 처리 지연이라는 근본 원인은 그대로입니다. 오히려 처리 안 된 오프셋이 커밋돼 메시지 유실이 생길 수 있습니다. 수동 커밋 + 멱등 처리가 안전합니다.
Q. 파티션을 늘리면 리밸런스가 줄어드나요? A. 처리량 분산엔 도움이 되지만, 파티션 증가 자체가 리밸런스를 유발하고 컨슈머가 부족하면 효과가 없습니다. 먼저 한 배치 처리 시간을 줄이는 게 우선입니다.
Q. 스태틱 멤버십과 오토스케일은 충돌하나요?
A. 충돌하진 않지만 group.instance.id가 인스턴스마다 고정·유일해야 합니다(StatefulSet ordinal 등). 스케일아웃으로 새 ID가 들어오면 그때는 정상적으로 한 번 리밸런스가 발생하며, 스케일인 시에는 session.timeout.ms만큼 좀비 멤버가 남을 수 있으니 graceful shutdown으로 LeaveGroup을 보내세요.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...