카프카핵심가이드 6장 - 카프카 내부 메커니즘
카프카 핵심가이드 책을 읽은 내용 정리
카프카 내부 메커니즘
카프카를 프로덕션 환경에서 운용하기 위하거나 카프카를 사용하는 애플리케이션을 개발하기 위해서 꼭 내부를 알아야하는 것은 아니지만 카프카가 실행되는 방식을 이해하는 데 도움이 됨
아래의 주제를 싶이 이해하는 것은 카프카를 튜닝할때 도움이 됨.
- 카프카 컨트롤러
- 카프카에서 replication이 동작하는 방식
- 카프카가 프로듀서와 컨슈머의 요청을 처리하는 방식
- 카프카가 저장을 처리하는 방식(파일 형식, 인덱스…)
클러스터 멤버십
카프카는 현재 클러스터 멤버인 브로커들의 목록을 유지하기 위해 Apache ZooKeeper를 사용하며, 각 브로커는 브로커 설정 파일에 정의되었거나 자동으로 생성된 고유한 식별자를 가짐
컨트롤러
- 컨트롤러 : 브로커 기능 + 파티션 리더를 선출하는 역할
클러스터에서 가장 먼저 시작되는 브로커는 주키퍼의 /controller에 임시(Ephemeral) 노드를 생성함으로써 컨트롤러가 된다.
컨트롤러 브로커가 멈추거나 주키퍼와의 연결이 끊어질 경우 이 임시(Ephemeral)노드는 삭제됨(zookeeper.session.timeout.ms
에 설정된 값보다 오랫동안 주키퍼에 하트비트를 전송하지 않는것도 해당됨)
KRaft: 카프카의 새로은 래프트 기반 컨트롤러
Apache Kafka의 새로운 협의 프로토콜이다.(kafka 2.8에 포함되었으며, 3.3부터 정식 production)
Apache Kafka의새로운 메커니즘인 KRaft를 사용하면 메타데이터를 효율적으로 관리할 수 있음
KRaft는 주키퍼의 의존성을 제거하고, 카프카 클러스터 내 컨트롤러가 선출된 후 메타데이터를 직접 관리한다. 이로 인해 성능과 안정성이 향상되며, 유지보수가 단순화되고, 병목현상이 줄어듦
Raft알고리즘이란?
Raft는 분산 시스템에서 합의(Consensus)를 구현하기 위해 설계된 알고리즘
- 주요 구성
- 리더(Leader): 클라이언트 요청을 처리하고 로그 복제를 관리
- 팔로워(Follower): 리더의 명령을 수신하고 적용
- 후보(Candidate): 리더가 없을 때 리더가 되기 위해 투표를 요청
- 주요 특징
- 리더 선출(Leader Election)
클러스터 내에서 하나의 리더를 선출하여 상태 변경을 관리함
리더는 클라이언트 요청을 처리하고 변경 사항을 다른 노드에 복제함- 로그 복제(Log Replication)
리더가 수신한 명령을 자신의 로그에 기록하고, 다른 팔로워(follower) 노드에 복제함 과반수의 노드가 기록을 승인하면 명령이 적용됨- 안정성(Safety)
어떤 상황에서도 로그의 일관성을 유지하며, 리더가 교체되더라도 이전 상태를 보장- 알고리즘의 동작 단계
- 리더 선출
초기에는 모든 노드가 Follower 상태로 시작
일정 시간 동안 리더가 없는 경우 Candidate 상태로 전환하여 투표를 요청
과반수 이상의 지지를 얻으면 리더가 됨- 로그 복제
리더는 클라이언트 요청을 처리하고 이를 로그에 추가
팔로워에게 새로운 로그를 전파(복제)
과반수의 노드가 확인하면 커밋(commit)- 장애 복구
리더가 실패하면 새로운 리더를 선출
네트워크 분할이나 노드 장애가 발생해도 데이터를 일관성 있게 유지
컨트롤러를 교체하기로 결정한 이유
컨트롤러가 주키퍼에 메타데이터를 쓰는 작업은 동기적이지만, 브로커 메시지를 보내는 작업과 주키퍼로부터 업데이트를 받는 과정은 비동기 적이여서 브로커, 컨트롤러, 주키퍼 간에 메타데이터 불일치가 발생할 수 있으며 잡아내기도 어려움.
컨트롤러가 재시작될 때 마다 주키퍼로부터 모든 브로커와 파티션에 대한 메타데이터를 읽어온 후, 메타데이터를 모든 브로커로 전송하므로 파티션과 브로커의 수가 증가할 수록 컨트롤러 재시작이 느려짐
메타데이터 소유권 관련 내부 아키텍처가 좋지 못함(어떤작업은 컨트롤러가, 다른건 브로커가, 나머지는 주키퍼가 하는등..)
주키퍼에 대한 추가적인 학습이 필요
KRaft 설계
로그 기반 아키텍처를 도입
KRaft 설계의 핵심은 카프카 그 자체에 사용자가 상태를 이벤트 스트림으로 나타낼 수 있도록 하는 로그 기반 아키텍처를 도입한 점
즉 다수의 컨슈머를 사용해서 이벤트 재생을 통해 최신 상태를 빠르게 따라잡을 수 있던 것 처럼, 새로운 컨트롤러 아키텍처에서 컨트롤러 노드들에도 적용되고 이전에 주키퍼에 저장하던 모든 정보들(토픽, 파티션, ISR, 설정 등)이 여기에 저장됨외부 시스템에 의존x
컨트롤러 노드들은 외부 시스템에 의존하지 않고 자체적으로 리더를 선출할 수 있게 됨
메타데이터 로그의 리더 컨트롤러인 액티브 컨트롤러는 모든 RPC 호출을 처리하고 팔로워 컨트롤러들은 액티브 컨트롤러에 쓰여진 데이터를 복제하여 액티브 컨트롤러에 장애 발생 시 즉시 투입이 가능함
따라서 모든 컨트롤러가 최신 상태를 유지하고 있으므로 컨트롤러 장애 복구에 긴 리로드 기간이 필요하지 않음컨트롤러는 다른 브로커에 변경사항을 PUSH 하지 않음
컨트롤러와 브로커 사이의 변경 사항 관련 통신은 컨트롤러가 push하는 형태가 아닌 브로커들이 마지막으로 가져온 메타데이터 변경 사항의 오프셋을 추적하고 컨트롤러로부터 pull하는 형태
브로커는 시동 시간을 줄이기 위해 파티션의 개수가 얼마나 많던지 간에 메타데이터를 디스크에 저장브로커 프로세스는 시작시 주키퍼가 아닌 컨트롤러 쿼럼에 등록
브로커는 시작시 컨트롤러 쿼럼에 등록되고 운영자가 등록을 해제하지 않는 한 유지
브로커가 종료되면 오프라인 상태로 들어갈 뿐 등록은 유지됨
온라인 상태지만 메타데이터를 최신 상태로 유지하고 있지 않은 브로커는 펜스된 상태(fenced state)가 되어 클라이언트 요청을 처리할 수 없음
복제
카프카 아키텍처의 핵심
개별적인 노드에 필연적으로 장애가 발생할 수밖에 없는 상황에서 카프카가 신뢰성과 지속성을 보장하는 방식이기에 “복제”는 매우 중요
리더 레플리카
- 각 파티션에는 리더 역할을 하는 레플리카가 하나씩 존재
- 일관성을 보장하기 위해 모든 쓰기 요청은 리더 레플리카로 주어짐
- 클라이언트들은 리더 레플리카나 팔로워로부터 레코드를 읽어올 수 있음
- 어느 팔로워 레플리카가 리더 레플리카의 최신 상태를 유지하고 있는지를 확인
리더 레플리카는 각 팔로워 레플리카가 마지막으로 요청한 오프셋 값을 확인함으로써 동기화 상태를 알 수 있음
- in-sync replica : 지속적으로 최신 메시지를 요청하고 있는 레플리카
- out-of-sync replica : 일정 시간(replica.lag.time.max.ms)동안 읽기 요청을 보내지 않거나 최근 메시지를 따라잡지 못한 레플리카
팔로워 레플리카
- 파티션에 속한 레플리카 중 리더를 제외한 나머지가 모두 팔로워 레플리카
- 리더 레플리카에 크래쉬가 날 경우, 최신 상태를 유지하던 팔로워 레플리카 중 하나가 리더로 승격
- 별도의 설정을 하지 않으면 클라이언트의 요청을 처리할 수 없음
선호 리더
각 파티션은 선호 리더를 갖음
선호 리더란 토픽이 처음 생성되었을 때 리더 레플리카였던 레플리카
파티션이 처음 생성되던 시점에서는 리더 레플리카가 모든 브로커에 걸쳐 균등하게 분포되기 때문에 “선호”라는 표현을 사용
요청처리
카프카 브로커가 하는 일의 대부분은 클라이언트, 파티션 레플리카, 컨트롤러가 파티션 리더에게 보내는 요청을 처리하는 것
카프카의 요청 표준 헤더
프로토콜 상세 내용은 여기를 참조
- 요청 유형 : API 키라고도 불림
- 요청 버전 : 브로커는 서로 다른 버전의 클라이언트로부터 요청을 받아 각 버전에 맞는 응답을 할 수 있음
- Correlation ID : 각각의 요청에 붙는 고유한 식별자
- 클라이언트 ID : 요청을 보낸 어플리케이션의 식별자
브로커의 요청 처리 순서
요청이 요청 큐에 들어오면, I/O 스레드(request handler thread)가 요청을 가져와서 처리하는 일을 담당
- 포트별로 억셉터(acceptor) 스레드 실행
- 억셉터 스레드가 연결 생성 후 요청을 프로세서 스레드에 전달
- 프로세서(네트워크) 스레드가 요청을 요청 큐에 넣고 응답을 응답 큐에서 가져와 클라이언트로 전달
가끔은 클라이언트로 보낼 응답에 지연이 필요할 때가 있는데 지연된 응답들은 완료될 때까지 퍼거토리(purgatory)에 저장됨
클라이언트 요청 유형
- 쓰기 요청 : 카프카 브로커로 메시지를 쓰고 있는 프로듀서가 보낸 요청
- 읽기 요청 : 카프카 브로커로부터 메시지를 읽어오고 있는 컨슈머나 팔로워 레플리카가 보낸 요청
- 어드민 요청 : 토픽 생성이나 삭제와 같이 메타데이터 작업을 수행중인 어드민 클라이언트가 보낸 요청
쓰기 요청
카프카 브로커로 메시지를 쓰고 있는 프로듀서가 보낸 요청
acks 설정변수는 메시지가 성공적으로 처리되었다고 간주하기 위해 응답을 보내야 하는 브로커의 수를 결정
acks=0
: 즉시 성공 처리 (브로커 응답 대기 없음)acks=1
: 리더 브로커만 메시지 수신 확인acks=all
: 모든 인-싱크 레플리카가 메시지 수신 확인
브로커는 유효성 검증 후 새 메시지를 로컬 디스크에 기록
리눅스 환경에서는 메시지가 파일시스템 캐시에 먼저 쓰이며, 실제 디스크 반영 시점은 불확실함
카프카는 디스크 저장 완료를 기다리지 않고, 메시지의 지속성을 복제 메커니즘에 의존
읽기 요청
카프카 브로커로부터 메시지를 읽어오고 있는 컨슈머나 팔로워 레플리카가 보낸 요청
- 클라이언트가 토픽, 파티션, 오프셋 목록을 지정
- 응답을 담을 수 있을 정도로 충분한 메모리를 할당하기 위한 최대 데이터 양 지정
- 파티션 리더를 가진 브로커로 요청 전송
- 유효성 검증
- 요청된 오프셋의 존재 여부 확인
- 유효하지않은 오프셋 요청 시 에러를 응답
- 데이터 읽기 및 전송
- zero-copy 최적화를 적용
zero-copy란?
데이터를 복사하지 않고 직접 전송하거나 처리하는 기술.
주로 시스템 프로그래밍, 네트워크 통신, 파일 입출력 등에서 성능을 향상시키기 위해 사용됨.Kafka는 sendfile() 시스템 호출을 이용하여 제로카피를 구현하고, 이 방식은 데이터를 브로커에서 클라이언트로 전송할 때, 디스크에서 커널 공간을 거쳐 바로 네트워크 소켓으로 전송
- 동작 과정
- Kafka가 데이터를 디스크에서 커널 공간으로 읽음.
- 커널 공간의 데이터를 유저 공간으로 복사하지 않고, 바로 네트워크 소켓 버퍼로 전달.
- 네트워크 소켓에서 클라이언트로 데이터 전송.
- 이 과정에서는 유저 공간에서 데이터를 처리하거나 복사하지 않으므로, 메모리 복사 횟수가 크게 줄어듦
- 동작 과정
- zero-copy 최적화를 적용
모든 인싱크 레플리카에 쓰여진 메시지만 읽을 수 있음 -> 복제가 완료되지 않은 메시지는 ‘불안전’으로 간주
- 컨슈머가 많은 수의 파티션으로 부터 이벤트를 읽어오는 경우가 있을 때, 카프카는 오버헤드를 최소화하기 위해 읽기 세션 캐시를 활용
기타 요청
카프카 프로토콜은 현재 약 61개의 서로 다른 요청 유형을 정의하고 있으며 앞으로 더 늘어날 것
물리적 저장소
레플리카 : 카프카의 기본 저장단위 파티션은 서로 다른 브로커들 사이에 분리될 수 없고, 같은 브로커의 서로 다른 디스크에 분할 저장 또한 불가능. 즉, 파티션의 크기는 특정 마운트 지점 사용 가능한 공한에 제한을 받음
Tiered Storage: 계층화된 저장소
kafka 3.6에 탑재되었으며, 대량의 데이터를 저장하기 위한 목적으로 사용.
Tiered Storage의 문제점
- 파티션별 데이터 최대 저장 한도가 존재. 결과적으로 물리적인 디스크의 크기에도 제한을 받음
- 디스크와 클러스터의 크기는 저장소 요건에 의해 결정됨. 지연과 처리량이 주된 고려사항인 경우, 클러스터는 필요 이상으로 커질 수 있는데 이는 곧 비용으로 직결.
- 클러스터의 크기를 키우고 줄일 때, 파티션의 위치를 다른 브로커로 옮기는데에 걸리는 시간은 파티션의 크기에 따라 결정된됨. 즉, 파티션의 크기가 클수록 클러스터의 탄력성이 줄어듦
Tiered Storage 계층
- 로컬
- 현재의 카프카 저장소 계층과 똑같이 로컬 세그먼트를 저장하기 위해 카프카 브로커의 로컬 디스크 사용
- 보존 정책(기한) : 대개 몇 시간 이하
- 지연 속도 짧음
- 원격
- 완료된 로그 세그먼트를 저장하기 위해
HDFS
나S3
와 같은 외부 저장소 시스템 사용 - 보존 정책(기한) : 며칠~몇 달 수준
- 지연 속도 김
- 완료된 로그 세그먼트를 저장하기 위해
무한한 저장 공간, 더 낮은 비용, 탄력성뿐만 아니라 오래 된 데이터와 실시간 데이터를 읽는 작업을 분리 시키는 기능.
파티션 할당
사용자가 토픽을 생성하면, 카프카는 우선 이 파티션을 브로커 중 하나에 할당.
각 브로커에 라운드 로빈식으로 파티션을 할당하며, 리더를 결정.
사용 가능한 공간이나 현재 부하와 같은점이 고려되지 않으며, 파티션 수만 고려됨.
파일 관리
카프카는 영구히 데이터를 저장하지도, 데이터를 지우기 전에 모든 컨슈머들이 메시지를 읽어갈 수 있도록 기다리지도 않음.
- 보존기한 설정
- “이만큼 오래된 메시지는 지운다” 혹은 “이 용량이 넘어가면 지운다”를 설정
- 하나의 파티션을 여러개의 세그먼트로 분할
세그먼트란?
실제 메시지가 브로커의 로컬 디스크에 저장되는 파일 - 각 세그먼트는 1GB 또는 일주일치의 데이터 만큼 저장됨
- 카프카가 파티션 단위로 메시지를 쓰는 만큼 각 세그먼트 한도가 다 차면 세그먼트를 닫고 새로운 세그먼트가 생성됨.
- 현재 쓰여지고 있는 세그먼트를 액티브 세그먼트라고 하는데 이 액티브 세그먼트는 어떠한 경우에도 삭게 되지 않음
파일 형식
각 세그먼트는 하나의 데이터 파일 형태로 저장되는데 파일 안에는 카프카의 메시지와 오프셋이 저장됨.
디스크에 저장되는 데이터 형식은 사용자가 프로듀서를 통해서 브로커로 보내는, 그리고 나중에 브로커로부터 컨슈머로 보내지는 메시지의 형식과 동일함.
카프카 메시지는 사용자 페이로드와 시스템 헤더로 구성
- 메시지 배치 헤더
- 메시지 형식의 현재 버전을 가리키는 매직넘버
- 배치에 포함된 첫 번째 메시지의 오프셋과 마지막 오프셋과의 차이
- 첫 번째 메시지의 타임스탬프와 배치에서 가장 큰 타임스탬프
- 바이트 단위로 표시된 배치의 크기
- 체크섬
- 등등….
인덱스
컨슈머는 임의의 사용 가능한 오프셋부터 메시지 읽기 가능
오프셋 인덱스
- 주어진 오프셋의 메시지를 빠르게 찾기 위함.
- 오프셋과 세그먼트 파일 및 파일 내 위치를 매핑.
- 각 파티션마다 유지됨.
타임스탬프 인덱스
- 타임스탬프 기준 메시지 검색
- 타임스탬프와 메시지 오프셋을 매핑
인덱스 관리
- 세그먼트 단위로 분할됨. 오래된 메시지 삭제 시 해당 인덱스 항복도 삭제 가능. 체크섬을 유지하지 않음.
- 인덱스 오염시 자동 재생성
- 운영자가 인덱스 세그먼트를 삭제해도 자동으로 다시 생성됨(복구 시간은 고려해야함).
- 만약 의도치않게 인덱스 파일만 제거되거나 해도 카프카가 다시 생성
압착
카프카는 설정된 기간 동안만 메시지를 저장하며, 보존 시간이 지나간 메시지들은 삭제함.
삭제 보존 정책과 압착보존정책을 허용함으로써 메시지관리
삭제(delete) 보존정책
지정된 보존 기한보다 오래된 이벤트 삭제.
압착(compact) 보존 정책
토픽에서 각 키의 가장 최근 값만 저장(키가 null인 메시지가 있을 경우 압착 실패).
ex) 현재 고객 주소등과 같은 최신 상태만 필요한 경우
delete + compact를 동시에 적용 가능!
지정된 보존 기한 이후 메시지는 삭제 되며 키에 대한 가장 최근 값도 삭제됨.
압착된 토픽의 과도한 성장 방지 및 일정기한 이후 레코드 삭제 요구사항도 충족함.
압착의 작동 원리
클린 영역
: 이전에 압착된 적이 있었던 메시지들이 저장. 이 영역은 하나의 키마다 하나의 값만을 포함함더티 영역
: 마지막 압착 작업 이후 쓰여진 메시지들이 저장되는 영역
클리너 스레드가 더티 영역을 읽어 인-메모리 맵(키,오프셋)을 생성하여 클린 세그먼트들을 오래된 것부터 읽으며 맵과 대조하며 진행됨
삭제된 이벤트
가장 최근 메시지조차 남기지 않고 삭제하려면 해당 키값과 null 벨류값을 갖는 메시지(툼스톤)를 써주면됨.
클리너 스레드가 이 메시지를 발견하면 평소대로 압착 작업을 한 뒤 null 밸류값을 갖는 메시지만 보존할 것.
컨슈머가 툼스톤 메시지를 놓치지 않도록 충분한 시간을 주는 것이 중요.
토픽은 언제 압착되는가?
액티브 세그먼트를 절대로 압착하지 않음.
토픽 내용물의 50% 이상이 더티 레코드인 경우에만 압착을 시작함.
min.compaction.lag.ms
: 메시지가 쓰여진 뒤 압착될 때까지 지나가야하는 최소 시간max.compaction.lag.ms
: 메시지가 쓰여진 뒤 압착이 가능해질 때까지 딜레이 될 수 있는 최대 시간
요약
아직 알아야할 내용이 많다.