대표적인 메시징 시스템으로 KafkaRabbitMQActive MQ가 있는데요.

각 메시징 시스템마다 장단점이 있겠지만, 하도 Kafka가 좋다하고 도입하길래 어떤 녀석인지 궁금했습니다.

Kafka에 대해 알아보니 빠르고, 안정성있게 설계가 되어있는 것을 확인할 수 있었는데요, 이 글에서 입문자 입장에서 Kafka를 이해하기 위해 나름대로 정리를 해보았습니다.


메시징 시스템에 대한 이해가 없으면 이해가 어려울 수 있으니, Kafka에 대해 알아보기 전에 간단하게 메시징 시스템에 대해 정리한 글을 참고하시길 바랍니다.




1. Kafka 아키텍쳐

Kafka가 어떻게 이루어져 있는지 큰 크림을 그려 아키텍쳐를 먼저 살펴보겠습니다.

그리고 세부적인 구조와 용어들을 이해하면서 특징들을 살펴보도록 하겠습니다.



Kafka의 클러스터를 큰 덩어리로 표현해보았습니다.

큰 집합으로 순서대로 간략하게 정리하면 다음과 같습니다.

  • Zookeeper ( Apache Zookeeper )
    • 본래 Zookeeper의 용도는 클러스터 최신 설정정보 관리, 동기화, 리더 채택 등 클러스터의 서버들이 공유하는 데이터를 관리하기 위해 사용됩니다.
      • 복잡하니까 그냥 Broker에 분산 처리된 메시지 큐의 정보들을 관리한다고만 이해합시다...
    • 클러스터를 관리하는 Zookeeper 없이는 Kafka 구동이 불가능합니다.
      • 즉, Kafka 서버를 가동하려면 Zookeeper를 먼저 가동해줘야 합니다.( 그래서 Kafka 다운로드시 Zookeeper도 함께 제공해줍니다.)
  • Broker
    • Kafka Server를 의미합니다.
    • 한 클러스터 내에서 Kafka server를 여러 대 띄울수 있습니다.
  • Topic
    • 메시지가 생산되고 소비되는 주제입니다.
      • 예를 들어, 카톡 단체방 A, B가 있는데, A 방으로 보낸 메시지가 B 방에 노출되면 안되겠죠? A 방에서 생산된 메시지는 A 방에 존재하는 사람들(구독한 사람)에게만 보여져야 합니다.
    • 주제에 따라 여러 topic을 생성하면 됩니다.
      • ex) email topic, sms topic, push topic ...
  • Partition
    • Topic 내에서 메시지가 분산되어 저장되는 단위입니다.
      • 한 Topic에 Partition이 3개 있다면, 3개의 Partition에 대해서 메시지가 분산되어 저장이됩니다.
      • 이 때 Queue 방식으로 저장되므로 Partition의 끄트머리에 저장이 되어 Partition 내에서는 순서를 보장해주지만, Partition끼리는 메시지 순서를 보장해주지 않습니다.
        • 그래서 Topic 내에 하나의 Partition이 존재할 때와 여러 개의 Partition이 존재할 때는 차이점이 있습니다. ( 무슨 말인지는.. 뒤에서 다룹니다 ㅎㅎ )
  • Log
    • Partition의 한 칸을 Log라 합니다.
    • Log는 key, value, timestamp로 구성됩니다.
  • Offset
    • Partition의 각 메시지를 식별할 수 있는 유니크한 값입니다.
      • 메시지를 소비하는 Consumer가 읽을 차례를 의미하므로 Partition마다 별도로 관리됩니다.
      • 0부터 시작하여 1씩 증가합니다.


Kafka Cluster를 구성하는 요소들에 대해 알아보았습니다.

다음으로 Partition에 메시지를 어떻게 기록하고 사용하는지에 대해 알아보겠습니다.





2. Producer와 Consumer Group

