카프카(Kafka) (3) - 기본 개념과 구조

Share

Last Updated on 2월 21, 2023 by Jade(정현호)

안녕하세요. 
이번 포스팅은 카프카의 기본 개념과 구조에 대한 글로 실전 카프카 개발부터 운영까지 책을 정리한 글 입니다. 

카프카 연재글로 아래 포스팅에서 이어지는 글 입니다. 

카프카 기초 다지기

카프카의 기본적인 내용에 대해서 먼저 살펴보도록 하겠습니다.

카프카를 구성하는 주요 요소로는 아래와 같습니다.

• 주키퍼(Zookeeper): 아파치 프로젝트 애플리케이션으로 카프카의 메타데이터(metadata) 관리 및 브로커의 정상상태 점검(health check) 을 담당 합니다.

• 카프카(Kafka) 또는 카프카 클러스터(Kafka cluster) : 아파치 프로젝트 애플리케이션으로 여러대의 브로커를 구성한 클러스터를 의미 합니다.

• 브로커(broker) : 카프카 애플리케이션이 설치된 서버 또는 노드를 의미 합니다.

• 프로듀서(producer): 카프카로 메세지를 보내는 역할을 하는 클라이언트로 총칭 합니다.

• 컨슈머(consumer) : 카프카에서 메세지를 꺼내가는 역할을 하는 클라이언트를 총칭 합니다.

• 토픽(topic) : 카프카는 메시지 피드들을 토픽으로 구분하고, 각 토픽의 이름은 카프카 내에서 고유 합니다.

• 파티션(partition) : 병렬 처리 및 고성능을 얻기 위해 하나의 토픽을 여러개로 나눈 것을 의미 합니다

• 세그먼트(segment) : 프로듀서가 전송한 실제 메세지가 브로커의 로컬 디스크에 저장되는 파일을 말합니다.

• 메세지(message) 또는 레코드(record): 프로듀서가 브로커로 전송하거나 컨슈머가 읽어가는 데이터 조각을 말합니다.
             

리플리케이션

카프카에서 리플리케이션(replication) 이란 각 메세지들을 여러 개로 복제해서 카프카 클러스터내 브로커들에 분산시키는 동작을 의미 합니다.

이러한 리플리케이션 동작 덕분에 하나의 브로커가 종료 되더라도 카프카는 안정성을 유지 할 수 있습니다.

카프카 클러스터 설치 완료후 토픽을 생성하였을 때 --partition 1, --replication-factor 3 이라는 옵션을 이용하여 생성하였습니다.

토픽 명령어 중 replication-factor 옵션은 카프카내에서 몇개의 리플리케이션을 유지하는지를 설정하는 옵션 입니다.

토픽 생성시 --replication-factor 3 으로 지정하였기 때문에 원본은 포함하여 총 3개의 replication 이 있음을 의미 합니다.

위의 그림에서 test-overview01 토픽은 설정된 --replication-factor 옵션 값에 의해서 각 브로커에 배치된 상태를 보여주는 것 입니다.

그리고 조금더 정확하게 말해서 카프카에서 토픽이 리플리케이션이 된 것이 아니라 토픽의 파티션이 리플리케이션(복제)가 된 것이라고 표현할 수 있습니다.

replication-factor의 값이 높아지면 안정성이 높아지지만, 그만큼 브로커의 리소스 사용이 많아지며,복제에 대한 오버헤드가 발생될 수 있으므로 적절한 팩터수를 설정해서 사용을 해야 합니다.

테스트나 개발환경 : 리플리케이션 팩터 수 1
운영(로그성 메세지로 약간의 유실 허용가능) : 리플레키이션 팩터 수 2
운영(유실 허용안됨) : 리플레키이션 팩터 수 3
              

파티션

하나의 토픽이 한번에 처리할 수 있는 단계를 높이기 위해서 토픽을 여러 개로 나눠 병렬 처리가 가능하게 만든 것을 파티션(partition) 이라고 합니다.

이렇게 하나를 여러개 로 나누면(파티션 하면) 분산 처리가 가능해지며, 파티션 수 만큼 컨슈머를 연결할 수 있게 됩니다.

위의 그림에서 카프카 클러스터에 있는 토픽을 파티션으로 나눈 그림 입니다.

토픽1은 파티션1개로 구성 되어있고, 토픽3는 파티션0, 파티션1, 파티션2 의 총 3개의 파티션으로 구성되어 있습니다

