본문 바로가기
데이터 엔지니어링

Kafka란? (1) | 아키텍처, 핵심 개념

by 내기록 2022. 3. 4.
반응형

Kafka?

메시지 발행/구독 시스템이다. 발행된 시스템을 저장하고 중계하는 역할을 브로커(broker)가 수행한다.

* 데이터를 메시지 발행자가 직접 구독자에게 보내지 않는다. 대신 발행자가 메시지를 구분해서 발행/구독 시스템에 전송하면 구독자가 특정 부류의 메시지를 구독할 수 있게 해준다.

 

카프카의 데이터는 지속해서 저장하고 읽을 수 있으며, 시스템 장애에 대비하고 확장에 따른 성능 저하를 방지하기 위해 데이터를 분산처리 할 수 있다.

 

카프카의 메시지는 토픽의 파티션에 저장되는데, 데이터를 저장할 파티션을 결정하기 위해 일관된 해시 값으로 키를 생성한다.

따라서 같은 키를 갖는 메시지는 항상 같은 파티션에 저장된다.

 

[배치]

효율성을 위해서 여러 개의 메시지를 모아 배치 형태로 파티션에 저장하므로 네트워크로부터 매번 메시지를 받아서 처리하는 데 따른 부담을 줄일 수 있다. 이 경우 대기 시간(latency)과 처리량(throughput) 간의 트레이드오프가 생길 수 있다. 배치의 크기가 클수록 단위 시간당 처리될 수 있는 메시지는 많아지지만 각 메시지의 전송 시간은 더 길어진다. 

 

 

https://cjrequena.com/assets/pdf/slides-apache-kafka-architecture-fundamentals.pdf

 

프로듀서와 컨슈머?

기본적으로 프로듀서는 메시지가 어떤 파티션에 수록되는지 관여하지 않는다. 그러나 때로는 프로듀서가 특정 파티션에 메시지를 직접 쓰는 경우가 있다. 이때는 메시지 키와 파티셔너를 사용한다.

파티셔너는 키의 해시 값을 생성하고 그것을 특정 파티션에 대응시켜 지정된 키를 갖는 메시지가 항상 같은 파티션에 수록하게 한다.

 

컨슈머는 메시지의 offset을 유지하여 읽는 메시지의 위치를 알 수 있다.

 

! 한 토픽의 각 파티션은 하나의 컨슈머만 소비할 수 있다. 

각 컨슈머가 특정 파티션에 대응되는 것 -> 파티션 소유권(ownership)

한 컨슈머가 파티션 메시지를 읽는데 실패하면 같은 그룹의 다른 컨슈머가 대신 파티션 소유권을 받고 실패한 파티션 메시지를 읽는다.

 

< 컨슈머 >

컨슈머는 내부적으로 컨슈머 그룹, 리밸런싱 등 여러 동작을 수행한다.

컨슈머 그룹은 각 파티션의 리더에게 카프카 토픽에 저장된 메시지를 가져오기 위한 요청을 보낸다. 이때 파티션 수와 컨슈머 수는 일대일로 매핑되는 것이 이상적이다. 파티션 수보다 컨슈머 수가 많아지면 처리량이 높아지는 것이 아니라 더 많은 컨슈머들이 그냥 대기 상태로만 존재한다.

 

< 컨슈머 그룹 리밸런싱 >

컨슈머 그룹 내에서 리밸런싱 동작을 통해 장애가 발생한 컨슈머의 역할을 동일한 그룹에 있는 다른 컨슈머가 그 역할을 대신 수행한다.

참고 : https://always-kimkim.tistory.com/entry/kafka101-consumer-rebalance

 

브로커와 클러스터

카프카는 애플리케이션의 이름을 말하고, 브로커는 카프카 애플리케이션이 설치된 서버 또는 노드를 의미한다.

브로커는 프로듀서로부터 메시지를 수신하고 오프셋을 지정한 후 해당 메시지를 디스크에 저장한다.

