Chapter 06. 카프카 내부 메커니즘
6.1 클러스터 멤버십 📡
브로커 목록 관리
- 카프카 클러스터의 브로커 목록은 ZooKeeper에서 관리하며
/brokers/ids
경로에 저장됨
브로커 ID
- 브로커는 생성 시 고유한 ID를 가짐
- ID는 사용자가 직접 설정하거나, 자동으로 생성 가능
Ephemeral 노드
- ZooKeeper는 브로커 ID를 Ephemeral 노드로 저장
- 브로커가 삭제되면 해당 Ephemeral 노드도 자동으로 제거됨
브로커 ID 재활용
- 운영 중인 카프카에서는 모든 브로커 ID가 완전히 제거되지 않음
- 동일한 ID를 새 브로커에 할당하면, 기존 브로커를 대체하여 정상적으로 동작 가능
6.2 컨트롤러 🎛️
컨트롤러의 역할
- 파티션 리더 선출과 같은 핵심 역할 수행
- 클러스터 동작을 조율
컨트롤러 선출 과정
- 첫 번째 브로커가 ZooKeeper의
/controller
경로에 Ephemeral 노드를 생성하며 컨트롤러로 지정 - 이후에 시작된 다른 브로커들은 노드 생성에 실패하고,
/controller
의 변경 사항을 감지하기 위해 watch를 설정함 - 이를 통해 클러스터 내에서 항상 하나의 컨트롤러만 유지됨
컨트롤러 장애 처리
기존 컨트롤러 장애 시,
- 다른 브로커들이
/controller
의 변경을 감지하고 노드 생성을 시도 - 가장 먼저 생성에 성공한 브로커가 새로운 컨트롤러로 선출
- epoch 값으로 컨트롤러 세대를 관리하며, 이전 세대 요청은 무시
브로커 장애 처리
- 컨트롤러는 장애가 발생한 브로커를 감지 후, 해당 브로커가 리더로 맡고 있던 모든 파티션을 순회
- 파티션의 레플리카 목록에서 다음 레플리카를 리더로 선출
리더 전환 요청
- 컨트롤러는 LeaderAndISR 요청을 통해 새로운 리더와 팔로워 정보를 업데이트
브로커 추가 시 처리
- 새 브로커가 추가될 경우 리더 선출 없이 모든 파티션 레플리카를 팔로워로 설정
ZooKeeper 기반 컨트롤러의 한계
메타데이터 불일치
- 컨트롤러가 주키퍼에 메타데이터를 쓰는 작업은 동기적
- 반면, 업데이트 및 메시지 전달 작업은 비동기적 → 불일치 가능성
컨트롤러 재시작 문제
- 재시작 시 모든 브로커와 파티션 메타데이터를 다시 읽고 전송해야 하므로 시간이 지연됨
ZooKeeper 학습 부담
- ZooKeeper는 분산 시스템으로 설계되어 추가 학습이 필요하며, 카프카 학습 곡선을 높이는 원인이 됨
6.2.1 KRaft: 카프카의 새로운 Raft 기반 컨트롤러 🚀
로그 기반 아키텍처
- 기존 주키퍼 기반 컨트롤러는 메타데이터를 관리하며 다양한 기능 수행
- KRaft는 이벤트 로그 기반 설계로 여러 컨트롤러 노드가 메타데이터의 이벤트 로그를 관리
다중 컨트롤러 구성
- 리더 컨트롤러(액티브 컨트롤러)와 여러 팔로워 컨트롤러로 구성
- 리더 컨트롤러가 장애 시 팔로워 컨트롤러 중 하나를 리더로 승격
- 장애 발생 시에도 빠른 재시작이 가능
메타데이터 일관성
- 리더 컨트롤러가 메타데이터 로그를 작성하고 팔로워 컨트롤러가 이를 복제
- 주키퍼와 컨트롤러가 분리되어 동작하던 문제를 해결하여 메타데이터 불일치 문제를 방지
브로커와의 연동
- 브로커는 컨트롤러의 MetadataFetch API를 사용해 메타데이터를 읽고 동작
ZooKeeper와 KRaft 컨트롤러 비교
ZooKeeper 기반 컨트롤러
- 브로커 중 하나가 컨트롤러 역할을 수행
- 주키퍼와 함께 파티션 리더 선출 및 메타데이터 관리 담당
KRaft 컨트롤러
- 브로커와 별개의 카프카 프로세스로 동작
- 리더 컨트롤러에서 파티션 리더 선출과 메타데이터 관리를 전담
- Raft 기반 설계로 다수의 컨트롤러가 존재하여 고가용성 제공
6.3 복제 🌀
복제 기능의 중요성
- 카프카는 다수의 레플리카를 통해 데이터의 신뢰성과 지속성을 보장
- 장애 발생 시에도 데이터 손실 없이 클러스터가 안정적으로 동작하도록 설계됨
리더 레플리카
- 파티션에 대한 모든 읽기 및 쓰기 요청을 처리함
팔로워 레플리카
- 리더 레플리카 데이터를 복제하여 최신 상태를 유지함
- 리더 레플리카가 장애를 겪으면 팔로워 레플리카 중 하나가 리더로 승격됨
팔로워 레플리카의 읽기 요청
rack
설정값에 따라 팔로워 레플리카도 읽기 요청을 처리할 수 있음- 장점: 부하를 분산하고 가까운 레플리카에서 데이터를 읽어와 트래픽 비용 절감
- 단점: 리더 커밋 상태를 확인해야 하므로 일관성을 유지하는 과정에서 지연 발생 가능
레플리카 상태 확인
- 리더 레플리카는 팔로워 레플리카의 복제 상태를 확인
- 복제 상태에 따라 in-sync 레플리카와 out-of-sync 레플리카로 분류
- 복제 지연 기준은
replica.lag.time.max.ms
설정으로 관리됨
리더 선출 기준
- 리더 레플리카가 장애를 겪으면 in-sync 레플리카 중에서 새로운 리더를 선출함
선호 리더 레플리카
- 선호 리더 레플리카는 파티션 생성 시 리더로 설정된 레플리카를 의미함
- 선호 리더가 in-sync 상태일 경우, 리더로 승격되어 부하 분산 유지가 가능함
6.4 요청 처리 📩
브로커와 클라이언트 통신
- 카프카 브로커는 TCP 이진 프로토콜을 사용해 클라이언트와 통신함
내부 요청 처리 로직
- Acceptor 스레드
- 브로커는 연결을 받는 포트별로 acceptor 스레드를 실행
- 요청을 processor 스레드(네트워크 스레드)로 전달
- Processor 스레드
- 받은 요청을 요청 큐에 넣음
- 완료된 응답을 응답 큐에서 받아 클라이언트로 전송
- I/O 스레드
- 요청 큐에서 요청을 가져와 처리
- 완료된 요청의 응답을 응답 큐에 추가
- Purgatory
- 응답이 지연되는 상황(예: 데이터 준비 중)에서는 요청을 purgatory에 임시 저장
- 요청 처리가 완료되면 purgatory에서 응답을 꺼내 클라이언트로 전송
메타데이터 요청 처리
- 리더 파티션 정보 요청
- 클라이언트는 리더 파티션이 있는 브로커로 읽기/쓰기 요청을 전송
- 이를 위해, 클라이언트는 주기적으로 메타데이터 요청을 아무 브로커로 보냄
- 반환된 메타데이터를 캐시하여 사용
- 메타데이터 요청 응답
- 메타데이터에는 토픽, 파티션, 레플리카, 리더 레플리카 정보 포함
- 리더 변경 시, Not a Leader 에러 발생 → 메타데이터 최신화 후 재요청
6.4.1 쓰기 요청 ✍️
acks 설정에 따른 응답 시점
acks=0
→ 요청 즉시 응답acks=1
→ 리더 브로커 쓰기 완료 시 응답acks=all
→ 모든 레플리카 복제 완료 후 응답
Purgatory 사용
- 리더 브로커는 쓰기 요청 처리 중 레플리카 응답 대기
- 대기 중 응답을 Purgatory에 저장하며, 복제 완료 시 클라이언트로 응답 전송
6.4.2 읽기 요청 처리 📖
클라이언트 요청
- 클라이언트는 읽고자 하는 토픽, 파티션, 오프셋, 데이터 한도 정보를 브로커에 전송
리더 브로커 동작
- 요청 오프셋의 유효성 검사를 수행
- 유효하지 않으면 에러 반환, 유효하면 데이터를 전송
Zero-Copy 최적화
- 리더 브로커는 데이터를 읽을 때 Zero-Copy 최적화를 사용
- 데이터 중간 버퍼 없이 네트워크 채널로 직접 전송 → 오버헤드 감소
Fetch Session Cache
- 클라이언트가 많은 파티션 데이터를 읽는 경우 변경 사항만 업데이트하여 요청 크기를 최소화
- 세션 생성/해제 제한 발생 시 적절한 에러 반환
6.4.3 기타 요청 🛠️
다양한 요청 유형
- 메타데이터, 쓰기, 읽기 외에도 다양한 요청이 존재하며, 지속적으로 새로운 요청이 추가됨
하위 호환성 유지
- 클라이언트는 상위 버전 브로커와 호환 가능
- 상위 버전 클라이언트는 하위 버전 브로커와 호환되지 않음
- 브로커를 우선적으로 업데이트하는 것이 권장됨
6.5 물리적 저장소 💾
카프카의 기본 저장 단위
- 파티션 레플리카
- 카프카는 파티션 레플리카를 기본 저장 단위로 사용
- 데이터를 저장할 때 계층화된 저장소 구조를 활용
6.5.1 계층화된 저장소 📂
기존 저장소의 문제점
- 저장 한도 제한
- 최대 보존 기한, 파티션 수, 디스크 크기에 따라 저장이 제한됨
- 확장성 문제
- 디스크 및 클러스터 크기를 과도하게 늘려야 할 수 있음
- 리밸런싱 비용 증가
- 파티션 크기가 증가하면 리밸런싱 작업에 많은 시간이 소요됨
계층화된 저장소 동작 방식
- 로컬 저장소
- 기존 방식대로 로컬 디스크에 데이터를 저장
- 원격 저장소
- 완료된 로그 세그먼트를 HDFS, S3와 같은 외부 시스템에 저장
- 보존 정책 분리
- 로컬과 원격 저장소의 보존 정책을 각각 설정 가능
- 데이터 처리 흐름
- 로컬 저장소 → 빠르게 처리해야 할 데이터를 보관
- 원격 저장소 → 완료된 데이터를 저장하고 복구 등에 활용
계층화된 저장소의 장점
- 저장소 확장이 카프카 클러스터의 CPU, 메모리 리소스와 무관
- 로컬 저장소의 부담이 줄어들어 복구 및 리밸런싱 속도 향상
- 오래된 데이터를 접근할 때 네트워크 비용만 추가되어 효율적
6.5.2 파티션 할당 📈
할당 목표
- 균등 분산
- 파티션 레플리카를 브로커에 고르게 분배
- 다른 브로커 배치
- 같은 파티션의 레플리카를 각각 다른 브로커에 배치
- 랙 분산
- 브로커에 랙 정보가 설정된 경우, 레플리카를 서로 다른 랙에 배치
추가 파티션 할당
- 새로 생성된 파티션은 가장 적은 파티션 수를 가진 브로커에 할당됨
- 파티션 크기 및 서버 부하는 고려하지 않으므로 주의 필요
6.5.3 파일 관리 📑
보존 정책
- 특정 기간을 초과하거나 데이터 양이 초과되면 파일을 삭제
세그먼트 구조
- 파티션은 여러 세그먼트로 나뉘어 저장되며, 각 세그먼트는 별도의 파일로 관리
- active 세그먼트(현재 사용 중인 세그먼트)는 삭제되지 않음
파일 디스크립터 관리
- 각 파티션의 모든 세그먼트 파일 디스크립터를 열어 유지
- 시스템의 파일 디스크립터 한도에 맞게 OS를 튜닝해야 함
6.5.4 파일 형식 📜
데이터 일관성
- 프로듀서가 생성한 데이터는 세그먼트 파일에 저장된 후 컨슈머로 전송됨
- zero-copy 최적화를 통해 중간 버퍼 없이 데이터 전송 가능
메시지 구조
- 사용자 페이로드
- 키값, 밸류값, 헤더를 포함
- 시스템 헤더
- 전송 관련 키/밸류 순서쌍 포함
메시지 배치
- 여러 메시지를 묶어 배치 단위로 전송
- 배치 헤더
- 오프셋, 타임스탬프, 체크섬, 에포크 값 등 다양한 정보를 포함
- 컨트롤 배치
- 트랜잭션 커밋/롤백, 오프셋 커밋 등 시스템 동작 관리를 위한 메시지 묶음
6.5.5 인덱스 🔍
인덱스 역할
- 특정 오프셋 메시지의 조회 속도를 개선
- 오프셋 및 타임스탬프와 세그먼트 파일 위치를 매핑
파일 구조
- 각 세그먼트 파일에는 오프셋 인덱스와 타임스탬프 인덱스가 별도로 존재
- 세그먼트가 삭제되면 해당 인덱스도 함께 삭제됨
인덱스 복구
- 인덱스가 오염될 경우 메시지를 재처리하여 인덱스를 재생성
6.5.6 압착 📦
보존 정책
- 카프카는 두 가지 보존 정책을 지원
- 삭제 보존 정책: 메시지를 기한에 따라 삭제
- 압착 보존 정책: 키 값에 대해 가장 최근의 밸류값만 유지, 이전 메시지 삭제
혼용 옵션
- 두 가지 정책을 혼합하여 사용 가능
- 최신 메시지라도 보존 기한이 초과되면 삭제됨
6.5.7 압착의 작동 원리 ⚙️
압착 활성화
- log.cleaner.enabled 설정으로 관리
- 활성화 시
- 압착 매니저 스레드 시작됨
- 여러 압착 스레드 병렬로 실행
메시지 상태
- 클린 상태: 압착이 완료된 메시지
- 더티 상태: 압착이 미완료된 메시지
- 클린/더티 분류 기준: 마지막 압착이 일어난 오프셋 기준을 기준으로 구분
압착 프로세스
- 클리너 스레드 역할
- 더티 영역 메시지를 읽고 키값-오프셋 맵 생성
- 최신 메시지만 새로운 세그먼트로 복제
- 작업 방식
- 오래된 클린 세그먼트부터 읽기 시작
- 키값 확인
- 인메모리 맵에 없는 키값 → 최신 메시지 → 복제
- 인메모리 맵에 있는 키값 → 오래된 메시지 → 건너뛰기
메모리 제한
- 클리너 스레드의 인메모리 맵은 메모리 제한 설정 가능
- 한 번에 처리 가능한 세그먼트 수가 적을 경우, 가장 오래된 세그먼트부터 압착
6.5.8 삭제된 이벤트 🗑️
삭제 방식
- 키값에 대한 데이터를 삭제하려면 밸류를 null로 설정
- 밸류가 null인 메시지는 tombstone 메시지라고 불림
tombstone 메시지 처리
- tombstone 메시지는 설정된 보존 시간 동안 유지 후 삭제
- 압착 후 결과
- 키 값의 밸류는
null
로 유지 - 이후 tombstone 메시지가 삭제되면 해당 키값도 영구 삭제됨
- 키 값의 밸류는
컨슈머와 tombstone 메시지
- 데이터베이스 동기화
- tombstone 메시지를 읽은 컨슈머가 데이터베이스에서도 삭제를 수행
- 문제 발생 가능성
- tombstone 메시지 삭제 전 컨슈머가 중단되면 데이터베이스에 데이터가 남을 수 있음
완전 삭제를 위한 방법
deleteRecords
메서드 활용- 카프카 어드민 클라이언트를 사용하여 최하위 오프셋 증가
- 지정 오프셋보다 작은 메시지는 접근 불가 → 완전 삭제 보장
6.5.9 토픽은 언제 압착되는가? ⏳
active 세그먼트와 압착
- active 세그먼트에서는 압착이 실행되지 않음
- 삭제 보존 정책에서도 active 세그먼트는 삭제되지 않는 것과 동일함
압착 대상 세그먼트 조건
- 기본적으로 세그먼트의 50% 이상이 더티 상태일 경우 압착 대상이 됨
- 모든 세그먼트 압착의 문제점
- 디스크 공간 절약 가능
- 그러나 압착 과정에서 I/O 성능 저하가 발생할 수 있으므로 무분별한 사용은 권장하지 않음
압착 관련 설정
min.compaction.lag.ms
- 메시지가 쓰여진 후 최소 경과 시간을 설정
- 설정된 시간 이전에는 압착 실행 불가
max.compaction.lag.ms
- 메시지가 쓰여진 후 압착이 최대 지연될 수 있는 시간을 설정
- 설정된 시간 내에 압착이 반드시 실행되도록 보장
This post is licensed under CC BY 4.0 by the author.