Kafka 메시지 순서 보장 확인해보기
토이 프로젝트

Kafka 메시지 순서 보장 확인해보기

모든 소스 코드는 https://github.com/lkimilhol/kotlin-kafka-toy 에서 확인 가능합니다.

 

오늘의 고민 내용은 카프카를 통하여 메시지를 순서대로 받을 수 있을까 입니다.

사실 이는 가능한데요. 카프카의 파티션을 하나만 사용한다면 어렵지 않게 순서를 보장 할 수 있습니다.

하지만 이는 카프카의 성능을 100% 발휘 할 순 없는 구조라고 생각됩니다. 

 

천천히 고민해 보도록 하겠습니다.

1. 성능을 어떻게 보장할까?

카프카의 파티션과 컨슈머를 생각해보겠습니다.

아래 사진은 파티션 3개가 컨슈머 3개와 1:1로 매핑되어 있는데요. 카프카의 프로듀서(producer)가 키를 지정하여 각 파티션으로 프로듀스 되었고, 컨슈머가 이 토픽을 컨슘하여 동작하는 방식입니다. 

사진은 우테캠 프로 동기분이 그려주셨습니다. 감사합니다!

저 사진에서 파티션이 1개이고, 컨슈머도 1개라면 메시지의 순서는 보장 가능하지만, 파티션과 컨슈머가 하나라면 아무래도 성능상으로 부족 함이 있을 수 있겠는데요. 이를 보완하기 위해 우리는 각 파티션에 지정된 키로 토픽을 프로듀스하여 컨슈머가 이를 처리한다면 하나의 키에 대해선 계속해서 같은 파티션을 선택하게 될테니 순서를 보장 할 수 있지 않을까요? 이를 한번 구현해 보도록 해보겠습니다.

 

2. 구현

일단 카프카를 파티션1개로 실행시켰습니다. 한번 순서있는 메시지를 전달해보겠습니다.

 

ProducerApplication.kt

fun main(args: Array<String>) {
    val map = mutableMapOf<String, String>()
    map["key.serializer"]   = "org.apache.kafka.common.serialization.StringSerializer"
    map["value.serializer"] = "org.apache.kafka.common.serialization.StringSerializer"
    map["bootstrap.servers"] = "localhost:9092"
    var producer = KafkaProducer<String, String>(map as Map<String, Any>?)

    for (i in 1..10) {
        var producerRecord : ProducerRecord<String, String> = ProducerRecord("test", "key", "test: $i")
        var future: Future<RecordMetadata> = producer.send(producerRecord)!!
        future.get()
    }
}

간단하게 메시지를 보내는 코드입니다. test에 $i 변수를 조합하여 순서를 명시했습니다. 결과는 어떨까요?

순서대로 메시지가 잘 도착했네요. 이는 파티션이 1개이고 컨슈머도 1개이기 때문에 가능한것으로 생각됩니다. 

같은 키에 대해서 파티션이 2개인 경우에는 순서가 꼬여서 메시지를 컨슘하게 될까요? 마찬가지로 동일한 결과가 나옵니다. 파티션이 2개로 나뉜다고 하더라도 같은 키에 대해서는 같은 파티션을 사용하게 될테니까요. 그렇다면 키를 바꿔보면 어떨까요? 코드를 조금 수정하여 테스트해보겠습니다.

그리고 파티션 개수를 2개로 하였습니다. 아래와 같은 결과를 확인했습니다.

파티션이 2개이고 컨슈머가 1개인 경우에 순서가 일정한 것을 볼 수 있습니다. 앞에 있는 숫자가 key 이기 때문에 각 키에 대해서는 순서가 일정함을 볼 수 있는데요. 파티션이 1개 컨슈머가 2개인 경우에는 어떻게 될까요?

 

이 경우에는 컨슈머 하나는 실행이 되지 않습니다. 즉 파티션의 개수보다 컨슈머의 개수가 많다면 실제로는 컨슈머 하나는 idle 상태가 되는 것이겠네요. 자원 낭비로 볼 수 있겠습니다.

 

3. 컨슈머 개수 == 파티션 개수

컨슈머 개수를 2개로 해보도록 하겠습니다.

 

SampleTopicListener.kt

@Service
class SampleTopicListener {
    val log: Logger = LoggerFactory.getLogger(SampleTopicListener::class.java)

    @KafkaListener(topics = ["test"])
    fun consume(@Payload data: String) {
        log.info("Message1: $data")
    }

    @KafkaListener(topics = ["test"])
    fun consume2(@Payload data: String) {
        log.info("Message2: $data")
    }
}

코드는 위와 같습니다. 예제를 위해 작성한 코드라 조금 엉성하지만 실행을 시켜보겠습니다.

컨슈머가 파티션에 맞게 1:1 매핑이 된거로 확인되는데요. 결과가 어떻게 될까요? 우리가 예상 할 수 있는 것은 메시지1과 메시지2에서 순서대로 (아마 메시지1은 홀수, 메시지2는 짝수) 출력이 될 것이다 인데요. 결과가 어떨까요?

보시는 바와 같이 메시지1, 2 모두 순서대로 출력이 되는 것을 볼 수 있습니다. 이로써 저희가 궁금했던 컨슈머와 파티션의 개수가 동일 할 때 순서를 보장 할 수 있을까에 대한 궁금증이 해결되었네요.

 

결국 같은 키에 대해서는 순서를 보장 할 수 있다 라고 생각 할 수 있겠습니다.

4. 마치며

카프카를 직접 사용하면서 예상한 플로우대로 동작하는지 검증을 해보았는데요. 이로써 카프카에 순서를 보장 할 수 있도록 하는 방법에 대해 궁금증이 조그마나 풀렸습니다. 카프카에 대해서 공부하고 알아보고 있는 만큼 제가 생각치 못한 부분에 대해서 계속해서 테스트를 진행해봐야겠네요. 혹시라도 코드, 이론에 이상한 부분이 있다면 코멘트를 꼭 달아주세요! 감사합니다 :)

'토이 프로젝트' 카테고리의 다른 글

Kafka offset에 대해서  (0) 2021.12.05
MongoDB를 활용하여 Repository 구현  (0) 2021.08.11
Spring-data-mongodb + docker 사용해보기  (0) 2021.08.08