또한, 컨슈머의 파티션 읽기 요청에 응답하고 디스크에 저장된 메시지를 전송한다.

하드웨어 성능에 따라 다르겠지만, 하나의 브로커는 초당 수천 개의 토픽과 수백만 개의 메시지를 처리할 수 있다.

 

카프카의 브로커는 클러스터의 일부로 동작하도록 설계되었다. 즉, 하나의 클러스터에 여러 개의 브로커가 있다.

 

클러스터 컨트롤러는 같은 클러스터의 각 브로커에게 담당 파티션을 할당하고 브로커들이 정상적으로 동작하는지 모니터링 하는 관리 기능을 맡는다.

각 파티션은 한 브로커가 소유하며, 그 브로커를 파티션 리더라고 한다.

같은 파티션에 여러 브로커가 지정되는 경우는 파티션을 복제하기 위함이다.

 

repliation(리플리케이션)

메시지를 여러 개로 복제해서 카프카 클러스터 내 브로커들에 분산시키는 동작이다. 이러한 리플리케이션 덕분에 하나의 브로커가 종료되더라도 카프카는 안정성을 유지할 수 있다. replication-factor 옵션으로 리플리케이션 값을 설정할 수 있다.

정확하게 말하면 토픽이 리플리케이션 되는것이 아니고 토픽의 파티션이 리플리케이션되는 것이다.

 

replication-factor 수가 커지면 안정성은 높아지지만 그만큼브로커 리소스를 많이 사용학 된다. 따라서 복제에 대한 오버헤드를 줄여서 최대한 브로커를 효과적으로 사용하는 것을 권장한다.

 

  권장 Replication-factor
테스트나 개발 환경 1
운영 환경(로그성 메시지로서 약간의 유실 허용) 2
운영 환경(유실 허용하지 않음) 3

replication-factor 수를 4,5로 설정할 수 있지만 3일 경우에도 충분히 메시지 안정성이 보장되고 적절한 디스크 공간을 사용할 수 있다.

만약 4 또는 5 이상으로 변경하면 디스크 공간을 많이 사용하게 되므로, 이 점을 염두에 두고 유의해서 늘려야 한다.

 

Partition(파티션)

하나의 토픽이 한 번에 처리할 수 있는 한계를 높이기 위해 토픽 하나를 여러개로 나눠 병렬 처리가 가능하게 만든 것이다.

하나를 여러 개로 나누면 분산 처리도 가능하다.

파티션 수 선정 방법은 초기에 2-4 정도로 생성한 후, 메시지 처리량이나 컨슈머의 LAG 등을 모니터링 하면서 조금씩 늘려가는 방법이 가장 좋다.

 

Segment(세그먼트)

파티션에서 좀 더 확장된 개념이다.

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

https://cjrequena.com/assets/pdf/slides-apache-kafka-architecture-fundamentals.pdf

각 파티션마다 N개의 세그먼트 로그 파일들이 존재한다. 

실제로 확인하기 위해서는 broker 서버에 접근해서 kafka-logs 디렉토리 내 파일 리스트를 확인하면 된다.

__consumer_offsets-0
__consumer_offsets-1
topic-name-0
topic-name-1
...

topic-name-0 디렉토리는 topic-name 이라는 토픽의 0번째 파티션 디렉토리이다. 

000000000000000.index
000000000000000.log
000000000000000.timeindex
leader-epoch-checkpoint

 

.log 파일의 내용을 살펴보면 프로듀서에서 전송했던 메시지를 확인할 수 있다.

여기서 .log 파일이 세그먼트이기 때문에 메시지가 브로커의 로컬 디스크에 안전하게 저장되어 있음을 알 수 있다.

 

* 프로듀서로 부터 받은 메시지를 파티션N의 세그먼트 로그 파일에 저장한다.

* 브로커의 세그먼트 로그 파일에 저장된 메시지는 컨슈머가 읽어갈 수 있다.

