본문 바로가기
kafka

Kafka - Producer 메시지 분배 전략(파티셔닝 전략)과 배치(feat. 하나의 파티션으로만 메시지가 전송된다?)

by khds 2024. 6. 6.

 

들어가기 

 

Spring boot와 Kafka를 같이 사용하는 토이 프로젝트를 진행 중이었다.

Kafka ui 툴을 통해서 메시지가 잘 전송이 되나~ 확인하려고 했는데 내 눈을 의심하였다...

어째서 파티션 하나로만 메시지가 전송된거지??

 

Kafka를 공부했을 때는 파티션을 돌아가면서 메시지가 전송된다고 했는데?

 

처음에 당혹함을 감추지 못하였지만, 찾아보니 이는 단순한 이유였다. 공부했었던 파티션에 균등하게 분배하는 것은 파티션 분배 전략 중 라운드로빈(Round-Robin) 방식이고, 특정 파티션에 레코드를 축적하다가, 일정량이 채워지면 다른 파티션으로 이동하는 것이 스티키(Sticky) 방식이라고 한다. 

스티키 방식은 파티션 전환 비용을 줄이고, 네트워크의 효율성을 높이며, 레코드의 일괄 전송(batch processing)을 최적화는 장점들 때문에 Kafka 2.4 버전 이후부터는 기본 방식이 스티키 방식이라고 한다.

나는 Kafka를 최신버전으로 실행했었고, 스티키 방식이 적용되어서 위 사진과 같이 하나의 파티션으로만 메시지가 전송되었던 것이다.

 

물론 실시간성을 보장하기 위해선 배치를 사용하지 않고 라운드 로빈 방식이 더 좋다고 한다. 그래서 설정을 라운드 로빈 방식으로 변경하고자 한다.

그렇다면 배치라는 것이 무엇이길래 파티션 전략에 영향을 미치는 것인가?

이 글에서는 두 파티션 전략(라운드 로빈 방식, 스티키 방식)의 특징과 배치라는 것이 무엇인지, 그리고 Spring boot에서 파티션 전략을 라운드 로빈 방식으로 변경하는 것은 무엇인 알아보고자 한다. 

 

Kafka에 대한 기본적인 내용은 여기를 참고하길 바란다.

https://khdscor.tistory.com/107

 

카프카(Kafka)는 어떻게 사용하는가?

들어가기 최근 모놀리식 아키텍처와 MSA( MircroService Architecture)에 대해 공부를 하였다. 모놀리식 아키텍처는 하나의 애플리케이션 안에 서비스의 모든 부분을 담아서 개발을 하는 것이고, MSA는 서

khdscor.tistory.com

 

 

본론 

1. 파티셔닝 전략과 배치

 

Kafka에서는 라운드 로빈 파티셔닝 전략, 스티키 파티셔닝 전략, 키 기반 파티셔닝 전략으로 제공되며 그 외 커스텀한 전략을 적용할 수 있다. 이 글에선 라운드 로빈 전략과 스티키 전략을 주로 살펴보겠다.

 

Producer가 메시지를 Kafka 브로커로 전송할 때 사용되는 파티셔닝 전략에 따라 파티션이 선택되고 선택된 파티션을 통해 컨슈머에게 전송된다. 만약 키가 지정되었다면, 키에 따라 파티션이 정해지고(Key-Based Partitioning), 키가 지정되지 않은 메시지는 라운드 로빈 방식 또는 스티키 파티셔닝 전략에 따라 파티션에 배정된다. 

 

라운드 로빈 방식은 각 메시지를 순서대로 각 파티션에 분배하는 방식이다. 모든 파티션에 골고루 분배가 되어, 전체적으로 고른 메시지 분포를 유지할 수 있다. Kafka 2.4 버전 이전까지 기본 전략이었다.


스티키 방식은 Kafka 2.4 버전 이후부터 도입된 기본 전략으로, '배치 최적화'를 위해 설계되었다. 일정 수의 메시지를 하나의 파티션에 쌓다가, 지정된 양이 채워지면 다른 파티션으로 전환하며 보내는 방식이다.

배치는 메시지를 하나하나씩 컨슈머에게 전송하는 것이 아니라 모아뒀다 한 번에 전송하는 것을 의미한다.

 

출처 : https://www.confluent.io/blog/apache-kafka-producer-improvements-sticky-partitioner/

 

 

Kafka의 배치 처리는 대량의 데이터를 일괄적으로 처리하는 방법을 의미한다. 이를 통해 한 번에 여러 메시지를 처리하여 시스템의 성능을 최적화하고 데이터 처리량을 향상시킬 수 있다. 설정을 추가하지 않는다면 배치는 최소화된 상태이다.


Kafka의 배치 처리에는 몇 가지 특징이 있다.


1. 네트워크 트래픽을 줄여 성능을 개선할 수 있다.
2. Consumer가 묶음으로 메시지를 받아 처리하므로 일괄작업에 최적화되어 있다.
3. 대량의 데이터 처리에 효과적일 수 있다.

 

