<아파치 카프카 애플리케이션 프로그래밍 with 자바> 라는 책으로 카프카를 공부하고 있는데, Spring Kafka 를 이용해 프로듀서 애플리케이션을 개발하는 예제를 실습하다가 이슈가 발생했다.
예제는 단순히 Spring Kafka 에서 제공하는 기본 KafkaTemplate 을 이용해서 메시키 키가 없는 String 데이터를 토픽에 send하는 간단한 예제다.
토픽을 생성할때 파티션을 3개 생성했기 때문에 3개의 메시지를 send하면 3개의 파티션에 하나의 메시지가 적재될거라 기대했다..
그 근거는 우선 별도로 파티셔너를 지정해주지 않았기 때문에, 그리고 메시지 키 없이 값만 전송하기 때문에 DefaultPartitioner 로 동작하기 때문이다.
(책에서는 디폴트 파티셔너가 메시지 값만 있을 경우 라운드-로빈 방식으로 파티션에 적재한다고 나와있었다.)
하지만 10개의 데이터를 전송해도 계속 0번 파티션에만 적재가 되는 것이다 -_-;;
컨슈머 애플리케이션에서 컨슈머 스레드를 3개 만들어놨기 때문에 각각의 스레드가 하나씩 가져가서 처리해야하는데 2개의 스레드가 놀고 있는 상황이 발생..
카프카 클라이언트 명령어로 확인해봐도 0번 파티션에만 데이터가 적재되고..
이유를 좀 찾다가
https://www.confluent.io/blog/apache-kafka-producer-improvements-sticky-partitioner/
이 글을 보고 이해했다.
요약하면 카프카 2.4 버전 이후부터 디폴트 파티셔너가 (메시지 키가 없을 경우) 라운드-로빈 방식에서 스티키 방식으로 변경이 되었다는 뜻이다.
스티키 파티셔너가 무엇인고 하니, 3개의 파티션이 있을 경우 0번 파티션에 batch.size 옵션 값 만큼의 데이터를 전송하고 그 다음 데이터부터는 1번 파티션에 또 batch.size 값 만큼 전송하는 방식이다.
이름이 스티키인 이유도 배치가 다 채워질때까지 특정 파티션에 파티셔너를 "고정" 하기 때문인듯 하다.
내가 원하는건 스티키 방식이 아니라, 라운드-로빈이기 때문에 어떻게든 수정해보고 싶었다.
일단 내가 해결한 첫번째 방법은 디폴트 파티셔너를 명시적으로 라운드 로빈 파티셔너로 변경하는 것이다.
스프링 카프카에서 제공하는 기본 KafkaTemplate 를 사용하면 스티키 파티셔너를 사용하기 때문에 기본 KafkaTemplate 의 Config 에 파티셔너를 지정하는 옵션을 추가한 뒤 새로운 KafkaTemplate 를 생성하는 방식으로 구현했다.
ProducerFactory producerFactory = kafkaTemplate.getProducerFactory();
Map<String, Object> configs = new HashMap<>();
configs.putAll(producerFactory.getConfigurationProperties());
configs.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, RoundRobinPartitioner.class.getName());
this.kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(configs));
이렇게 하니 내가 의도한대로 잘 동작한다.
근데 뭔가 너무 억지로 구현한 느낌이 들어서 찝찝하다...
그래서 결론적으로 내가 선택한 방법은,
spring.kafka.producer.batch-size: 1
옵션을 추가한 것이다.
아무튼 책 집필 시점의 카프카 버전과 현재 최신 카프카 버전 차이에 의한 이슈였고, 덕분에 파티셔너에 대해서 조금 더 알게된 계기가 된 것 같다.
스프링 부트 3.x 로 개발하면서 발생했던 버전 차이 이슈 때문에 재미좀 봤던 기억이 있는데 오랜만에 그 재미를 느낀 기분이다.