컨슈머는 topic-name 토픽을 consume해서 해당 토픽 내 파티션 N(0)의 세그먼트 로그 파일에서 메시지를 가져온다.

 

 

토픽과 파티션?

카프카의 메시지는 토픽으로 분류된다 (DB테이블이나 파일 시스템의 폴더와 유사)

하나의 토픽은 여러 개의 파티션으로 구성될 수 있다.

각 파티션은 서로 다른 서버에 분산될 수 있다. 즉, 하나의 토픽이 여러 서버에 걸쳐 수평적으로 확장될 수 있음을 의미한다.

이로 인해 단일 서버로 처리할 때보다 훨씬 성능이 우수하다.

 

* stream : 데이터를 쓰는 프로듀서에서 읽는 컨슈머로 이동되는 연속적인 데이터

참고로 토픽의 파티션 개수는 증가만 가능하고 감소될 수 없다.

 

파티션은 아래에서 상세히 한번 더 다뤄진다.

 

Schema?

카프카는 메시지를 단순히 바이트 배열로 처리하지만, 내용을 이해하기 쉽도록 메시지의 구조를 나타내는 스키마를 사용할 수 있다.

주로 Avro를 선호한다. Avro는 직렬화(serialization) 프레임워크로 데이터를 직렬화하는 형식을 제공하며, 메시지와는 별도로 스키마를 유지 관리한다.

* 카프카에서는 일관된 데이터 형식이 중요하다. 메세제의 쓰기와 읽기 작업을 분리해서 수행하기 때문이다.

 


카프카의 핵심 개념

카프카가 많은 인기를 얻고 있는 이유는 높은 처리량, 빠른 응답 속도, 안정성 때문이다. 이에 대해 자세히 알아보자.

 

분산 시스템

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

카르카도 구성한 클러스터의 리소스가 한계치에 도달했을 때 브로커를 추가하는 방식으로 확장이 가능하며, 온라인 상태에서 매우 간단하게 추가할 수 있다.

→ 확장이 용이하다는 점은 카프카의 매우 큰 장점이다.

 

페이지 캐시

카프카는 높은 처리량을 얻기 위해 추가한 기능 중 하나는 페이지 캐시(page cache)이다.(디스크 캐싱)

운영체제는 성능을 높이기 위해 꾸준히 진화하고 개선되고 있는데, 특히 페이지의 캐시 활용이 대표적이다. 카프카 역시 OS의 페이지 캐시를 활용하는 방식으로 설계되어 있다.

캐시는 직접 디스크에 읽고 쓰는 대신 물리 메모리 중 애플리케이션이 사용하지 않는 일부 잔여 메모리를 활용한다.

 

https://needjarvis.tistory.com/602 너무 귀여운 그림..

 

카프카가 OS의 페이지 캐시를 이용한다는 것은 카프카가 직접 디스크에서 읽고 쓰기를 하지 않고 페이지 캐시를 통해 읽고 쓰기를 한다는 것이다. (모든 디스크 읽기 및 쓰기는 페이지 캐시를 통한다)

 

* Page cache?

Linux 파일 작성 명령이 있을 때 즉시 디스크에 파일을 저장하는 대신 Page Cache 라는 공간에 먼저 저장한 후 지연하여 파일을 저장한다.

Consumer 에 의해 읽기 요청이 왔을 때, Page Cache에 저장되어 있는 데이터라면 메모리에 적재된 데이터를 바로 전송한다.

 

 

참고) 빠른 속도의 이유 중 하나인 Zero Copy

리눅스 2.2 버전에서 처음 소개된 sendfile() 시스템 콜이 제로카피(Zero-copy) 동작을 구현했다. 자바(Java)에서는 nio 패키지에 transferTo(), transferFrom() 메소드로 구현되어 read(), send() 두 번의 시스템 콜이 transferFrom() 한번의 호출로 가능해졌다.

