본문 바로가기

IT

[Kafka] Producer, Consumer 설정에 따른 partition과의 상호작용 방식 (2023.10.10)

1. 서론

기본적으로 kafka는 순서제어가 되는 것으로 알려져 있다. 그러나, 특정 topic에 대해 consume 하였을 때 partitioning 전략에 따라 순서 제어가 잘 되지 않는 케이스가 발생 할 수 있다. 이러한 케이스를 방지하고 상황에 맞게 데이터를 consume하기 위해서는 내부 동작에 대해 이해하고 record가 어떤 파티션에 저장되는지에 대해서 어느정도의 지식이 필요하다고 판단이 되었다.

 

 

2. Kafka Partition에 대한 이해

 

zookeeper 기반 kafka 그림
 

Kafka는 topic이라는 단위를 통해 여러개의 record를 저장한다. partition이 하나일 경우에는 FiFO 형태로 일반적인 rabbitmq와 유사하게 record가 적재된다. 그러나 topic이 여러개의 partition으로 구성 될 경우, 어떤 partition에 적재되고 이를 어떤 순서로 consume하냐에 따라서 데이터의 순서가 틀려질 수 있기 때문에 주의해야한다.

 

 

3. Kafka에서 지원하는 Partition Assignment Strategy

3.1. Producer

파티션 전략에 대한 설명에 앞서 producer의 동작방식에 대해 먼저 이야기를 해보고자 한다.

 

 

Producer는 크게 Accumulator, network thread로 나뉘어져 있다.

  • Accumulator : Cluster로 전송 할 kafka record(batch record)를 임시로 memory에 저장하고 있는 역할을 한다. 이 때, 이 크기는 buffer.memory에 의해 결정된다. (default : 32MB)
    • batch record의 경우, 최대 크기가 batch.size에 결정되며 linger.ms에 의해 최대 batch.size(default : 16KB)에 도달하기에 까지 기다릴 수 있는 시간을 세팅할 수 있다.
  • Network Thread : Accumulator에 쌓인 record를 cluster로 전송하는 역할을 한다. broker로 전송될 때, max.request.size(default : 1MB)에 의해 한번의 전송 시 크기가 결정되며, batch.size(default 기준 1 req = 16 batch record)에 따라 1 batch record의 크기가 달라져야한다.
    • message를 전송시, ack packet를 받는 option을 선택할 수 있으며, 상세 내용은 아래와 같다.
      • -1 : producer가 보낸 message에 대해 leader, follwer partition 단위까지 복제가 잘 되었는지 확인한다.
      • 0 : producer가 보낸 message에 대해 잘 받았는지에 대한 확인을 하지 않는다. 따라서 그만큼 message가 유실 될 가능성이 높다.
      • 1 : producer가 보낸 message에 대해 leader partition에 대해서 잘 받았는지 확인한다.

 

3.1.1. Range Partitioning

기본적으로 producer의 경우, key가 없으면 roundrobin 형태로 저장이 되지만, key가 있는 경우에는 murmur2 해쉬 알고리즘을 이용하여 특정 파티션에 할당한다. 즉, 같은 topic의 같은 key를 가진 record는 같은 partition에 할당이 된다. 따라서, 같은 key를 가진 record에 대해 일괄처리 하는 것에 특화되어 있다. 키 개수만큼 파티션이 비례하지 않게되면 한 파티션에 여러가지 key를 가진 record가 저장될 수 있으므로 설계에 참고한다.

 

 

3.1.2. Round Robin Partitioning

Round Robin 방식을 채택할 경우 produce되는 데이터를 순차적으로 각 partition간 cycle 형태로 돌면서 고르게 분포시키는 partitioning 전략이다. 만약 insert하고자 하는 partition이 busy한 경우에는 partition 순차적으로 적재가 되지 않을 수 있기 때문에, 이를 염두에 두자. 순서 상관없이, 여러 partition에 분산하여 저장할 때 효율을 보이는 전략이다.

 

 

3.1.3. Sticky Partitioning

batch.size와 linger.ms에 의해 설정 된 record batch를 한 파티션에 저장하는 방식이다. 하나의 batch record를 하나의 partition에 적재하게 되기 때문에, 배치단위로 record의 순서는 보장되는 방식이다. 따라서, 한 묶음 단위로 처리되어야 하는 business logic일 경우에는 이 전략이 유리할 수 있다.

 

 

3.2. Consumer

