Apache Kafka 간략하게 살펴보기

Greg Lee
12 min readDec 10, 2023

--

본 문서는 “카프카 핵심 가이드 (제이펍)” 를 참고하였습니다.

Apache Kafka 는 데이터 엔지니어링에서의 핵심 플랫폼이면서 마이크로서비스와 같은 아키텍처 내에서도 유용하게 활용되는 오픈소스이다. 현 시점에서 Apache Kafka 는 여러 IT 서비스와 플랫폼에서 사실상의 표준처럼 활용되고 있지만, Kafka 에 대한 기본 개념과 Kafka 를 활용한 서비스 개발 과정에서 주의해야할 점을 명확히 알고 개발하는 것은 쉽지 않을 수 있다.

이러한 문제 의식을 기반으로 Apache Kafka 에 대한 기본 개념을 정리하고, 실무 관점에서 생각하는 Apache Kafka 의 장점과 Kafka 를 활용한 개발 과정에서 주의해야 할 점을 정리하고자 한다.

Apache Kafka 란?

아파치 카프카는 링크드인에서 처음 개발되어 현재는 아파치 재단에서 관리하고 있는 오픈소스이다. 2011년의 링크드인에서는 수많은 데이터를 실시간으로 처리하는 과정에서 많은 어려움을 겪고 있었다. 그 당시의 링크드인은 데이터를 전송하고 수신하는 과정에서 다수의 프로듀서와 컨슈머가 필요에 따라 개별적인 연결을 가져가는 구조였고, 그 때문에 하나의 시스템만 추가되어도 통신 구조가 기하급수적으로 복잡해질 수 있었다. (M * N 구성)