https://ohjongsung.io/2019/11/11/%EC%B9%B4%ED%94%84%EC%B9%B4-%EB%94%94%EC%9E%90%EC%9D%B8

  1. 사용자가 transferTo() 메소드를 이용해 파일 전송을 요청한다. read()와 send() 함수가 하나로 합쳐진 형태의 시스템 콜이다. read() 시스템 콜과 마찬가지로 DMA 엔진이 디스크에서 파일을 읽어 커널 주소 공간에 위치한 Read buffer로 데이터를 복사한다.
  2. 커널 모드에서 유저 모드로 컨텍스트 스위칭하지 않고 바로 Socket buffer로 데이터를 복사한다.
  3. Socket buffer에 복사된 데이터를 DMA 엔진을 통해 NIC buffer로 복사되어 진다.
  4. 데이터가 전송되고 transferTo() 메소드에서 리턴한다.

모든 동작이 transferTo() 메소드 내에서 발생한다. 즉 컨텍스트 스위칭이 4번에서 2번으로 줄어들었다. transferTo() 메소드 호출시 커널 모드로 한번, 종료시 유저모드로 한번 총 2번의 컨텍스트 스위칭이 발생한다.

https://ohjongsung.io/2019/11/11/%EC%B9%B4%ED%94%84%EC%B9%B4-%EB%94%94%EC%9E%90%EC%9D%B8

또 동작들이 유저 주소공간에 있는 Application Buffer로 복사되어지지 않기 때문에 데이터의 복사본이 4군데에서 3군데로 줄어들었다. 따라서 데이터를 복사하는 동작도 한 번 줄어들었다. 컨텍스트 스위칭 회수와 복사본의 개수가 줄어든만큼 CPU 자원의 낭비가 줄어들게되어 성능이 향상된다.

 

출처 : https://ohjongsung.io/2019/11/11/%EC%B9%B4%ED%94%84%EC%B9%B4-%EB%94%94%EC%9E%90%EC%9D%B8

 

 

배치 전송 처리

카프카는 프로듀서, 컨슈머 클라이언트들과 통신하며 수많은 메시지를 주고받는다. 이때 발생하는 수많은 통신을 묶어서 처리할 수 있다면, 단건으로 통신할때에 비해 네트워크 오버헤드를 줄일 수 있을뿐 아니라 장기적으로는 더욱 빠르고 효율적으로 처리할 수 있다.

 

예를 들어, 구매 로그를 저장소로 보내는 작업은 이미 로그가 서버에 기록되어 있으므로 실시간 처리보다는 배치 처리를 이용하는 편이 효율적이다. 카프카에서는 이러한 장점을 지닌 배치 전송을 권장한다.

 

압축 전송

카프카는 메시지 전송 시 좀 더 성능이 높은 압축 전송을 사용하는 것을 권장한다. 압축만으로도 네트워크 대역폭이나 회선 비용 등을 줄일 수 있는데, 앞서 설명한 배치 전송과 결합해 사용한다면 더욱 높은 효과를 얻게 된다.

 

토픽, 파티션, 오프셋

카프카는 topic이라는 곳에 데이터를 저장하는데, 토픽을 병렬 처리를 위해 여러 개의 파티션(partition)이라는 단위로 다시 나뉜다.

파티셔닝을 통해 단 하나의 토픽이라도 높은 처리량을 수행할 수 있으며, 이 파티션의 메시지가 저장되는 위치를 오프셋(offset)이라 한다.

오프셋을 통해 메시지의 순서를 보장하고 컨슈머에서는 마지막까지 읽은 위치를 알 수도 있다.

 

offset은 Consumer Group별로 관리된다.

 

고가용성 보장

카프카는 분산 시스템이기 때문에 하나의 서버나 노드가 다운되어도 다른 서버 또는 노드가 장애가 발생한 서버의 역할을 대신해 안정적인 서비스가 가능하다. 이러한 고가용성을 보장하기 위해 카프카에서는 리플리케이션 기능을 제공한다.

