카프카 6장 내부 메커니즘
개요
카프카의 내부 작동 방식
•
카프카 컨트롤러
•
카프카에서 복제가 작동하는 방식
•
카프카가 프로듀서와 컨슈머의 요청을 처리하는 방식
•
카프카가 저장을 처리하는 방식
6.1 클러스터 멤버십
•
브로커 관리
◦
카프카는 아파치 주키퍼를 사용하여 클러스터 멤버인 브로커들의 목록을 관리합니다.
◦
각 브로커는 고유한 식별자(ID)를 가집니다.
•
브로커 등록 과정
◦
브로커 프로세스 시작 시 주키퍼에 Ephemeral 노드 형태로 ID를 등록합니다.
◦
등록 경로는 /brokers/ids 입니다.
◦
동일한 ID를 가진 다른 브로커 시작 시 에러가 발생합니다.
•
브로커 모니터링
◦
카프카 컴포넌트들은 주키퍼의 브로커 등록 경로를 구독하여 브로커의 추가/제거를 감지합니다.
•
브로커 연결 끊김
◦
브로커와 주키퍼 간 연결이 끊기면 해당 브로커의 Ephemeral 노드가 자동으로 삭제됩니다.
◦
이를 통해 다른 카프카 컴포넌트들이 브로커 다운을 감지할 수 있습니다.
•
브로커 ID 유지
◦
브로커가 정지해도 해당 ID는 다른 자료구조(예: 토픽의 레플리카 목록)에 남아있습니다.
•
브로커 복구
◦
브로커가 완전히 유실된 경우, 동일한 ID를 가진 새 브로커를 투입하면 이전 브로커의 역할을 대신합니다.
◦
새 브로커는 유실된 브로커의 토픽과 파티션들을 자동으로 할당받습니다.
6.2 컨트롤러
1. 컨트롤러 브로커의 정의와 선출
•
클러스터 내 한 브로커가 컨트롤러 역할 수행
•
보통 클러스터에서 가장 먼저 실행된 브로커가 컨트롤러가 됨
•
주요 역할: 파티션 리더 브로커 선출 및 토픽 파티션 분배
2. 컨트롤러 선출 메커니즘
•
컨트롤러로 선출된 브로커는 주키퍼에 /controller Ephemeral 노드 생성
•
다른 브로커들은 이 노드에 watch를 설정하여 변경 감지
3. 컨트롤러 장애 및 재선출 과정
•
컨트롤러 브로커 중단 시 /controller 노드 삭제
•
다른 브로커들이 새로운 /controller 노드 생성 시도
•
가장 먼저 성공한 브로커가 새 컨트롤러가 됨
4. 에포크(세대) 값 시스템
•
새 컨트롤러 선출 시 에포크 값 증가
•
브로커들은 현재 컨트롤러의 에포크 값을 인지
•
낮은 에포크 값의 메시지 무시 (좀비 컨트롤러 방지)
5. 컨트롤러의 주요 작업
•
주키퍼에서 최신 레플리카 상태 맵 읽기
•
브로커 변동 시 새로운 파티션 리더 할당
•
상태 변경 사항을 주키퍼에 기록
•
관련 브로커들에게 LeaderAndISR 요청 전송
•
모든 브로커의 MetadataCache 업데이트 (UpdateMetadata 요청)
6. 리더십 변경 및 브로커 동작
•
새 리더 브로커: 클라이언트 요청 처리 시작
•
팔로워: 새 리더로부터 메시지 복제
•
브로커 재시작 시: 모든 레플리카는 팔로워로 시작, 리더 메시지 따라잡기
7. 컨트롤러의 주요 책임
•
브로커 추가/제거 시 파티션과 레플리카 중 리더 선출
•
"스플릿 브레인" 현상 방지를 위한 에포크 번호 사용
6.2.1 Kraft
KRaft라 불리는 새로운 컨트롤러가 개발
이는 기존의 주키퍼 기반 컨트롤러의 한계점을 극복한 래프트 기반 컨트롤러 쿼럼
새로운 컨트롤러 설계의 핵심 아이디어
’ 카프카 그 자체에 사용자가 상태를 이벤트 스트림으로 나타낼 수 있도록 하는 로그 기반 아키텍처를 도입 ‘
1.
KRaft 소개
a.
2019년부터 아파치 카프카 커뮤니티가 시작한 프로젝트
b.
목적: 주키퍼 기반 컨트롤러에서 Raft 기반 컨트롤러 쿼럼으로 전환
c.
카프카 2.8에서 프리뷰 버전으로 포함, 3.3부터 프로덕션 환경에서 사용 가능
2.
KRaft 도입 이유
a.
기존 모델의 확장성 한계 (파티션 수 증가에 따른 문제)
b.
메타데이터 불일치 문제 해결
c.
컨트롤러 재시작 시 성능 병목 현상 개선
d.
내부 아키텍처 개선 (메타데이터 소유권 관련)
e.
운영 복잡성 감소 (주키퍼 제거로 인한)
3.
새로운 컨트롤러 아키텍처
a.
로그 기반 아키텍처 도입
b.
Raft 알고리즘을 사용한 리더 선출
c.
메타데이터 이벤트 로그 관리
d.
액티브 컨트롤러와 팔로워 컨트롤러 구조
4.
주요 변경사항
a.
브로커의 메타데이터 업데이트 방식 변경 (pull 방식)
b.
브로커 상태 관리 개선 (등록, 오프라인, 펜스된 상태 등)
c.
클라이언트 통신 방식 변경 (주키퍼 대신 컨트롤러와 통신)
5.
KRaft 사용법
a.
클러스터 ID 생성 및 저장 공간 포맷
i.
kafka-storage.sh random-uuid 명령으로 클러스터 ID 생성
ii.
kafka-storage.sh format 명령으로 로그 디렉토리 포맷
b.
설정 변경
i.
주요 새 설정 파라미터:
•
process.roles: 인스턴스 역할 지정 (controller, broker, 또는 둘 다)
•
node.id: 인스턴스 고유 ID
•
controller.quorum.voters: 컨트롤러 쿼럼 지정
•
listeners: 브로커 및 컨트롤러 리스너 설정
•
log.dirs: 레코드 로그 및 메타데이터 로그 저장 위치
c.
실행
•
컨트롤러 역할 프로세스 먼저 시작
•
그 다음 브로커 역할 프로세스 시작
d.
사용
•
기존 카프카 명령어와 동일하게 사용 가능
•
주의사항
◦
프로덕션 환경에서는 컨트롤러와 브로커 역할을 분리하는 것을 권장
6.3 복제
카프카는 분산되고, 분할되고, 복제된 커밋 로그 서비스
•
개별적인 노드에는 필연적으로 장애가 발생
•
카프카는 이러한 상황속 신뢰성과 지속성을 보장하는 방식
카프카는 데이터를 토픽 단위로 조직화.
•
각 토픽은 1개이상의 파티션으로 분할
•
각 파티션은 다시 다수의 레플리카를 가질 수 있음
•
각각의 레플리카는 브로커에 저장
◦
대개 하나의 브로커는 수백 개에서 수천 개의 레플리카를 저장
레플리카 종류 (2가지)
리더 레플리카
•
각 파티션에는 리더 역할을 하는 레플리카가 하나씩 존재
•
모든 쓰기 요청을 처리하여 일관성 보장
•
읽어올 때는 리더 레플리카, 팔로워 레플리카 둘다 사용
팔로워 레플리카
•
리더 레플리카를 제외한 나머지 레플리카
•
주요 역할: 리더 레플리카의 최신 메시지 복제
•
리더 장애 시 새로운 리더로 승격 가능
팔로워로부터 읽기
•
목적: 네트워크 트래픽 비용 감소
•
설정: client.rack (클라이언트), replica.selector.class (브로커)
•
주의: 약간의 지연 발생 가능
복제 프로토콜
•
리더 레플리카: 팔로워의 동기화 상태 확인
•
팔로워 레플리카: 리더에게 읽기 요청 보내 동기화 유지
•
하이 워터마크: 커밋된 최신 오프셋 정보
레플리카 동기화 상태
인-싱크 레플리카
•
최신 메시지를 지속적으로 요청하는 레플리카
•
리더 장애 시 새로운 리더로 선출 가능
아웃-오브-싱크 레플리카
•
일정 시간 이상 동기화 실패한 레플리카
•
replica.lag.time.max.ms 설정으로 동기화 판단 시간 결정 (기본값 30초, v2.5.0부터)
선호 리더
•
토픽 생성 시 최초의 리더 레플리카
•
목적: 브로커 간 부하 균형
•
파티션의 레플리카 목록 중 첫 번째가 선호 리더
•
auto.leader.rebalance.enable 설정으로 자동 리밸런싱 가능
6.4 요청 처리
카프카 브로커가 하는 일의 대부분
•
클라이언트, 파티션 레플리카, 컨트롤러가 파티션 리더에게 보내는 요청을 처리
카프카 통신 프로토콜
•
TCP 기반의 이진 프로토콜 사용
•
요청 형식과 응답 방식 정의
•
다양한 언어로 구현된 클라이언트 존재
통신 특징
•
클라이언트가 연결 시작 및 요청 전송
•
브로커는 요청 처리 후 응답
•
요청은 수신 순서대로 처리 (메시지 순서 보장)
요청 헤더 구조
•
요청 유형 (API 키)
•
요청 버전
•
Correlation ID
•
클라이언트 ID
브로커의 요청 처리 과정
•
acceptor 스레드: 연결 수신 및 프로세서 스레드에 전달
•
프로세서 스레드(네트워크 스레드): 요청을 요청 큐에 넣고, 응답 큐에서 응답 가져옴
•
I/O 스레드: 요청 큐에서 요청을 가져와 처리
•
과정
1.
연결 (네트워크 쓰레드)
2.
요청 큐에 넣기
3.
I/O쓰레드
4.
응답 큐
5.
네트워크 쓰레드에서 폴링하여 반환
주요 클라이언트의 요청
•
쓰기 요청
◦
카프카 브로커로 메시지를 쓰고 있는 프로듀서가 보낸 요청
•
읽기 요청
◦
카프카 브로커로부터 메시지를 읽어오는 컨슈머나 팔로워 레플리카가 보낸 요청
•
어드민 요청
◦
토픽 CRUD 어드민 요청 (메타데이터 작업)
리더 레플리카 요청 처리
•
파티션의 리더 레플리카로만 요청 전송 가능
•
비리더 브로커로 요청 시 'Not a Leader for Partition' 에러 발생
•
메타데이터 관리
◦
클라이언트는 메타데이터 요청을 통해 토픽 정보 획득
◦
브로커는 메타데이터 캐시 보유
◦
클라이언트는 주기적으로 메타데이터 갱신 (metadata.max.age.ms 설정)
•
에러 처리
◦
'Not a Leader' 에러 발생 시 클라이언트는 메타데이터 갱신 후 재시도
6.4.1 쓰기 요청
acks 설정 변수
•
쓰기 작업이 성공한 것으로 간주되기 전 메시지에 대한 응답을 보내야 하는 브로커의 수를 의미
•
acks=1: 리더만 메시지 수신
•
acks=all: 모든 인-싱크 레플리카가 메시지 수신
•
acks=0: 브로커 응답 대기 없이 메시지 전송
파티션의 리더 레플리카를 가지고 있는 브로커가 해당 파티션에 대한 쓰기 요청을 받게 되면 몇 가지 쓰기 요청 유효성 검증 수행
•
사용자의 토픽 쓰기 권한 확인
•
acks 설정값 확인 (0, 1, 'all'만 가능)
•
acks=all 일 경우, 충분한 인-싱크 레플리카 존재 여부 확인
메시지 저장 과정
•
브로커가 새 메시지를 로컬 디스크에 쓰기
•
리눅스의 경우 파일시스템 캐시에 쓰여짐 (디스크 반영 시점 불확실)
•
카프카는 데이터의 디스크 저장을 기다리지 않음 (복제에 의존)
응답 처리
•
acks=0 또는 1: 즉시 응답
•
acks=all:
◦
요청을 퍼거토리(버퍼)에 저장
◦
팔로워 레플리카들의 메시지 복제 확인 후 응답
데이터 지속성
•
디스크 저장 대신 복제에 의존
•
인-싱크 레플리카 수 설정으로 안정성 조절 가능
6.4.2 읽기 요청
읽기 요청의 구조
•
클라이언트가 토픽, 파티션, 오프셋 목록 지정
•
최대 데이터 양 지정 (메모리 관리를 위해)
•
최소 데이터 양 지정 가능 (선택적)
요청 라우팅
•
파티션 리더를 가진 브로커로 전송
◦
읽기 요청 또한 파티션 리더를 맡고 있는 브로커에게 전송
◦
파티션 리더는 요청이 유효한지 확인하고, 메시지를 가져와 클라이언트에게 전송
•
클라이언트는 메타데이터 요청을 통해 라우팅 정보 획득
유효성 검증
•
요청된 오프셋의 존재 여부 확인
•
유효하지 않은 오프셋 요청 시 에러 응답
데이터 읽기 및 전송
•
Zero-Copy 최적화 적용
◦
리눅스의 파일시스템 캐시에서 읽어온 메시지를 중간 버퍼를 거치지 않고 바로 네트워크 채널로 보내는 것을 의미
◦
중간 버퍼 사용하지 않아 성능 향상
⇒ 데이터를 복사하고 메모리 상에 버퍼를 관리하기 위한 오버헤드가 사라짐.
최소 데이터 양 및 타임아웃
•
최소 데이터 양 지정 시, 해당 양 도달할 때까지 대기
•
타임아웃 설정으로 과도한 대기 방지
•
CPU 및 네트워크 사용량 감소에 효과적
읽기 제한
•
모든 인-싱크 레플리카에 쓰여진 메시지만 읽기 가능
•
복제 완료되지 않은 메시지는 '불안전'으로 간주
◦
복제 지연 영향
▪
어떠한 이유로 브로커들 사이의 메시지 복제가 늦어지면, 새 메시지가 컨슈머에 도달하는 데 걸리는 시간도 길어짐
▪
복제 지연 시 컨슈머의 메시지 수신도 지연
▪
replica.lag.time.max.ms 설정으로 지연 시간 제한
•
일관성 유지를 위한 조치
컨슈머가 많은 수의 파티션으로부터 이벤트를 읽어오는 경우가 있을 때, 카프카는 읽기 세션 캐시를 활용
•
읽기 세션 캐시 사용 시
◦
다수 파티션 읽기 시 효율성 증대
◦
파티션 목록과 메타데이터 캐싱
▪
모든 메타데이터를 캐시하며, 성능을 향상
◦
점진적 읽기 요청 가능
◦
변경사항 있을 때만 메타데이터 포함하여 응답
◦
캐시 크기 제한으로 우선순위 부여 (팔로워 레플리카, 다수 파티션 읽기 컨슈머 우선)
◦
캐시 생성 실패 또는 해제 시 적절한 에러 처리
에러 처리
•
캐시 관련 문제 발생 시 브로커가 에러 리턴
•
컨슈머는 자동으로 전체 메타데이터 포함 요청 재시도
6.5 물리적 저장소
카프카의 기본 저장 단위
•
파티션 레플리카
•
파티션은 다른 브로커들 사이에 분리될 수 없으며
◦
같은 브로커의 서로 다른 디스크에 분할 저장되는 것도 불가능
카프카가 데이터를 저장하기 위해 사용 가능한 디렉토리들을 어떻게 활용
6.5.1 계층화된 저장소
카프카 3.0부터 파티션 데이터를 저장할 때 계층화된 저장소 기능을 사용
이유
•
파티션별로 저장 가능한 데이터에 한도 존재
◦
최대 보존 기한과 파티션 수는 물리적인 디스크 크기에 제한을 받음
•
디스크와 클러스터 크기는 저장소 요구 조건에 의해 결정
◦
지연과 처리량이 주 고려사항일 경우, 클러스터는 필요한 것 이상으로 커지는 경우가 많음
•
클러스터의 크기를 줄이거나 키울 때, 파티션의 위치를 다른 브로커로 옮기는데 걸리는 시간은 파티션의 수에 따라 결정
◦
즉 작은 클러스터는 더 유연
계층화된 저장소 기능에서 카프카 클러스터는 저장소를 원격, 로컬로 나눔
•
로컬은 기존과 동일
•
원격은 HDFS, S3와 같은 전용 저장소 시스템을 이용
원격 저장소
•
지연성 관점에서 단점 존재
•
무한한 저장 공간, 더 낮은 비용, 탄력성 뿐만 아니라 실시간 데이터를 읽는 작업분야에서 원격 저장이 더 유용
6.5.2 파티션 할당
사용자가 토픽을 생성하면, 카프카는 우선 이 파티션을 브로커 중 하나에 할당.
만약 브로커가 6개 있을 때
•
파티션이 10개
•
복제 팩터가 3인 토픽을 생성하고자 한다면,
•
카프카는 30개의 파티션 레플리카를 브로커 6개에 할당해야 합니다.
이럴 때 다음과 같은 목표를 가짐
•
레플리카들을 가능한 한 브로커 간에 고르게 분산 (브로커 별로 5개의 레플리카)
•
각 파티션에 대해 각각의 레플리카가 서로 다른 브로커에 배치
•
브로커에 랙 정보가 설정되어 있다면 다른 파티션들은 서로 다른 랙에 할당해야함
카프카에서는 위 기능을 제공하며 각 브로커에 라운드 로빈 식으로 파티션을 할당, 리더를 결정
단, 사용 가능한 공간이나 현재 부하와 같은 점이 고려되지 않으며, 파티션 수만이 고려됨
6.5.3 파일 관리
카프카는 영구히 데이터를 저장하지도, 데이터를 지우기 전에 모든 컨슈머들이 메시지를 읽어갈 수 있도록 기다리지도 않음
카프카 파일 관리
•
오래되면 지운다 또는 용량이 넘어가면 지운다 라는 규칙에 의거하여 메시지를 관리
•
하나의 파티션은 여러 개의 세그먼트로 분할
•
각 세그먼트는 1GB 또는 1주일치의 데이터 만큼 저장됨
•
카프카가 파티션 단위로 메시지를 쓰는 만큼 각 세그먼트 한도가 다 차면 세그먼트를 닫고 새로운 세그먼트가 생성됨
◦
현재 쓰여지는 세그먼트를 액티브 세그먼트라고 불리움
6.5.4 파일 형식
세그먼트는 하나의 데이터 파일 형태로 저장되며, 파일 안에는 메시지와 오프셋이 저장
•
디스크의 수록되는 형식은 메시지의 형식과 동일
•
프로듀서가 카프카로, 카프카에서 컨슈머로 보내는 형식
메시지
•
키와 값 및 오프셋, 크기, checksum, 압축방식(코덱) 등에 대한 내용을 포함
•
타임스탬프는 카프카의 구성에 따라 메시지가 전송될 때 일 수 있으며 브로커가 수신할 때 시간일 수 있음
6.5.5 인덱스
메시지 검색 유연성
•
컨슈머는 임의의 사용 가능한 오프셋부터 메시지 읽기 가능
•
예: 오프셋 100부터 1MB 메시지 요청 시, 브로커가 빠르게 위치 찾아 제공
오프셋 인덱스
•
목적: 주어진 오프셋의 메시지를 빠르게 찾기 위함
•
구조: 오프셋과 세그먼트 파일 및 파일 내 위치를 매핑
•
각 파티션마다 유지됨
타임스탬프 인덱스
•
목적: 타임스탬프 기준 메시지 검색
•
구조: 타임스탬프와 메시지 오프셋을 매핑
•
사용 사례:
◦
카프카 스트림즈에서 광범위하게 사용
◦
일부 장애 복구 상황에서 유용
인덱스 관리
•
세그먼트 단위로 분할됨 (로그와 같음)
•
오래된 메시지 삭제 시 해당 인덱스 항목도 삭제 가능
•
체크섬 유지하지 않음
인덱스 복구 메커니즘
•
인덱스 오염 시 자동 재생성
◦
해당 로그 세그먼트의 메시지를 다시 읽어 오프셋과 위치 기록
•
운영자가 인덱스 세그먼트 삭제해도 안전
◦
자동으로 다시 생성됨 (단, 복구 시간 고려 필요)
▪
만약 의도치않게 인덱스파일만 제거되거나 해도 카프카가 다시 생성
6.5.6 압착
기본 보존 메커니즘
•
일반적으로 설정된 기간 동안만 메시지 저장
•
보존 시간 경과 후 메시지 삭제
보존 정책의 필요성
•
사용 사례에 따라 다양한 보존 전략 필요
◦
예: 고객 주소 변경 내역, 애플리케이션 상태 저장
메시지는 압착과 삭제 보존정책에 의하여 관리
삭제(Delete) 보존 정책
•
지정된 보존 기한보다 오래된 이벤트 삭제
•
사용 사례: 일정 기간 동안의 모든 이벤트 보존 필요 시
압착(Compact) 보존 정책
•
토픽에서 각 키의 가장 최근 값만 저장
•
사용 사례:
◦
최신 상태만 필요한 경우 (예: 현재 고객 주소)
◦
애플리케이션 상태 복원 시 최근 상태만 필요한 경우
•
주의사항: 키가 null인 메시지가 있을 경우 압착 실패
혼합 정책: Delete + Compact
•
보존 기한과 압착 설정 동시 적용 가능
•
작동 방식:
◦
지정된 보존 기한 이후 메시지 삭제
◦
키에 대한 가장 최근 값도 삭제 대상
•
장점:
◦
압착된 토픽의 과도한 성장 방지
◦
일정 기한 이후 레코드 삭제 요구사항 충족
6.5.7 압착의 작동 원리
각 로그는 다음과 같은 두 영역으로 나뉘게 됨
•
클린
◦
이전에 압착된 적 없었던 메시지들이 저장됨. (하나의 키에 하나의 값)
•
더티
◦
마지막 압착 작업 이후 쓰여진 모든 메시지들이 저장됨.
•
압착 프로세스 시작
◦
log.cleaner.enabled 설정으로 활성화
◦
각 브로커
▪
압착 매니저 스레드 시작
▪
다수의 압착 스레드 시작
•
압착 매니저 쓰레드와 압착 스레드를 활용하여 압착 작업을 수행
1.
압착 시작:
•
압착 매니저 스레드와 여러 압착 스레드가 활성화
2.
파티션 선택:
•
더티 메시지 비율이 가장 높은 파티션을 압착 대상으로 선택
3.
인메모리 맵 생성:
•
선택된 파티션의 더티 영역을 읽어 인메모리 오프셋 맵을 생성
•
이 맵은 메시지 키의 해시와 오프셋 정보를 저장.
4.
클린 세그먼트 처리:
•
가장 오래된 클린 세그먼트부터 처리를 시작.
5.
메시지 비교 및 처리:
•
각 메시지의 키를 오프셋 맵과 비교.
•
맵에 없는 키: 최신 값으로 간주하고 교체용 세그먼트에 복사.
•
맵에 있는 키: 더 새로운 값이 존재한다고 판단하여 해당 메시지를 건너뜀.
6.
세그먼트 교체:
•
모든 메시지 처리가 완료되면 교체용 세그먼트와 원본 세그먼트를 바꿈.
7.
반복:
•
다음 세그먼트로 이동하여 과정을 반복.
8.
압착 완료
a.
모든 세그먼트 처리가 끝나면 압착이 완료
b.
결과적으로 각 키별로 최신 값을 가진 하나의 메시지만 남게 됨
압착 조건 및 대상
조건
•
토픽 내용물의 50% 이상이 더티 레코드일 때 압착 시작
•
목표: 압착 빈도와 디스크 공간 사용의 균형 유지
대상
•
현재의 액티브 세그먼트는 압착하지 않음
•
비액티브 세그먼트의 메시지만 압착 대상
압착 시점 조절 매개변수
1.
min.compaction.lag.ms
•
정의: 메시지가 쓰여진 후 압착될 때까지의 최소 시간
2.
max.compaction.lag.ms
•
정의: 메시지가 쓰여진 후 압착이 가능해질 때까지의 최대 시간
•
용도: 특정 기한 내 압착 보장 (예: GDPR 준수)
압착의 영향
•
토픽의 읽기/쓰기 성능에 영향을 줄 수 있음
•
디스크 공간 사용 효율성 개선