Consumer는 크게 fetcher와 coordinator, poll request로 이루어져 있다.

  • fetcher : poll이 실행 될 때, 적절한 크기의 record를 client에게 return 하기 위해 Kafka Cluster로부터 record를 요청하고 메모리 상에 미리 저장하는 역할을 한다.
    • fetch.min.byte ~ fetch.max.byte(default : 1byte ~ 50MB)의 값으로 broker에서 가져 올 데이터의 양이 결정되며, max.partition.fetch.bytes(default : 1MB)에 의해 하나의 partition에서 가지고 올 수 있는 데이터의 최대 크기가 정해진다.
    • fetch.min.byte에 도달하기 까지 fetch.max.wait.ms만큼 기다릴 수 있다.
  • coordinator : Kafka Cluster의 Coordinator(zookeeper, kraft)와 통신하여 consume 전략, offset commit, consumer group join, heartbeat 등을 수행한다.
  • poll : 구조적으로 나누기는 애매하지만, 여러 설정값에 의해 돌아가기 때문에 이 섹션에서 소개하며, 동작은 아래를 참고한다.
    • max.poll.records(default : 500)에 의해 fetcher로 부터 가져 올 record 수가 정해진다.
    • max.poll.interval.ms : poll 요청을 받은 메시지를 처리할 때 까지 최대 기다릴 수 있는 시간. 이 시간이 초과한다면, 해당 consumer는 rebalancing 될 때 consumer group에서 제외되상이 된다.

3.2.1. Range Assigner

 

 

3.2.2. Round Robin Assigner

Message를 Consume시, 여러 partition에 분산된 message를 round robin 형식으로 partition과 consumer group안의 consumer와 매핑시켜 최대한 분산하여 처리할 수 있도록 하는 방식이다. 그러나 consumer가 추가되거나 삭제 되어 consumer group rebalancing이 일어나게 되면 전체 consumer, partition에 대해 재할당 작업이 일어나기 때문에 이 점에 대해서 유의해야한다.

 

 

3.2.3. Sticky Assigner

Round Robin Assigner와 거의 유사하지만, rebalancing 때 조금 더 나은 효율을 보인다. Round Robin Assigner에서 rebalancing이 일어나게 되면 모든 consumer와 partition에 대해서 재설정되지만, sticky assigner를 사용하게 되면 문제가 생긴 consumer가 구독하던 topic에 대해서 상대적으로 consume 하는 partition이 적은 쪽으로 rebalancing이 일어나기 때문에 round robin에 비해서 상대적으로 할당 불균형이 일어날 가능성이 적다.

 

4. 내가 속해있는 팀이 적용해야할 전략에 대한 개인적인 고민

팀에서 사용하는 데이터는 raw한 형태의 파형 데이터이다. 이러한 데이터를 하나의 message로 실어 보내도 되지만 아래와 같은 문제가 있을 것으로 판단하였다.

  • 큰 message를 한번에 보낼 때에 대한 network 부하
  • 큰 message를 kafka cluster 내에 적재하고, 복제하는 속도가 느림
  • 적재 및 복제가 느려짐에 따라 ack를 producer 측에 보내는 속도가 느려짐에 따라 상호 네트워크 대기시간이 길어짐

만약 꼭 raw 파형 데이터를 kafka에 전송해야한 하는 상황이라면 큰 message를 잘게 쪼개서 보내야한다. 이 상황은 마치 여러개의 완성본 퍼즐에 대한 조각들이 한데 섞여서 다시 조립해야하는 상황과 같다. 잘게 쪼개서 다시 조립할 때 아래 요소를 생각해야한다.

 
  • 쪼개진 파형 데이터가 어떤 데이터의 원형인가
  • 쪼개진 파형 데이터가 조립될 때, 파형 데이터의 어느 시점 데이터인가

위 요소를 생각했을 때, 몇가지 솔루션을 생각할 수 있다.

 

4.1. Round Robin을 이용한 chunk message 처리

round robin을 이용하면 특정 파티션에 몰리지 않고 골고루 분산 되기 때문에, 내부적인 저장 효율성은 좋을 수 있다. 그러나 분산되어 저장되어 있고, 무조건 파티션 순서대로 저장되는 것이 아니기 때문에 consume하였을 때 저장된 순서대로 데이터를 받지 못할 수 있다. 따라서 consumer 구성시, consumer group을 이루고 있는 데이터들이 message를 조립하기 위해 chunk를 공유해야하는 상황이 올 수 있다. 따라서 이 방식은 적절하지 않을 수 있다고 생각이 들었다.

 

 

4.2. sticky partition with batch message 을 이용한 chunk message 처리

sticky 전략을 사용하게 되면 batch message 단위로 메시지를 보낼 수 있다. 이 전략을 사용할 경우, round robin보다는 분산 저장 쪽으로 약할 수 있겠지만, 하나의 batch 단위로 묶은 message들에 대해서는 순서가 보장이 되기 때문에, consumer group 에서도 하나의 consumer가 하나의 message를 처리할 수 있도록 유도할 수 있다. 따라서 chunk message를 처리할 경우, 이 전략을 사용하는 것이 가장 적잘하다고 생각이 들었다.