01. 클러스터 멤버십
클러스터 멤버십을 가진다는 것은 구체적으로는 클러스터에 속한 브로커들의 식별자인 broker id 가 주키퍼와 같은 메타데이터 관리 시스템에 등록 되었다는 것을 의미한다.
브로커와 클러스터 멤버십
- 브로커 프로세스가 시작될 경우 : 주키퍼에 Ephemeral Node 형태로 id가 등록됨.
- 브로커와 주키퍼간 연결이 끊어질 경우 Ephemeral Node가 삭제됨.
- 브로커가 정지될 경우: 브로커를 Znode 또한 삭제되지만 브로커의 id 는 토픽의 레플리카 목록 등 다른 자료구조에 남아있는다.
02. 컨트롤러
컨트롤러와 브로커
- 컨트롤러는 브로커가 클러스터에 추가되거나 제거될 경우 파티션과 레플리카 중 리더를 선출할 책임을 지는 브로커이다.
- 컨트롤러는 에포크 번호를 사용해 서로 다른 2개 브로커가 controller로 동작하는 split brain을 방지한다.
Kraft
[!NOTE] > 왜 주키퍼 대신 자체 컨트롤러를 쓰는가?
- 컨트롤러가 주키퍼에 메타데이터를 쓰는 작업은 동기적이지만 브로커 메시지를 보내거나 주키퍼로부터 업데이트를 받는 과정은 비동기적으로 이루어진다. 이는 브로커, 컨트롤러, 주키퍼 간 메타데이터 불일치를 초래할 수 있다.
- 컨트롤러가 재시작할 때만다 주키퍼로부터 모든 브로커와 파티션에 대한 메타데이터를 읽어와야한다.
- 주기퍼 자체가 분산시스템이라 관리 포인트가 늘어난다.
KRaft의 핵심아이디어는 카프카 자체적으로 사용자가 상태를 이벤트스트림으로 나타낼 수 있도록 하는 로그 기반 아키텍쳐를 도입한 것이다.
- 순서 기반 보장
- 최신상태 팔로업 용이 : 컨트롤러들이 모두 최신 상태를 가지기에 컨트롤러 장애 복구는 모든 상태를 새 컨트롤러로 이전하는 장애복구 기간이 필요하지 않는다.
브로커 프로세스는 시작 시 주키퍼가 아닌 컨트롤러 quorum에 등록됨
03. 복제
- 복제는 카프카 아키텍쳐의 핵심이다.
- 복제는 개별적인 노드에 필연적으로 장애가 발생할 수 밖에 없는 상황에서 카프카가 가용성과 내결함성을 보장하는 방식이다.
- 카프카는 기본적으로 분산되고, 분할되고, 복제된 커밋로그 프로세스 이다.
- 기본적으루 두 가지 종류의 레플리카를 가진다.
- 리더 레플리카
- 팔로워 레플리카
[!NOTE] > 왜 리더 레플리카가 필요한가? 기본적으로 파티션 리더를 선출하는 이유는 크게 3가지이다.
-
1. 데이터의 읽기/쓰기 작업에 있어서 단일한 컨택 포인트 제공 단일 접접을 통해 데이터 일관성을 유지할 수 있으며 여러 replica에 쓰기 요청이 분산될 경우 발생가능한 데이터 충돌 문제를 방지.
-
2. 고가용성 및 장애복구 leader에 장애가 발생하더라도 컨트롤러를 통해 다른 replica중 하나를 leader로 승격시킬 수 있다.
-
3. 성능 최적화 leader replica를 통해 읽기/쓰기 요청이 중앙화되기 때문에 데이터 처리와 복제 간의 작업분리가 가능.
04. 요청처리
사전 지식
카프카 클러스터는 크게 두 가지 영역으로 나뉜다.
Concept
- Controller Plane : 클러스터에서 역할에 따라 카프카 브로커를 구분한 것. 클러스터의 모든 메타 데이터를 관리하는 영역.
- Data Plane : 클러스터에서 역할에 따라 카프카 브로커를 구분한것. 카프카에 들어오는 실제 데이터에 대한 읽고 쓰기를 담당하는 영역.

브로커의 요청처리 방식
- Client의 요청은 기본적으로 produce request와 fetch request의 두 가지 범주로 나뉜다.
Concept
- produce request : 데이터 배치 하나가 특정 topic에 쓰여지는 것에 대한 요청.
- fetch request : kafka topic으로 부터 데이터를 request하는 것에 대한 요청.

