Kafka 컨슈머 장애 사례: 메시지 처리 시간 초과로 인한 CommitFailedException
장애 상황
운영 중인 Kafka 컨슈머 애플리케이션에서 특정 시점에 다음과 같은 오류 로그가 발생했습니다:
org.apache.kafka.clients.consumer.CommitFailedException: Offset commit cannot be completed since the consumer is not part of an active group for auto partition assignment; it is likely that the consumer was kicked out of the group.
이 오류는 컨슈머가 메시지를 처리하는 도중 Kafka의 컨슈머 그룹에서 리밸런싱(Rebalancing)이 발생하며 나타납니다.
이로 인해, 정상적으로 처리 중이던 메시지의 오프셋을 커밋하지 못하고 메시지 처리가 중단되었습니다. 특히, 애플리케이션의 성능 저하와 메시지의 중복 처리 위험까지 겹치며 장애의 영향이 커졌습니다.
발생 개요
애플리케이션 내부 코드에서 시점 문제로 feign 호출전에 Thread.sleep(1000)를 추가하였습니다.
이로 인해 메시지 처리 시간이max.poll.interval.ms(기본값: 300,000ms)보다 길어짐.
Kafka가 해당 컨슈머를 멈춘 것으로 간주하고 그룹에서 제외(rebalance).
Kafka는 컨슈머가 주기적으로 poll()을 호출하지 않거나, Heartbeat 신호를 보내지 않으면 컨슈머를 그룹에서 제거.
제거된 컨슈머는 오프셋 커밋 권한을 잃어 CommitFailedException이 발생.
Kafka의 컨슈머 동작 이해
Kafka 컨슈머는 기본적으로 컨슈머 그룹이라는 단위로 동작합니다. 컨슈머 그룹은 다음과 같은 두 가지 중요한 제약 조건을 가집니다:
1. 파티션 할당
Kafka는 각 컨슈머에게 파티션을 나눠서 할당합니다. 컨슈머가 메시지를 처리하는 동안, Kafka는 지속적으로 이 컨슈머가 정상적으로 동작하는지 확인합니다.
2. 정해진 시간 안에 응답해야 함
컨슈머가 Kafka에 정해진 시간 동안 응답(poll())하지 않거나, 메시지 처리 시간이 지나치게 길면, Kafka는 해당 컨슈머를 그룹에서 제외하고 리밸런싱을 진행합니다.
이때, 리밸런싱 과정에서 메시지가 다른 컨슈머로 전달되며 CommitFailedException이 발생합니다.
3. 원인 분석
이번 장애의 원인을 단계별로 살펴보았습니다.
1) 메시지 처리 시간 초과
Kafka는 메시지 처리가 일정 시간(max.poll.interval.ms)을 초과하면 컨슈머를 그룹에서 제외합니다. 기본 설정은 5분(300,000ms)입니다.
이번 장애에서는 컨슈머 애플리케이션의 메시지 처리 로직 중, 비즈니스 요청을 기다리기 위해 추가된 Thread.sleep(1000)이 문제를 야기했습니다. 메시지 처리 시간이 길어지면서 Kafka가 컨슈머를 타임아웃 처리한 것입니다.
2) max.poll.records 설정
한 번에 처리해야 할 메시지 수(max.poll.records)가 너무 많았습니다. 현재 설정은 500개로, 이 모든 메시지를 처리하는 데 시간이 오래 걸렸습니다. 특히, 비즈니스 로직이 복잡한 상황에서는 이 값이 성능에 큰 영향을 미칩니다. 처리 시간이 길어질수록 Kafka는 이를 정상적으로 동작하지 않는 컨슈머로 간주합니다.
3) 리밸런싱으로 인한 그룹 탈퇴
Kafka는 컨슈머가 응답하지 않으면 리밸런싱을 통해 해당 컨슈머를 그룹에서 제거합니다. 이 과정에서 오프셋 커밋이 실패하며 CommitFailedException이 발생한 것입니다.
4. 장애 분석의 교훈
이 장애를 통해 Kafka의 중요한 동작 원리를 다시 확인할 수 있었습니다.
Kafka는 효율적인 메시지 처리를 위해 속도와 안정성의 균형을 중시합니다. 설정 값이 실제 비즈니스 로직에 맞지 않으면 컨슈머가 그룹에서 제외되거나 리밸런싱이 자주 발생할 수 있습니다.
아래는 이번 사례에서 확인된 주요 개념들입니다:
1. max.poll.interval.ms
• 컨슈머가 메시지를 처리하고 다음 poll() 호출까지 걸릴 수 있는 최대 시간.
• 기본값은 5분. 이를 초과하면 컨슈머가 그룹에서 제거됩니다.
• 해결책: 메시지 처리 시간이 이 값을 초과하지 않도록 로직을 최적화하거나, 필요하다면 이 값을 늘릴 수 있습니다.
2. max.poll.records
• 한 번에 가져오는 메시지의 수.
• 설정값이 크면 처리 속도는 높아질 수 있지만, 개별 메시지 처리 시간이 길어지면 오히려 장애를 유발할 수 있습니다.
• 해결책: 처리 가능한 적정 메시지 수로 설정을 조정합니다.
3. 리밸런싱(Rebalancing)
• 컨슈머가 그룹에서 제외되면 Kafka는 파티션을 재분배합니다. 이 과정에서 메시지 처리 중단이나 중복 처리가 발생할 수 있습니다.
• 해결책: 컨슈머의 정상적인 동작을 유지하고 불필요한 리밸런싱을 방지합니다.
5. 해결 방안
1) Thread.sleep() 제거
문제의 핵심이었던 Thread.sleep()을 제거하여 불필요한 대기 시간을 없앴습니다. 비동기 처리나 다른 논블로킹 로직으로 대체하는 것이 바람직합니다.
2) Kafka 설정 최적화
• max.poll.records 값을 줄임으로써 처리량을 컨트롤합니다.
예: 기존 500 → 100으로 조정.
• max.poll.interval.ms 값을 늘려 메시지 처리에 충분한 시간을 확보합니다.
예: 기존 5분(300,000ms) → 20분(1,200,000ms)으로 조정.
3) 메시지 처리 로직 개선
비즈니스 로직의 복잡도를 줄이고, 병렬 처리 방식을 도입하여 처리 시간을 단축했습니다.
6. 결론
이번 장애는 메시지 처리 시간 초과와 Kafka 설정의 비효율성에서 비롯되었습니다. 메시지 처리 시간을 줄이고 Kafka 설정을 조정함으로써 문제를 해결할 수 있었습니다.
Kafka 컨슈머 애플리케이션을 설계할 때는 메시지 처리 시간을 고려한 설정 조정이 중요합니다. 장애를 예방하기 위해 정기적인 모니터링과 최적화 작업을 병행해야 한다는 점도 기억해야겠습니다.
'Kafka' 카테고리의 다른 글
Kafka에서 동시에 같은 메시지 처리 시 발생한 PK 중복 오류(feat. merge로 인한 장애대응) (2) | 2024.10.18 |
---|---|
스프링 부트에서 Kafka 로그 관리하기: 불필요한 로그 숨기고 중요한 로그만 남기는 방법(feat. AppInfoParser) (1) | 2024.10.17 |
Apache Kafaka의 주요 요소 - Consumer (0) | 2022.07.15 |