파티션 수도 토픽을 생성할 때 옵션으로 설정하게 되며, 사실 파티션 수를 정하는 기준은 다소 모호한 경우가 많습니다. 인터넷에서는 적절한 파티션 수를 구하는 공식도 간혹 있으나 각 메세지 크기나 초당 메세지 건수 등에 따라서 달라짐으로 정확한 예측은 사실 어렵습니다.

특히 파티션수는 초기 생성 후 언제든지 늘릴 수 있지만, 반대로 한번 늘린 파티션 수는 절대로 줄일 수 없습니다.

따라서 초기에 토픽 생성시에는 파티션수를 작게 적절하게(2또는 4정도) 생성 한 후, 메세지 처리량인 컨슈머의 LAG 등을 모니터링 하면서 조금씩 늘려가는 방법이 가장 좋습니다.

여기서 컨슈머의 LAG 이란 '프로듀서가 보낸 메세지수(카프카에 남아 있는 메세지 수) - 컨슈머가 가져간 메세지 수' 를 의미 합니다.

컨슈머의 지연이 없다면(모든 메세지를 가져갔다면) 5-5=0 이 되게 됩니다.
              

세그먼트

카프카에서 전송한 메세지는 어디에 저장이 될까요?

프로듀서를 이용해 보낸 메세지는 토픽의 파티션 0에 저장되어 있습니다

이처럼 프로듀서에 의해 브로커로 전송된 메세지는 토픽의 파티션에 저장되며, 각 메세지들은 세그먼트(segment) 라는 로그 파일의 형태로 브로커의 로컬 디스크에 저장되게 됩니다.

위의 그림은 각 파티션별로 세그먼트를 나눠본 그림입니다. 각 파티션 마다 N 개의 세그먼트 로그 파일들이 존재 합니다.

카프카가 설치된 디렉토리의 logs 디렉토리로 이동하여 살펴보면 생성한 토픽 이름으로 디렉토리가 있는 것을 확인 할 수 있습니다. 해당 디렉토리 이름 끝에 "-0" 는 0번 파티션을 의미하게 됩니다.

그리고 그 디렉토리 아래에는 여러가지 파일이 있습니다

[root@kafka1]# ls -al | grep overview
drwxr-xr-x.  2 root root   4096 Apr 20 14:57 test-overview01-0

[root@kafka1]# cd test-overview01-0

[root@kafka1]# ls -alrt
total 16
-rw-r--r--.  1 root root 10485760 Apr 20 14:52 00000000000000000000.index
-rw-r--r--.  1 root root 10485756 Apr 20 14:52 00000000000000000000.timeindex
-rw-r--r--.  1 root root        8 Apr 20 14:57 leader-epoch-checkpoint
drwxr-xr-x.  2 root root     4096 Apr 20 14:57 .
-rw-r--r--.  1 root root       81 Apr 20 14:57 00000000000000000000.log
drwxr-xr-x. 20 root root     4096 Apr 20 17:41 ..


hexdump 를 보기 위해서 xxd 명령어나 strings 명령어를 이용하면 이전 테스트에서 전송한 First message 라는 메세지를 확인 할 수 있으며, 브로커의 로컬디스크에 안전하게 저장된 것을 확인할 수 있습니다.

[root@kafka1]# xxd 00000000000000000000.log
0000000: 0000 0000 0000 0000 0000 0045 0000 0000  ...........E....
0000010: 02d5 73b5 9400 0000 0000 0000 0001 8045  ..s............E
0000020: 8bb7 1f00 0001 8045 8bb7 1fff ffff ffff  .......E........
0000030: ffff ffff ffff ffff ff00 0000 0126 0000  .............&..
0000040: 0001 1a46 6972 7374 206d 6573 7361 6765  ...First message
0000050: 00       

[root@kafka1]# strings 00000000000000000000.log
First message


메세지 전송 과정을 다시 한번 정리해보면 아래와 같습니다.

1. 프로듀서는 카프카의 test-overview01 토픽으로 메세지를 전송합니다.
2. test-overview01 토픽은 파티션이 하나뿐임으로, 프로듀서로 부터 받은 메세지를 파티션0의 세그먼트 로그 파일에 저장합니다.
3. 브로커의 세그먼트 로그 파일에 저장된 메세지는 컨슈머가 읽어 가게 됩니다.
            

카프카의 핵심 기능

카프카는 여러 우수한 기업들이 서둘러서 도입하고 많은 개발자가 좋아하는 이유로는 앞에서 설명한 내용처럼 높은 처리량, 빠른 응답 속도, 안정성 때문 입니다.

이런 카프카가 어떻게 좋은지 어떻게 높은 처리량을 갖게 되었는지에 대해서 카프카의 특성과 핵심 기능에 대해서 살펴보도록 하겠습니다.
           