1) 메시지 생산/소비


  • Producer
    • Producer는 정해진 Topic으로 메시지를 기록합니다.
    • Partition이 여러 개 있을 경우, 기록 될 Partition의 선택은 기본적으로 Round-Robin 방식을 따릅니다.
      • Partition이 여러 개 있으면 병렬 처리라는 이점이 있지만, Partition 개수는 주의해서 잘 설정해줘야 합니다.
    • 각 Partition 내에서는 가장 마지막 offset 뒤에 신규 메시지가 저장되므로, Partition 내에서는 순서가 보장되며 기록이됩니다.
      • 하지만 실제 메시지가 사용되는 순서는 순서가 보장되지 않는데요, 그 이유는 Consumer의 동작 방식을 이해해야 합니다.
  • Consumer Group
    • Consumer Group은 하나의 Topic을 담당합니다.
      • 즉, Topic은 여러 개의 Consumer Group이 접근할 수 있지만, 하나의 Consumer Group은 하나의 Topic에만 접근할 수 있습니다.
    • 왜 존재하는가?
      • 1) Partition 접근하는 Consumer 관리
        • Consumer Group 내에서 Consumer 인스턴스들은 Topic내에 Partition에서 다음에 소비할 offset이 어디인지 공유하면서 메시지를 소비합니다. 그렇기 때문에 다음에 소비할 offset을 잘 관리할 수 있습니다.
          • 예를 들어 Consumer Group이 없을 경우, 하나의 Partition에 2개의 Consumer가 동시에 접근한다면 어떤 Consumer가 몇 번의 offset을 소비해야 하는지 알 수 없게 됩니다.
          • 즉, Consumer Group을 통해 하나의 Partition에는 하나의 Consumer 인스턴스만 접근할 수 있도록 관리합니다.
      • 2) offset을 공유하여 고가용성을 확보
        • Partition에는 하나의 Consumer 인스턴스만 접근할 수 있기 때문에, 특정 Consumer 인스턴스에 에러가 발생했을 시 다른 Consumer 인스턴스는 에러가 발생한 Consumer 인스턴스가 소비하던 Partition을 소비하게 됩니다.
          • 즉, Consumer가 다운될 때를 대비해 Consumer Group의 Consumer 인스턴스들은 offset을 공유하고 있으며, 이를 통해 고가용성이 확보됩니다.

개인적으로는 Kafka를 이해할 때 Consumer Group을 이해하는것이 중요하다고 생각됩니다.

이에 대한 좋은 글이 있으니 참고하시길 바랍니다. ( 링크 )




2) Partition과 Consumer의 개수

Partition은 하나의 Consumer만 접근이 가능합니다.

반대로 Consumer는 여러 개의 Partition을 소비할 수 있죠.

대량의 메시지가 Kafka에 쓰여진다고 가정해보겠습니다.


(1) Partition 1개 / Consumer 인스턴스 1

메시지가 대량으로 막 생산되고 있는데, 처리할 수 있는 Consumer가 1개 밖에 없네요.

그래서 Consumer를 늘리기로 했습니다.


(2) Partition 1개 / Consumer 인스턴스 4

Consumer를 4개로 늘렸지만, Consumer Group에서 Partition은 하나의 Consumer 밖에 접근을 못하는 구조입니다.

즉, Consumer를 늘리나 마나인 상황이 되었네요.

그래서 이번에는 Partition을 늘려보겠습니다.


(3) Partition 4개 / Consumer 인스턴스 4

Consumer는 하나의 Partition에 접근할 수 있으므로, Partition과 Consumer는 1:1 구성이 되었습니다.

이상적인 상황이네요.


(4) Partition 4개 / Consumer 인스턴스 3

잘 운영이 되다가, 갑자기 Consumer 하나가 죽어버렸습니다.

그래도 문제는 없습니다.

Consumer Group에서 offset이 공유되고 있으므로 Consumer가 하나 죽더라도 다른 Consumer가 해당 Partition에 접근하면 되니까요.


(5) Partition 3개 / Consumer 인스턴스 3

메시지가 잘 처리되고 있고, 상황을 보니 Partition을 3개로 줄여도 될 것 같습니다.

그래서 Partition을 줄이려고 했지만, Partition은 한 번 늘리면 줄일수가 없습니다.



위 상황의 결론을 말씀드리면, Partition의 개수 >= Consumer 인스턴스의 갯수를 유지하는 것이 좋습니다.

( Consumer > Partition은 불가능합니다. )

하나의 Partition에 하나의 Consumer가 담당하는 것이 좋지만 딱 맞출수는 없으므로, Consumer 수가 모자라도 상관은 없습니다.

주의할 점은 한 번 Partition을 늘리면 다시 줄일 수 없기 때문에, 처리량을 잘 고려하여 Partition과 Consumer의 개수를 선택해야 할 것입니다.





3. Consumer Design

Kafka와 마찬가지로 대표적인 메시징 시스템인 RabbitMQ, ActiveMQ 역시 분산 큐 시스템(Distributed Queue System)입니다.

분산이니까 성능이 다 빠를것 같은데.. 왜 유독 Kafka가 빠르다고 할까요?


성능이 좋으려면 소비자가 최대의 효율을 내는 것을 목표로 해야합니다.

분산처리가 된다한들, 소비자가 메시지를 처리못하면 전체적인 성능이 느려지겠죠?

즉, 메시지를 소비하는 방식에 대한 차이가 성능의 차이를 보이는 것 같습니다.


Kafka는 Single Consumer가 아닌, Multi Consumer를 염두에 두고 설계되었기 때문에 Consumer를 잘 살펴볼 필요가 있습니다.

