Kafka 이벤트 전송 최적화: Partitioner와 batch 설정 이해하기
[kt cloud 플랫폼Innovation팀 오준영]
Kafka 이벤트 전송 최적화: Partitioner와 batch 설정 이해하기
Kafka에서 Producer는 기본적으로 Sticky Partitioner를 사용하여, 이벤트를 batch 단위로 묶어 효율적으로 전송할 수 있습니다.
배치 설정은 batch.size와 linger.ms가 있으며, 각각 배치의 크기와 데이터가 쌓이는 시간을 설정해 전송 조건을 조절할 수 있습니다.
본 포스팅에서는 Kafka를 활용한 이벤트 발행과 구독하는 과정에서, 겪은 이벤트 분산 이슈의 원인을 파악하기 위해 Kafka Partitioner, batch.size, linger.ms 설정을 분석한 내용을 공유합니다.
1.시나리오
테스트 환경 (Local)
Kafka Cluster 구성, Topic & Partition 그리고 Consumer Group은 설명하기 쉽게 구성했습니다.
Kafka 환경
- 1 cluster - 3 broker (KRaft 모드)
- 1 Topic - 3 Partition - 1 Replica
AP 환경 (Producer / Consumer)
- Producer
- A Topic을 Publishing합니다. (이때, partition id / key 값은 null 입니다.)
- batch.size=16 (KB), linger.ms=0 (ms)입니다.
- Consumer Group
- Consumer Group은 3개의 Consumer로 이루어집니다.
Kafka는 3.7 버전이고 AP는 Spring Boot, Java 환경이며, Spring Kafka:3.2.4 라이브러리를 사용합니다.
다음은 Kafka를 사용하여 이벤트를 분산하고 구독하는 시나리오입니다.
예상 시나리오
- 3개의 Consumer가 서로 다른 Partition을 구독합니다.
- Producer는 linger.ms=0 이기 때문에 batch를 사용하지 않고, Kafka로 이벤트를 전송합니다.
- Producer가 다음 이벤트를 발행합니다.
- 다른 파티션으로 이벤트가 분산되어, 다른 Consumer가 이벤트를 처리합니다.
각각의 파티션으로 이벤트들이 분산되어, Consumer들은 서비스를 병렬적으로 처리할 수 있습니다.
2.이벤트 분산 실패 및 원인 분석
- “예상 시나리오”에서 다른 파티션으로 이벤트가 발송되기를 바랐으나, 지속적으로 같은 파티션으로 이벤트가 발행됩니다.
- 이벤트 분산을 원했으나, 분산되지 못하고 하나의 Partition과 Consumer에 이벤트가 몰리는 문제가 발생했습니다.
Stack Overflow에서 같은 문제를 겪는 질문의 답변을 따르면, “배치가 완료” 될 때까지 Producer가 동일한 파티션으로만 이벤트를 발행하는 것이 정상적인 동작이라는 설명이 있습니다.
하지만 linger.ms=0으로 배치를 사용하지 않도록 설정이 되었음에도, 같은 Partition으로만 발행되고 있어 Partitioner 관련 공식 문서 및 내부 코드를 추가로 분석했습니다.
Apache Kafka 문서에 따르면, partitioner.class 설정으로 다른 Partitioner로 변경할 수 있습니다. 기본 설정은 null 값이며, 기본 Partitioner는 Producer의 내부에 구현되어 있습니다.
기본으로 사용되는 Partitioner는 내부 코드 분석 결과 BuiltInPartitioner 이며, Kafka Client 라이브러리 내부에 구현되어 있습니다.
linger.ms=0 설정임에도 동일한 파티션으로만 이벤트가 발행된 원인은 BuiltInPartitioner는 batch가 full 일 때만을 기준으로 새로운 배치를 생성하기 때문입니다.
linger.ms에 의해 새로운 배치를 생성하면 불필요한 suboptimal batches를 만들기 때문에, Kafka Client 라이브러리의 3.3.0 이후부터는 [KAFKA-14156]을 반영하여 linger.ms는 새로운 배치를 만드는데 영향을 주지 않습니다.
정리하면, Producer는 배치가 완료될 때까지는 동일한 파티션으로만 이벤트가 발행되고 batch가 full 상태일 때를 기준으로 다른 파티션으로 재설정되어 이벤트를 발행합니다.
여기서 linger.ms는 suboptimal batches 문제를 일으키기 때문에, Bug fix가 되었고 새로운 배치를 만드는 데는 영향을 주지 않게 됩니다.
3.해결방안
해결 방안 중에는 Partitioner를 변경/수정하거나, batch.size 과 같은 설정을 변경하는 방법이 있습니다. 하지만 Partitioner를 변경하는 방법은 다음과 같은 문제가 있습니다.
partitioner.class 설정으로 변경 가능한 Partitioner의 종류는 Test, Mock을 제외하고 DefaultPartitioner, RoundRobinPartitioner, UniformStickyPartitioner가 있습니다.
여기서 DefaultPartitioner와 UniformStickyPartitioner는 Deprecated 되었습니다. (Partition key 값으로, 동일한 Partition에 발행된다는 것을 보장할 수 없습니다.)
나머지 RoundRobinPartitioner을 사용하여 테스트했을 때, 원하는 예상 시나리오처럼 record 단위의 이벤트 분배 해결할 수 있습니다. 하지만 Apache Kafka의 테스트 문서에 따르면, RoundRobinPartitioner는 Partition의 개수가 증가할수록 Latency 성능에 좋지 못한 결과를 보여주고 있습니다.
- Round Robin vs Sticky - ProduceBench
4.결론
이벤트가 여러 파티션으로 분배되지 않고 특정 파티션으로만 이벤트가 전송되는 문제가 있었습니다.
원인은 기본적으로 사용되는 Partitioner는 BuiltInPartitioner 때문입니다. 해당 Partitioner는 batch.size 설정에 따라 batch가 full 상태를 기준으로 새로운 파티션으로 설정되어 이벤트를 발송하며, 이때 linger.ms 설정은 suboptimal batches를 만드는 이슈가 있어 Spring Client 3.3.0 이후부터는 영향을 주지 않게 됩니다.
해결방안은 partitioner.class 설정을 통해 Partitioner를 바꿀 수 있지만, 지원하는 Round Robin 전략은 성능의 이슈를 발생시킬 수 있습니다. custom partitioner를 개발하여 성능 최적화 및 이슈를 해결할 수도 있지만, 하드웨어 성능, 네트워크 환경, 데이터의 압축 알고리즘 등 성능에 영향을 줄 수 있는 예상치 못한 문제로 추가적인 문제를 발생시킬 수 있습니다.
따라서 여러 공식적인 비교 실험과 예상치 못한 이슈들이 있기 때문에, BuiltInPartitioner를 사용하고 주기적인 메트릭 수집을 통해 batch.size와 같은 설정 변경, 서비스의 성능 개선, 파티션 id 지정 등의 방법으로 최적화를 진행하여 해결할 수 있습니다.
참고/출처
https://stackoverflow.com/questions/58132107/kafka-producer-publishing-message-to-single-partition
https://github.com/apache/kafka/pull/12570
https://www.confluent.io/blog/apache-kafka-producer-improvements-sticky-partitioner/
https://cwiki.apache.org/confluence/display/KAFKA/KIP-480%3A+Sticky+Partitioner
https://velog.io/@dobby/Kafka-Producer-Config-%EC%A0%95%EB%A6%AC