1. 파티션 할당
- record에 key가 존재할 경우 해싱을 통해 record에 들어갈 파티션을 할당한다.
- 기본적으로 같은 키를 가진 record는 항상 같은 파티션에 할당된다.
2. Record Batching
- 기본적으로 single record를 매번 보내는게 비효율적이라 (overhead of network request) 배치형태로 묶어서 한번에 전송한다.
- 대기시간과 배치사이즈
- linger.ms- batch.size**3. Network Thread **
- broker의 소켓 버퍼에 request 가 전달된다.
- Network Thread가 소켓 버퍼에서 Request를 읽어 Queue에 추가한다.
4. IO Thread
- IO thread pool이 Queue에 있는 Request를 가져온다.
- IO thread에서 crc check과 같은 인증 절차를 거친다.
- 이후 데이터를 Page Cache에서 Physical Storage에 commit log형태로 저장한다.
5. Kafka Physical Storage
-
Physical Storage에서 commit log는 기본적으로 collection of segment이다.
-
segment는 다음의 두 가지 형태로 나뉜다.
- log file : event data를 담는다.
- index file : index structure를 담는다. record offset과 실제 record의 mapping역할을 한다.
6. Purgatory(Map)
The Purgatory : staging the pending Request
- Purgatory는 모든 다중 브로커에 데이터가 복제될 까지 Request가 broker에 머물러 있는 것을 의미한다.
- Purgatory는 Map 형태의 Data structure이다.
- Request가 모든 Broker에 복제되면, Request를 Purgatory에서 삭제하고 Response를 Purgatory에 추가한다.
7. Response Added to Socket
- Network Thread가 응답 결과를 받아 Socket Send Buffer에 전송한다.
쓰기 요청
읽기 요청
- Data is zero copied to the send buffer
05. 물리적 저장소
계층화된 저장소
카프카의 저장소를 로컬과 리모트의 두 계층으로 나눈다.
파티션 할당
- 가능한 파티션을 고르게 분산한다.
- 가능한 파티션을 서로 다른 rack에 할당한다.
파일 관리
- 큰 파일에서 삭제 대상 메시지를 찾아서 지우는 작업은 시간이 오래걸리고 에러의 가능성도 높아 하나의 파티션을 여러 개의 세그먼트로 분할하는 방식을 취한다.
- 카프카가 파티션 단위로 메시지를 쓰는 만큼 한도가 다 찰 경우 세그먼트를 닫고 새로운 세그먼트를 생성한다.
- 현재 쓰여지고 있는 세그먼트를 액티브 세그먼트라 하며 이 세그먼트는 어떠한 경우에도 삭제되지 않는다.
파일 형식
-
네트워크를 통해 전달되는 형식과 디스크에 전달되는 형식으로 통일함으로써 제로 카피 최적화를 달성할 수 있다.
-
Kafka의 zero copy는 디스크에서 네트워크로 데이터를 전송할 때 Application buffer를 거치지 않고 직접 전송하는 방식이다. 기존의 read/write 방식은 커널 버퍼와 유저 버퍼를 오가며 4번의 컨텍스트 스위칭이 발생하지만, zero copy는 sendfile() 시스템 콜을 사용해 page cache와 socket buffer로 직접 전송하여 CPU 사용량과 지연시간을 크게 줄인다. 중요한 것은 커널 스페이스 내에서만 데이터가 이동한다는 것이다.
[!NOTE] > 메시지 헤더 멱등성 달성을 위한 메타데이터를 포함한다.
압착
[!NOTE] > 압착의 작동원리 로그는 클린 영역과 더티 영역으로 나뉜다.
- 클리너 스레드가 파티션의 더티 영역을 읽어 인메모리 맵 생성
- 압착 스레드는 전체 파티션 크기 대비 더티 메시지의 비율이 가장 높은 파티션을 골라 클린 상태로 만든다.
이벤트 삭제
Key Takeaway
Key Takeaway
- 복제는 카프카 아키텍쳐의 핵심이다. 카프카는 실제로 분산되고 분할되고 복제된 커밋 로그로 볼 수 있다.
- 카프카는 분산커밋로그를 스토리지 계층으로 사용하며
- Purgatory는 기본적으로 모든 Request가 Broker에 복제되고 응답을 받기 전까지 Request가 대기하는 자료구조이다.
- 네트워크를 통해 전달되는 형식과 디스크에 전달되는 형식으로 통일함으로써 제로 카피 최적화를 달성할 수 있다.