토픽 자체를 복사하는 것이 아니라 토픽의 파티션을 복제하는 것이다.

원본과 리플리케이션을 구분하기 위해 leader, follower로 나눈다.

 

리더의 숫자는 1로 일정하고 팔로워의 수는 리플리케이션 펙터 수에 따라 증가하게 된다.

팔로워의 수만큼 결국 브로커의 디스크 공간도 소비되므로 이상적인 리플리케이션 팩터 수를 유지해야 한다.

리더는 프로듀서, 컨슈머로부터 오는 모든 읽기와 쓰기 요청을 처리하며, 팔로워는 오직 리더로부터 리플리케이션하게 된다.

 

 

주키퍼의 의존성

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

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

주키퍼는 카프카의 중요한 메타데이터를 저장하고 각 브로커를 관리하는 중요한 역할을 한다.

하지만 최근 들어 카프카가 점점 성장하면서 주키퍼 성능의 한계가 드러났고, 주키퍼 의존성을 제거하려는 움직임이 진행 중이다.

 


 

[참고] 카프카(KAFKA) 데이터 처리방식의 특화된 기능

 

1. KAFKA는 기존 메시징 시스템과는 달리 메시지를 메모리대신 파일 시스템에 쌓아두고 관리한다.

 

2. 디스크에 기반한 영속적인 저장 방식을 사용하지만 페이지 캐시를 활용하여 높은 처리량을 제공하는 인메모리 방식에 가깝다

 

3. 메모리에 별도의 캐시를 구현하지 않고 OS의 페이지 캐시에 위임하고 OS가 알아서 서버의 유휴 메모리를 페이지 캐시로 사용하여 앞으로 필요한 것으로 예상되는 메시지들을 미리 읽어들여(readahead)디스크 읽기 성능을 향상 시킨다.

 

4. Kafka 프로세스가 직접 캐시를 관리하지 않고 OS에 위임하기 때문에 프로세스를 재시작 하더라도 OS의 페이지 캐시는 그대로 남아있기 때문에 프로세스 재시작 후 캐시를 워밍업할 필요가 없다는 장점이 있다. 

 

5. 여러 consumer가 한 topic으로부터 여러 번에 걸쳐 메시지를 가져올 수 있다. 이러한 방식이 가능한 이유는 클라이언트가 해당 queue에서 어느 부분까지 데이터를 받아갔는지 위치를 알려주는 'offset'을 관리하기 때문이다.

 

6. 메시지를 메모리에 저장하지 않기 때문에 메시지가 JVM 객체로 변환되면서 크기가 커지는 것을 방지할 수 있고 JVM의 GC로 인한 성능 저하를 피할 수 있다.

 

출처: https://brocess.tistory.com/79 [행복한디벨로퍼:티스토리]

 