따라서, 데이터 처리량을 늘리고 성능을 향상시키기 위해 배치 처리를 적절히 활용하는 것이 중요하다. 그러나 실시간성이 중요한 경우에는 배치 크기와 주기를 조정하여 적절히 운영해야 한다. 지정한 배치 크기만큼 Consumer에게  메시지의 지연이 발생하여 실시간성에 영향을 줄 수 있기 때문이다.

 

라운드 로빈 전략은 메시지를 순차적으로 파티션에 분배하여 각 파티션이 적절한 양의 데이터를 전송받을 수 있다. 그러나 이 과정에서 파티션마다 배치 사이즈로 지정한 메시지가 쌓일 때까지 대기해야 하는 문제가 있다. 또한 파티션이 많을 경우 메시지를 분배하기 위해 더 많은 연산을 하게 되는 문제도 생긴다.

스티키 전략을 사용할 경우 하나의 파티션부터 채워지기 때문에 라운드 로빈 전략보다 배치 사이즈를 채우는 지연 시간이 덜 소모된다는 장점이 있다. 하지만 특정 파티션에 메시지를 배치 단위로 묶어 보내기 때문에 파티션의 메시지 순서는 보장되지만, 여러 파티션에 걸쳐 메시지 순서가 혼합될 수 있다. 특히, 컨슈머 그룹이 여러 파티션을 동시에 처리할 때 이러한 문제가 발생할 수 있다.

 

이를 해결하기 위한 방법으로는 아래의 두 가지가 있다.


1. 메시지에 특정 키 값을 지정하여 동일한 파티션으로 보내는 방식(키 기반 파티셔닝 전략)
2. 컨슈머 측에서 특정 값을 인덱스로 하여 메시지 순서를 맞추는 방식

 

 

2. Spring boot에서 배치 설정

 

기본적으로 특별한 설정을 하지 않았고 Producer와 Broker 간의 네트워크 통신이 원활하면, 메시지는 거의 실시간으로 Consumer에게 도착한다. 그러나 배치를 통한 전송 최적화를 수행할 경우, 실제 메시지가 Consumer에 도달하기까지의 시간이 조금 지연될 수 있다.

배치는 다음과 같은 설정으로 제어된다.

linger.ms: Producer가 배치를 구성하기 위해 대기하는 최대 시간으로 기본 값은 0ms로 설정되어 있다.

batch.size: 배치의 크기를 결정하며 기본 값은 16384 바이트(16KB)이다.

buffer.memory: 메시지를 배치로 묶는 동안 이를 임시 저장하는 공간으로 33554432 바이트(32MB)이다. 너무 작은 버퍼는 배치 크기를 작게 만들어 네트워크 효율성을 떨어뜨리고, 너무 큰 버퍼는 메모리를 과도하게 사용하여 시스템 자원을 낭비하게 만든다.


Application Configuration(application.properties나 application.yml)에 추가 설정을 통해 배치 설정을 할 수 있다.

spring.kafka.producer.batch-size=16384 # default
spring.kafka.producer.linger-ms=0      # default
spring.kafka.producer.buffer-memory=33554432 # default

 

 

3. 라운드 로빈 전략을 사용하기(With Spring boot)

 

내가 진행하고 있는 토이 프로젝트는 실시간성을 보장해야 하기 때문에 배치를 최소화하고 라운드 로빈 전략을 사용할 것이다. 배치는 기본 값이 최소 값이므로 전략만 변경해 주면 된다. 

Spring boot는 Kafka 버전을 낮은 버전을 사용하거나 Producer 설정에(Config)에 아래와 같은 코드를 추가해 준다.( 주석으로 표시 )

@Configuration
public class KafkaProducerConfig {

    private static final String BOOTSTRAP_SERVER = "localhost:9092, localhost:9093, localhost:9094";

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVER);
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        // 파티셔닝 전략 설정
        configProps.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, RoundRobinPartitioner.class.getName());


        return new DefaultKafkaProducerFactory<>(configProps);
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

 

코드를 추가한 후 아래와 같이 라운드 로빈 방식을 파티션에 메시지가 분배된 것을 확인할 수 있다.

 

 

 

결론

 

이 글에서는 Kafka의 라운드 로빈 방식과 스티키 방식, 그리고 배치 처리에 대해 알아보았다. 

아직도 Kafka를 다루기에는 많은 어려운 부분들이 있지만, 이렇게 하나하나 익혀가야겠다.

 

잘못된 내용이 있거나 궁금한 부분은 댓글로 질문해 주시면 감사하겠습니다!

 

 

참고 

 

https://www.confluent.io/blog/apache-kafka-producer-improvements-sticky-partitioner/

 

Apache Kafka Producer Improvements: Sticky Partitioner

Apache Kafka 2.4 introduces sticky partitioning, allowing Kafka producers to assign keyless messages to partitions for data processing at lower latency.

www.confluent.io

 

https://teki.tistory.com/82https://soono-991.tistory.com/64

 

Producer

프로듀서는 카프카의 토픽으로 메시지를 전송하는 역할을 담당합니다. 위 이미지는 프로듀서의 전체적인 흐름을 나타낸 것입니다. 먼저 카프카로 전송하기 위한 실제 데이터인 ProducerRecord를 생

soono-991.tistory.com

 

실전 카프카 개발부터 운영까지 - 고승범