분산 시스템

분산 시스템은 네트워크 상에서 연결된 컴퓨터들의 그룹을 말하며, 단일 시스템이 갖지 못한 높은 성능을 목표로 구성하게 됩니다. 이러한 분산 시스템은 성능이 높다는 장점 이외에도 하나의 서버 또는 노드 등에 장애가 발생할 때 다른 서버 또는 노드가 대신 처리 하므로 장애 대응에 좋으며, 부하가 높을 경우에는 확장이 용이 하다는 장점도 있습니다.

카프카도 이와 같은 의미로 분산 시스템이므로 최초 구성한 클러스터에서 브로커를 추가 하는 방식으로 확장이 가능 하며 카프카 브로커 추가는 온라인 상태에서 매우 간단하게 추가 할 수 있습니다.

이처럼 확장이 용이 하다는 점이 카프카의 매우 큰 장점 입니다.
          

페이지 캐시

카프카는 높은 처리량을 얻기 위해서 여러가지 추가된 기능 중에 대표적으로 페이지 캐시 의 이용 입니다.

운영체제는 성능을 높이기 위해 계속 진화하고 개선되고 있으며, 그중 대표적인것이 페이지 캐시의 활용 입니다.

[https://needjarvis.tistory.com/602]

카프카 역시 OS의 페이지 캐시를 활용하는 방식으로 설계가 되어있으며 페이지 캐시는 애플리케이션이 사용하지 않는 잔여 메모리를 활용하며, 이러한 페이지 캐시를 이용하면 디스크 I/O 에 대한 접근이 줄어들므로 성능을 높일 수 있습니다.
            

배치 전송 처리

카프카는 프로듀서 , 컨슈머 클라이언트들과 서로 통신하며, 이들 사이에서 수많은 메세지를 주고 받습니다.

이때 발생하는 수많은 통신을 묶어서 처리할 수 있다면 네트워크 오버헤드를 줄일 수 있으며 더욱 빠르고 효율적으로 처리 할 수 있습니다.

예를 들어, 온라인 상품 구매 프로세스에서 상품의 재고 수량 업데이트 작업과 구매 로그를 저장소로 보내는 작업을 한번 생각해봅시다.

상품의 재고 수량 업데이트 작업은 지연 없이 실시간으로 처리 되어야 하지만, 구매 로그를 저장소로 보내는 작업은 이미 로그가 서버에 기록되어 있기 때문에 실시간 처리 보다는 배치 처리를 이용하는 것이 더욱 효율적일 것 입니다.

카프카에서는 이와 같은 장점을 지닌 배치 전송을 권장하고 있습니다.
              

압축 전송

카프카는 메세지 전송시 좀 더 성능이 높은 압축 전송을 사용하는 것을 권장 합니다.

카프카에서 지원하는 압축 타입은 gzip , snappy , lz4 , zstd 등 입니다.

압축만으로도 네트워크 대역폭이나 회선 비용을 줄일수 있으며 앞에 설명한 배치 전송과 결합해 사용한다면 더욱 높은 효과를 얻게 됩니다.

파일 하나를 압축하는 것보다 비슷한 파일 10개, 혹은 20개를 압축하는 쪽의 효율이 더욱 좋기 때문 입니다.

압축 타입에 따라서 조금씩 특색이 있으며, 일반적으로 높은 압축률이 필요한 경우면 gzip 이나 zstd 를 권장하고, 빠른 응답속도가 필요하다면 lz4 나 snappy 를 권장 하고 있습니다.
               

토픽, 파티션, 오프셋

카프카는 토픽(topic) 이라는 곳에 데이터를 저장하는데, 이는 우리가 흔히 사용하는 메일 전송 시스템에서 이메일 주소 정도의 개념으로 이해하면 쉽습니다.

토픽은 병렬 처리를 위해서 여러개의 파티션 이라는 단위로 다시 나뉘게 됩니다.

카프카에서는 이와 같은 파티셔닝을 통해 단 하나의 토픽이라도 높은 처리량을 수행할 수 있도록 되어 있습니다.

파티션의 메세지가 저장되는 위치를 오프셋(offset) 이라고 부르며, 오프셋은 순차적으로 증가하는 숫자(64비트 정수) 형태로 되어 있습니다.

[https://sookocheff.com/post/kafka/kafka-in-a-nutshell]

하나의 토픽이 총 3개의 파티션으로 되어있고, 프로듀서로 부터 전송된 메세지들은 쓰기 동작이 각 파티션 별로 이루어짐을 볼 수 있으며, 각 파티션마다 순차적으로 증가 하는 숫자들이 오프셋 입니다.

각 파티션에서의 오프셋은 고유한 숫자이며 오프셋을 통해 메세지의 순서를 보장하고 컨슈머에서는 마지막 읽은 위치를 알수 있게 됩니다.
           

고가용성 보장

카프카는 앞서 설명한 것 처럼 분산 시스템이기 때문에 하나의 서버나 노드가 다운 되어도 다른 서버 또는 노드가 대신하여 안정적인 서비스가 가능하게 합니다.

이러한 고가용성을 보장하기 위해서 카프카는 리플리케이션(복제) 기능을 제공 하고 있으며, 토픽 자체를 복제하는 것이 아닌 토픽의 파티션을 복제를 하는 방식으로 합니다.

토픽을 생성할 때 옵션으로 리플리케이션 팩터 수를 지정할 수 있으며, 이 숫자에 따라서 리플리케이션들이 존재하게 됩니다.

원본과 리플리케이션을 구분하기 위해서 카프카에서는 리더(leader) 와 팔로워(follower) 라고 부릅니다.

| 리플리케이션 팩터수 | 리더 수 | 팔로워 수 |
|----------------|-------|---------|
|     2          |   1   |   1     |
|     3          |   1   |   2     |
|     4          |   1   |   3     |

위의 표는 리플리케이션 팩터 수에 따른 리더와 팔로우 수의 관계를 보여주고 있습니다.

표에서와 같이 리더의 숫자는 1을 유지 하게 되고 리플리케이션 팩터의 수에 따라서 팔로워 수만 증가하게 됩니다.

팔로워 수가 많다고 딱히 좋은 것은 아닙니다 왜냐하면 팔로워의 수만큼 결국 브로커의 디스크 공간도 소비되기 때문 입니다. 그래서 적절한(이상적인) 리플리케이션 팩터 수를 유지해야하며 일반적으로는 3으로 구성하도록 권장되고 있습니다.

리더(leader) 는 프로듀서, 컨슈머로 부터 오는 모든 읽기 와 쓰기 요청을 처리하며, 팔로워는 오직 리더로 부터 복제를 하게 됩니다.
              

주키퍼의 의존성

카프카에서는 빼놓을 수 없는 부분이 바로 주키퍼(ZooKeeper) 입니다. 주키퍼는 하둡의 서브 프로젝트 중 하나로 출발해 2011년에 아파치의 탑 레벨 프로젝트로 승격되었습니다.

주키퍼는 오늘날에 이르러 카프카를 비롯해 아파치 산하의 프로젝트 인 하둡, 나이파이(NiFi), 에이치베이스(HBase) 등 많은 분산 애플리케이션에서 코디네이터 역할을 하는 애플리케이션으로 사용 되고 있습니다.

주키퍼는 여러 대의 서버를 앙상블(Ensemble,클러스터) 로 구성하고, 살아 있는 노드 수가 과반수 이상 유지 된다면 지속적인 서비스가 가능한 구조 입니다.

따라서 주키퍼는 반드시 홀수로 구성해야 하며, 책의 실습 과정에서의 구성시 홀수인 3대로 구성하였습니다.

지노드(znode) 를 이용해 카프카의 메타 정보가 주키퍼에 기록되며, 주키퍼는 이러한 지노드를 이용해 브로커의 노드 관리, 토픽 관리 ,컨트롤러 관리 등 매우 중요한 역할을 하고 있습니다.


최근 들어 카프카를 사용하면서 주키퍼의 사용과정에서의 한계성이 드러나기 시작하였고, 그에 따라 주키퍼의 의존성을 벗어나고자 카프카에서는 주키퍼에 대한 의존성을 제거 하려는 움직임이 진행되고 있었습니다.

Apache Kafka 2.8 버전부터 주키퍼 대신 kraft 를 사용할 수 있으나 아직은 개발 단계로 문서에서도 표기 되어 있습니다.

Note

KRaft is in early access and should be used in development only. It is not suitable for production. https://developer.confluent.io/learn/kraft/

              

프로듀서의 기본 동작

프로듀서는 카프카의 토픽으로 메세지를 전송하는 역할을 담당합니다 프로듀서는 여러 옵션을 제공하며, 원하는 형태에 따라 옵션을 변경 하면서 다양한 방법으로 카프카로 메세지를 전송할 수 있습니다.
          

프로듀서 디자인

[https://dzone.com/articles/take-a-deep-dive-into-kafka-producer-api]

ProducerRecord 라고 표시된 부분은 카프카로 전송하기 위한 실제 데이터이며, 레코드는 토픽, 파티션 , 키 , 밸류(value) 로 구성되어 있습니다.

프로듀서가 카프카로 레코드를 전송할 때, 특정 토픽으로 메세지를 전송하며, 레코드에서 토픽과 밸류(메세지 내용)은 필수이고, 특정 파티션을 지정하기 위한 레코드의 파티션 과 특정 파티션에 레코드들을 정렬하기 위한 레코드의 키는 선택사항 (옵션) 입니다.

다음으로 각 레코드들은 프로듀서의 send() 메소드를 통해서 시리얼라이저(serializer, 직렬화) 그리고 파티셔너를 거치게 됩니다.

만약 프로듀서 레코드의 선택사항인 파티션을 지정 하였다면 파티셔너는 아무 동작도 하지 않고 지정된 파티션으로 레코드를 전달 합니다.

파티션을 지정하지 않은 경우에는 키를 가지고 파티션을 선택해 레코드를 전달하는데 기본적으로 라운드 로빈(round robin) 방식으로 동작 합니다.

send() 메소드 동작 이후 레코드들을 파티션 별로 잠시 모아두게 됩니다 레코드를 모아두는 이유는 프로듀서가 카프카로 전송하기 전에 배치 전송을 하기 위해서 입니다.

전송이 실패되면 재시도 동작이 이루어지게 되고 지정한 횟수만큼 재시도가 실패하면 최종 실패로 전달하게 되고, 전송이 성공되면 성공에 대한 메타데이터를 리턴 하게 됩니다.
                

컨슈머의 기본 동작

프로듀서가 카프카의 토픽으로 메세지를 전송하면 해당 메세지들은 브로커들의 로컬 디스크에 저장 되게 됩니다.

그리고 컨슈머를 이용해 토픽에 저장된 메세지를 가져올 수 있습니다.

컨슈머 그룹은 하나이상의 컨슈머들이 모여 있는 그룹을 의미하고 컨슈머는 반드시 컨슈머 그룹에 속하게 됩니다.

그리고 컨슈머 그룹은 각 파티션의 리더에게 카프카 토픽에 저장된 메세지를 가져오기 위한 요청을 보냅니다.

이때 파티션 수와 컨슈머(하나의 컨슈머 그룹 안에 있는 컨슈머 수)는 일대일로 매핑 해야하는 것은 아니지만, 파티션 수보다 컨슈머 수가 많게 구현되는 것은 바람직한 구성은 아닙니다.

컨슈머 수가 파티션 수보다 많다면 더 많은 수의 컨슈머들은 그냥 대기 상태로 존재하기 때문에 더 빠르게 메세지를 가져오거나 처리량을 늘어나지 않습니다.

컨슈머 그룹내에서 리밸런싱 동작을 통해 장애가 발생한 컨슈머의 역할을 동일한 그룹에 있는 다른 컨슈머가 그 역할을 대신 수행 하므로 굳이 장애 대비를 위한 추가 컨슈머 리소스를 할당하지 않아도 됩니다.
         

컨슈머 그룹의 이해

컨슈머는 컨슈머 그룹 안에 속한 것이 일반적인 구조로, 하나의 컨슈머 그룹안에 여러개의 컨슈머가 구성될 수 있습니다. 그리고 아래의 그림 처럼 토픽의 파티션과 일대일로 매핑 되어 메세지를 가져오게 됩니다.

peter-01 이라는 토픽이 있고 파티션 0 ,1, 2 라는 총 3개의 파티션으로 구성되어 있습니다.

우측에는 컨슈머 그룹01 이 있으며 그룹내에는 파티션수와 동일한 3개의 컨슈머가 속해 있습니다.

이와 같이 컨슈머들은 하나의 컨슈머 그룹 안에 속해 있으며, 그룹 내의 컨슈머들은 서로의 정보를 고유 하게 됩니다.

공유하는 정보로는 예를 들어 컨슈머01이 문제가 발생하여 종료되었다면, 컨슈머02 또는 컨슈머03은 컨슈머01이 하던일을 대신해서 peter-01 토픽의 파티션 0을 컨슘하게 됩니다.


해당 포스팅은 실전 카프카 개발부터 운영까지 책의 많은 내용 중에서 일부분의 내용만 함축적으로 정리한 것으로 모든 내용 확인 및 이해를 위해서 직접 책을 통해 모든 내용을 확인하시는 것을 권해 드립니다.


다음 이어지는글 

            

Reference

Reference Book
실전 카프카 개발부터 운영까지


관련 된 다른 글

 

 

         

2
0
글에 대한 당신의 생각을 기다립니다. 댓글 의견 주세요!x