다음은 RabbitMQ와 Kafka의 Consumer Design을 비교한 것입니다.

  • RabbitMQ
    • Message Broker가 Consumer에게 메시지를 push하는 방식
      • Broker는 Consumer의 처리여부에 관계없이 push를 하므로, 메시지 소비 속도보다 생산 속도가 빠를 경우 Consumer에 부하를 주게됩니다.
      • RabbitMQ는 DRAM을 사용하므로 buffer를 사용하지만, DRAM을 다 사용하면 disk에 저장합니다. 따라서 batch 같이 큰 작업에서는 disk로 메시지를 읽어올 경우 지연이 발생합니다.
  • Kafka
    • Consumer가 Broker로부터 메시지를 pull하는 방식
      • Consumer가 처리할 수 있을 때 메시지를 가져오므로 자원을 효율적으로 사용합니다.
      • Kafka는 애초에 메시지를 disk에 저장하고, 이미 처리한 과거의 offset으로 자유롭게 움직일 수 있으므로 batch 작업에서 자원의 낭비라던지 지연이 발생하지 않습니다.
        • 메시지를 쌓아두었다가 처리하는 batch Consumer 구현도 가능합니다.

항상 trade off가 있듯이, pull 방식에도 단점은 있습니다.

데이터가 없음에도 정기적인 polling으로 인해 자원을 낭비하는 문제인데요, 이러한 단점을 보완하기 위해 실제 데이터가 도착할 때까지 long poll 대기를 할 수 있는 parameter를 지원합니다.


자세한 정보는 공식문서를 참고하시면 좋습니다.





4. Replication

Topic을 생성할 때, --replication-factor 옵션을 부여하면 복제본(replication)을 생성할 수 있습니다.


Replication이란 Zookeeper가 leader가 되는 Partition을 정하고, Partition을 각 broker마다 복제를 하는 것을 말합니다.

이 때 leader를 복제하는 partition을 follower라 합니다.

  • leader
    • 메시지를 생산하고 소비하는 작업은 모두 leader broker에서 이뤄집니다.
  • follower
    • 나머지 follower들은 leader를 복제하기만 합니다.

이는 고가용성을 위한것이며, 혹시 leader가 죽었을 경우 follower 중 하나가 leader가 되어야 하기 때문에, follower는 leader와 싱크를 맞추고 있는 것입니다. ( In-Sync Replica, ISR )



위의 그림은 3대의 Broker Server에 대하여, topic-1에 4개의 Partition이 존재할 때, --replication-factor=2 로 설정한 것입니다.

4개의 Partition이 factor 설정 값만큼 Broker에 분배된 것을 확인할 수 있습니다.

( Zookeeper가 Partition을 골고루 분배합니다. )


예를 들어, Partition1에 메시지를 쓰는 상황일 때, leader partition이 존재하는 Broker2에서 메시지가 생산됩니다.

그리고 follower인 Broker3에 존재하는 Partition은 leader를 복제합니다. ( ISR )


만약 leader가 되는 Broker가 다운이 되면, follower가 leader로 선출됩니다.

예제는 follower가 1개지만 여러 개의 follower가 있다면, Zookeeper가 leader를 알아서 선출해줍니다.





5. 그 밖의 정보들

주제로 잡기엔 애매하고... 버리기엔 아까운 짤막한 정보들을 모아봤습니다.

  • 메시지 보존기간
    • 클러스터는 쓰여진 메시지를 보존기간동안 유지합니다.
      • 보존 기간 정책은 log.retention.hours 설정을 통해 가능하며, 기본값이 7일입니다.
      • 예를 들어, 보존 정책이 2일이면, 2일 뒤에는 공간 확보를 위해 해당 메시지를 폐기합니다.
      • 데이터 크기에 상관없이 카프카의 성능은 일정하기 때문에 장기간 저장해도 문제는 없으므로, 보존기간을 짧게 잡을 필요는 없습니다.
    • 즉, Consumer가 메세지를 소비한다고 해서 메시지가 없어지는게 아니라 보존기간이 지나야 사라집니다.
      • 그래서 Consumer가 과거의 offset에 대한 접근을 할 수 있는것입니다.
  • Partition이 1개이냐, 2개 이상이냐에 따른 메시지 순서 보장에 대한 이해 ( 참고 )




이상으로 메시징 시스템과 Kafka에 대해 알아보았습니다.

Kafka를 잘 안써봐서 잘못된 내용이 있을 수 있습니다 ^^; ( 많이 어렵네요... ㅎㅎ )

많은 참고 자료들을 읽고 공부하는 차원에서 정리한 글이니, 이를 통해 다른 더 좋은 자료를 읽는데 기반이 되었으면 합니다.

( 지적사항 및 새로 알게된 사실이 있으면 틈틈히 수정하겠습니다 )


[참고 자료]

https://kafka.apache.org/

https://kafka.apache.org/documentation/#design_pull

https://www.confluent.io/blog/hands-free-kafka-replication-a-lesson-in-operational-simplicity/

https://www.popit.kr/kafka-운영자가-말하는-처음-접하는-kafka

https://www.popit.kr/kafka-consumer-group/

https://epicdevs.com/17

https://medium.com/@umanking/카프카에-대해서-이야기-하기전에-먼저-data에-대해서-이야기해보자-d2e3ca2f3c2