카프카를 사용하는 이유?

  1. 다중 프로듀서
    여러 클라이언트가 많은 토픽을 사용하거나 같은 토픽을 사용해도 카프카는 무리 없이 많은 프로듀서의 메시지를 처리할 수 있다.
    따라서 여러 프런트엔드 시스템으로부터 데이터를 수집하고 일관성을 유지하는 데 이상적이다.
  2. 다중 컨슈머
    많은 컨슈머가 상호 간섭 없이 메시지 스트림을 읽을 수 있게 지원한다.
    * 한 클라이언트가 특정 메시지를 소비하면 다른 클라이언트에서 그 메시지를 사용할 수 없는 큐(queue) 시스템과는 다르다.
  3. 디스크 기반의 보존
    지속해서 메시지를 보존할 수 있다.
    메시지는 카프카 구성에 설정된 보존 옵션(보존 기간이나 토픽 크기)에 따라 디스크에 저장되어 보존된다.
    -> 보존 옵션을 토픽별로 선택할 수 있는데 우리는 데이터가 유독 많이 들어오는 토픽은 2일, 기본은 7일로 설정했다.
    보존 기간 동안 컨슈머가 동작하지 않더라도 메시지는 카프카에 보존되므로 프로듀서의 메시지 백업이 필요없고 컨슈머가 다시 실행되면 중단 시점의 메시지부터 처리할 수 있다.
    -> 이 내용은 컨슈머나 커넥터 부분을 보면 이해할 수 있을 것이다. 컨슈머에는 offset 이라는 개념이 있다.
    * offset : 컨슈머가 파티션의 어느 위치까지 읽어쓴지 나타냄
  4. 확장성
    카프카는 확장성이 좋아서 어떤 크기의 데이터도 쉽게 처리할 수 있다.
    확장 작업은 시스템 전체의 사용에 영향을 주지 않고 클러스터가 온라인 상태일 때도 수행될 수 있다.
    여러 개의 브로커로 구성된 클러스터는 개별적인 브로커의 장애를 처리하면서 클라이언트에게 지속적인 서비스를 할 수 있다.
  5. 고성능
    위의 모든 기능들이 합쳐져서 카프카를 고성능의 '메시지 발행/구독 시스템'으로 만들어준다.
    프로듀서, 컨슈머, 브로커 모두 대용량의 메시지 스트림을 쉽게 처리할 수 있도록 확장될 수 있다.

 

카프카의 탄생 배경 (요구사항)

위에서 설명한 사용 이유를 더 간단하게 말해보자

 

  • Push-pull 모델을 사용해서 메시지 프로듀서와 컨슈머를 분리시킨다.
  • 다수의 컨슈머가 사용할 수 있게 메시징 시스템의 데이터를 지속적으로 보존한다.
  • 많은 메시지 처리량에 최적화시킨다.
  • 데이터 스트림의 양이 증가될 때 시스템을 수평 확장할 수 있게 한다.

 

파티션 개수 산정 방법

- 단위 시간당 토픽의 처리량(throughput)은 얼마를 예상하는가?
  예) 초당 100KB? 1GB?

- 한 파티션의 데이터를 읽을 때 목표로 하는 최대 처리량은? 파티션 하나는 항상 한 컨슈머가 소비한다.

따라서 만일 처리 속도가 느린 컨슈머가 작업을 하는게 초당 50MB까지 가능하다면 파티션을 소비하는 최대 처리량은 초당 50MB로 제한된다.

- 하나의 파티션에 데이터를 생성하는 프로듀서당 최대 처리량도 산정이 가능하지만 매우 빠르게 처리되므로 처리량을 조사하지 않아도 무방하다.

- 키(key)를 사용해서 파티션에 메시지를 쓰는 경우에는 향후에 파티션을 추가할 때 개수 산정이 어려울 수 있다. 따라서 향후 예상되는 사용 방법을 기준으로 처리량을 추산하는 것이 좋다.

- 브로커마다 파티션 개수와 디스크 용량 및 네트워크 처리 속도를 고려하자.

- 파티션 개수를 처음부터 너무 많이 늘리지 말자. 왜냐하면 각 파티션은 브로커의 메모리와 그 외 다른 자원을 사용하므로 리더 선정에 더 많은 시간이 소요된다.

 

결론적으로 파티션 개수를 너무 많이 산정하지 않아야 한다. 

파티션 개수는 목표 처리량을 컨슈머 예상 처리량으로 나눠서 계산할 수 있다.

예) 목표 처리량 : 초당 1GB , 예상 처리량 : 초당 50MB

1000/50=20 최소 20개의 파티션이 필요하다.

 

파티션 개수가 많으면 throughput은 높아질 수 있으나 latency가 낮아질 수 있다. 그러므로 잘 고려해야 한다.

 

 

 

References

kafka : The Definitive Guide, Neha Narkhede, Gwen Shapira, Todd Palino

실전 카프카 개발부터 운영까지, 고승범 (추천!)

 

반응형

댓글