카프카 이전의 링크드인 내부 시스템 구조 (https://www.confluent.io/blog/event-streaming-platform-1/ 참고)

이를 해결하기 위해 중앙화된 메시지와 데이터의 흐름을 관리하는 구조를 가져가기로 했고, 그 과정에서 만들어진 것이 카프카이다.

Kafka 구현 후 링크드인 내부 시스템 구조 (https://www.confluent.io/blog/event-streaming-platform-1/ 참고)

카프카는 전통적인 메시지 큐 시스템인 RabbitMQ, ActiveMQ 와 같은 메시징 시스템과 비교할 때 처리량과 처리 속도, 가용성과 확장성이 월등히 앞서면서 링크드인을 포함한 여러 IT 서비스 회사에서 카프카를 채택하여 운영 중이다. (29CM 도 21년부터 활발하게 사용 중이다)

다음은 Apache Kafka 의 기본 개념을 하나씩 설명하도록 하겠다.

Apache Kafka 살펴보기

특정한 개념을 설명할 때에는 이를 하나의 명확한 문장으로 표현하는 것도 방법이지만, 해당 개념을 구성하는 여러 요소를 하나씩 뜯어보면서 전체 개념을 파악할 수 있게 하는 것도 방법이 될 수 있다. 본 문서에서는 카프카를 구성하는 여러 요소를 하나씩 설명하면서 카프카 전체의 개념을 파악할 수 있도록 한다.

지금부터 설명할 카프카의 구성 요소는 다음과 같다. 각각의 요소를 자세히 설명하면서 카프카 전체의 개념을 전달하고자 한다.

  1. 브로커 (Broker)
  2. 클러스터 (Cluster)
  3. 토픽과 파티션 (Topic, Partition)
  4. 메시지 (Message)
  5. 프로듀서와 컨슈머 (Producer, Consumer)
  6. 컨슈머 그룹 (Consumer Group)
  7. 리밸런싱 (Rebalancing)
  8. 리플리케이션 (Replication)

브로커 (Broker)

하나의 카프카 서버를 브로커(Broker) 라고 한다. 브로커는 프로듀서로부터 메시지를 수신하고 오프셋을 지정한 후 해당 메시지를 디스크에 저장한다. 또한 컨슈머의 파티션 읽기 요청에 응답하고 디스크에 수록된 메시지를 전송한다.

카프카의 브로커는 클러스터(Cluster) 의 일부 구성원으로 동작하도록 설계되었다. 여러 개의 브로커가 하나의 클러스터에 포함될 수 있으며, 그 중 하나는 클러스터의 컨트롤러(Controller) 역할을 수행한다. 컨트롤러는 클러스터 내의 각 브로커에게 담당 파티션을 할당하고, 브로커들이 정상적으로 동작하는지 모니터링한다.

클러스터 (Cluster)

여러 대의 분산 서버를 네트워크로 연결하여 마치 하나의 거대한 서버처럼 동작하게 만드는 개념을 서버 클러스터링(Server Clustering) 이라고 한다. 여러 대의 서버를 클러스터로 묶게 되면, 특정 서버에서 장애가 발생하더라도 다른 서버에서 외부의 요청을 처리할 수 있기 때문에 서비스 전체의 가용성에는 문제가 발생하지 않는 장점이 있다.

카프카도 여러 대의 서버(Broker) 를 묶어서 하나의 거대한 서비스(Cluster) 처럼 움직이기 때문에, 특정 서버에 장애가 발생하더라도 카프카를 이용하는 클라이언트에게는 정상적인 처리와 응답을 제공할 수 있다. 또한 클러스터 내에 카프카 서버를 추가할 때마다 그만큼 메시지의 수신과 전달에 대한 처리량이 증가하기 때문에 확장성 측면에서도 장점이 있다.

카프카 브로커와 카프카 클러스터의 개념 (Kafka : The Definitive Guide 54p 참고)

카프카 클러스터의 확장 작업은 시스템 전체의 사용에 영향을 주지 않으면서도 온라인 상태에서 수행이 가능하다. 이러한 특징은 카프카 클러스터를 처음 구축할 때에는 소규모로 운영하다가, 이후에 처리하는 트래픽의 양에 따라 카프카 서버를 대규모로 손쉽게 늘릴 수 있는 장점을 제공한다.

토픽과 파티션 (Topic, Partition)

카프카의 메시지는 토픽(Topic) 으로 분류된다. 토픽은 데이터베이스의 테이블이나 파일 시스템의 폴더와 유사하다. 하나의 토픽은 여러 개의 파티션(Partition) 으로 구성될 수 있다.

하나의 토픽은 여러 파티션으로 구성됨 (Kafka : The Definitive Guide 29p 참고)

메시지는 파티션에 추가되는 형태로만 기록되며, 맨 앞부터 제일 끝까지의 순서로 읽힌다. 대개 하나의 토픽은 여러 개의 파티션을 갖지만, 메시지의 처리 순서는 토픽이 아닌 파티션별로 관리된다. 이 때 각 파티션은 서로 다른 서버에 분산될 수 있는데, 이러한 특징 때문에 하나의 토픽이 여러 서버에 걸쳐 수평적으로 확장될 수 있다. 이는 단일 서버로 처리할 때보다 훨씬 높은 성능을 가질 수 있게 해준다.

메시지 (Message)

카프카에서는 데이터의 기본 단위를 메시지(Message) 라고 한다. 카프카는 메시지를 바이트 배열의 데이터로 간주하므로 특정 형식이나 의미를 갖지는 않는다. 이 때문에 카프카에는 어떠한 데이터 형태이든지 저장이 가능하고, 메시지를 읽어들인 후에는 적절한 형태로 변환하여 사용해야 한다.

카프카의 메시지는 토픽 내의 파티션에 기록되는데, 이 때 특정 메시지를 기록할 파티션을 결정하기 위해 메시지에 담긴 키 값을 해시 처리하고, 그 값과 일치하는 파티션에 메시지를 기록하게 된다. 여기서 메시지의 키 값을 해시 처리하는 로직을 파티셔너(Partitioner, 메시지를 기록하는 파티션을 선택하는 컴포넌트) 라고 한다. 이러한 원리 때문에 동일한 키 값을 가지는 여러 개의 메시지는 항상 동일한 파티션에 기록되게 된다. 만약 메시지의 키 값이 null 로 전달된다면 카프카 내부의 기본 파티셔너는 각 파티션에 저장되는 메시지 개수의 균형을 맞추기 위해 라운드 로빈(Round-Robin) 방식으로 메시지를 기록한다.

프로듀서와 컨슈머 (Producer, Consumer)

카프카의 클라이언트는 프로듀서(Producer) 와 컨슈머(Consumer) 가 있다.

프로듀서는 새로운 메시지를 특정 토픽에 생성하는데, 이 때 프로듀서는 기본적으로 메시지가 어떤 파티션에 기록하는지는 관여하지 않는다. 만약 프로듀서가 특정한 메시지를 특정한 파티션에 기록하고 싶을 때에는 메시지 키와 파티셔너를 활용할 수 있다. 파티셔너는 키의 해시 값을 생성하고 그것을 특정 파티션에 대응시키는데, 이러한 방식으로 지정된 키를 갖는 메시지가 항상 같은 파티션에 기록되게 해준다.

컨슈머는 하나 이상의 토픽을 구독하면서 메시지가 생성된 순서로 읽는다. 컨슈머는 메시지를 읽을 때마다 파티션 단위로 오프셋을 유지하여 읽는 메시지의 위치를 알 수 있다. 오프셋의 종류는 Commit Offset 과 Current Offset 이 있는데 Commit Offset 은 컨슈머로부터 ‘여기까지의 오프셋은 처리했다’ 는 것을 확인하는 오프셋이다. Current Offset 은 컨슈머가 어디까지 메시지를 읽었는지를 나타내는 오프셋이다. 각각의 파티션마다 오프셋이 있기 때문에 컨슈머가 읽기를 중단했다가 다시 시작하더라도 언제든 그 다음 메시지부터 읽을 수 있게 된다.

Consumer 의 offset 개념 (https://kontext.tech/diagram/1159/kafka-offset-explained 참고)

컨슈머 그룹 (Consumer Group)

카프카 컨슈머들은 컨슈머 그룹(Consumer Group) 에 속하게 된다. 여러 개의 컨슈머가 같은 컨슈머 그룹에 속할 때에는 각 컨슈머가 해당 토픽의 다른 파티션을 분담해서 메시지를 읽을 수 있다. 이처럼 하나의 컨슈머 그룹에 더 많은 컨슈머를 추가하면 카프카 토픽의 데이터 소비를 확장할 수 있다. 즉, 더 많은 컨슈머를 추가하는 것이 메시지 소비 성능 확장이 중요한 방법이 된다.

이 때 주의할 점은, 한 토픽의 각 파티션은 (마치 함수처럼) 하나의 컨슈머만 처리할 수 있다는 것이다. 그렇기 때문에 하나의 토픽 내의 파티션 개수보다 더 많은 수의 컨슈머를 추가하는 것은 의미가 없다는 것을 명심해야 한다. 그리고 각 컨슈머가 특정 파티션에 대응되는 것을 파티션 소유권(Partition Ownership) 이라고 한다. (파티션 소유권은 추후 리밸런싱 개념을 설명할 때 연결된다)

4개의 파티션에 2개의 컨슈머 (Kafka : The Definitive Guide 87p 참고)
4개의 파티션에 4개의 컨슈머. 최대의 읽기 성능 (Kafka : The Definitive Guide 88p 참고)
4개의 파티션에 5개의 컨슈머. 일하지 않는 컨슈머가 발생함 (Kafka : The Definitive Guide 88p 참고)

카프카는 하나의 토픽에 여러 개의 컨슈머 그룹이 붙어서 메시지를 읽을 수 있는 다중 컨슈머 기능을 제공하는데, 여러 개의 컨슈머 그룹이 서로간의 상호 간섭 없이 각자의 오프셋으로 각자의 순서에 맞게 메시지를 읽고 처리할 수 있다. 같은 토픽의 메시지를 읽어야 하는 여러 개의 애플리케이션이 있다면 각각의 애플리케이션마다 각자의 컨슈머 그룹을 갖도록 하면 되는데, 이 때문에 보통 컨슈머 그룹명은 애플리케이션 이름과 일치시켜 관리하는 편이다. 간단히 말해 컨슈머 그룹은 애플리케이션의 단위라고 생각하면 좋다.

하나의 토픽에 여러 개의 컨슈머 그룹이 메시지를 읽을 수 있음 (Kafka : The Definitive Guide 89p 참고)

리밸런싱 (Rebalancing)

한 컨슈머로부터 다른 컨슈머로 파티션 소유권이 이전되는 것을 리밸런싱(Rebalancing) 이라고 한다. 리밸런싱은 컨슈머 그룹의 가용성과 확장성을 높여주는 중요한 개념이다. 컨슈머 그룹 내의 컨슈머가 추가되면, 그에 맞게 특정 파티션의 소유권을 신규 컨슈머에게 넘겨줄 수 있어야 하고, 또 컨슈머 그룹 내에서 오류가 생긴 컨슈머에게는 파티션 소유권을 회수하여 다른 컨슈머에게 전달해줄 수 있어야 전체적인 메시지 소비 성능과 가용성이 유지될 수 있기 때문이다.

리밸런싱은 보통 다음과 같은 상황에서 발생한다.

  1. 컨슈머 그룹 내에 새로운 컨슈머가 추가되거나
  2. 특정 컨슈머에 문제가 생겨 중단되거나
  3. 해당 컨슈머 그룹이 바라보는 토픽 내에 새로운 파티션이 생기거나

리밸런싱은 위에서 언급한 것처럼 컨슈머 그룹의 가용성과 확장성을 높여주기 때문에 중요하지만, 리밸런싱동안 컨슈머들은 메시지를 읽을 수 없는 상태에 빠지므로(stop the world) 안전하게 리밸런싱하는 방법과 부적절한 리밸런싱을 피하는 것이 중요하다.

리플리케이션 (Replication)

리플리케이션(Replication, 복제) 은 카프카 클러스터의 가용성을 보장하는 개념이다.

카프카의 메시지는 토픽에 저장되며, 각 토픽은 여러 파티션으로 구성된다. 이 때 각각의 파티션은 다수의 리플리카(Replica, 복제본) 를 가질 수 있다. 리플리카는 다음 두 가지 형태가 있다.

  1. 리더 리플리카(Leader Replica) : 각 파티션은 리더로 지정된 하나의 리플리카를 가진다. 일관성을 보장하기 위해 모든 프로듀서와 컨슈머 클라이언트의 요청은 리더를 통해서 처리된다. 즉, 모든 메시지의 읽기와 쓰기 요청은 리더 리플리카를 통해서만 처리된다는 것이다.
  2. 팔로어 리플리카(Follower Replica) : 각 파티션의 리더를 제외한 나머지 리플리카를 팔로어라고 한다. 팔로어는 클라이언트의 요청을 서비스하지 않고, 단순히 리더의 메시지를 복제하여 리더의 것과 동일하게 유지하는 역할만 한다. 이후 특정 파티션의 리더 리플리카가 중단되는 경우에는 팔로어 리플리카 중의 하나가 해당 파티션의 새로운 리더로 선출된다.

리더 리플리카와 동기화 하기 위해 팔로어 리플리카들은 리더에게 Fetch 요청을 전송한다. 이것은 컨슈머가 메시지를 읽기 위해 전송하는 것과 같은 타입이다. 이 때, 최신 메시지를 계속 요청하는 팔로어 리플리카를 In-Sync Replica(ISR) 이라고 하고, 그렇지 않은 리플리카를 Out-Sync-Replica 라고 한다. 당연하게도 동기화되지 않은 리플리카는 추후 리더가 장애가 생겼을 때 새로운 리더가 될 수 없다.

여기까지가 카프카를 구성하는 주요한 요소를 하나씩 풀어서 설명한 내용이다. 이어지는 글에서는 “실무 관점에서 생각하는 카프카의 주요한 장점”과 “카프카를 활용하여 서비스를 개발할 때 주의해야 할 것들" 에 대해 정리할 예정이다. (실무 관점에서의 Apache Kafka 활용 으로 이어집니다)

--

--