티스토리 뷰
클러스터 멤버십
아파치 주키퍼는 현재 클러스터의 멤버인 브로커들의 목록을 유지하기 위해 사용됨
각 브로커는 고유한 식별자를 가지고 브로커 프로세스는 시작될 때마다 주키퍼에 Ephemeral 노드 의 형태로 ID 를 등록함
컨트롤러를 포함한 카프카 브로커들은 브로커가 등록되는 /brokers/ids 경로를 구독함으로써 브로커가 추가되거나 제거될 때마다 알림을 받음
브로커와 주키퍼 간의 연결이 끊어질 경우 생성된 Ephemeral 노드는 자동으로 주키퍼에서 삭제되고, 이 브로커 목록을 지켜보고 이던 카프카 컴포넌트는 이 브로커가 내려갔음을 알게 됨
특정한 ID를 가진 브로커가 완전히 유실되어 동일한 ID를 가진 새로운 브로커를 투입할 경우 곧바로 클러스터에서 유실된 브로커의 자리를 대신해서 이전 브로커의 토픽과 파티션을 할당받음
컨트롤러
Zookeeper 기반 컨트롤러
주키퍼: 컨트롤러 선출 + 클러스터 메타데이터(현재 운영중인 브로커, 설정, 토픽, 파티션, 레플리카 관련 정보 등) 저장
컨트롤러 : 일반적 카프카 브로커의 기능 + 파티션 리더 선출
- 컨트롤러 선출 방식
클러스터에서 가장 먼저 시작되는 브로커는 주키퍼의 /controller 에 Ephemeral 노드를 생성함으로써 컨트롤러가 됨
다른 브로커 시작할 때 동일한 동작 하면, 노드가 이미 존재함 예외를 받게 됨 브로커들은 주키퍼의 컨트롤러 노드에 변동이 생겼을 때 알림을 받기 위해 컨트롤러 노드에 와치 를 설정
컨트롤러 브로커가 멈추거나 주키퍼와의 연결이 끊어질 경우 & zookeeper.session.timeout.ms 에 설정된 값보다 주키퍼에 하트비트를 전송하지 않을 경우 ⇒ 이 컨트롤러의 Ephemeral 노드는 삭제 다른 브로커들은 설정된 와치를 통해 컨트롤러가 없어진 것을 알게 되고, 컨트롤러 노드를 생성하려고 시도함
브로커는 새로운 컨트롤러가 선출될 때마다 주키퍼의 조건적 증가(conditional increment ) 연산에 의해 증가된 에포크(epoch ) 값을 전달받게 됨 (⇒ 따라서, 브로커는 더 낮은 에포크 값을(이전 컨트롤러가 보낸 메시지) 가진 메시지를 전달받게 될 경우 무시함)
- 컨트롤러 작동 방식
브로커가 컨트롤러가 되면 먼저 주키퍼로부터 최신 레플리카 상태 맵 을 읽어옴 이 작업은 비동기로 수행되며, 파티션 수가 많을 경우 이 적재 작업이 몇 초씩 걸릴 수 있음
컨트롤러는 특정 브로커가 클러스터를 나갔다는 사실을 알게 되면 컨트롤러는 해당 브로커가 리더를 맡고 있었던 모든 파티션에 대해 새로운 브로커를 할당해 주게 됨
- 새로운 리더가 필요한 모든 파티션을 순회하면서 새로운 리더가 될 브로커를 결정함
- 새로운 상태를 주키퍼에 씀 (요청 여러개로 나눠서, 비동기로)
- 새로 리더가 할당된 파티션의 레플리카를 포함하는 모든 브로커에 LeaderAndISR 요청 보냄
- 이 요청은 해당 파티션들에 대한 새로운 리더 & 팔로워 정보 포함
- 이 요청들은 batch 단위로 묶어서 전송, 즉 각각의 요청은 같은 브로커에 레플리카가 있는 다수의 파티션에 대한 새 리더십 정보 포함
클러스터 내의 모든 컨트롤러는 전체 브로커와 레플리카의 맵을 포함하는 MetadataCache 를 가지고 있음, 컨트롤러는 각 브로커에 리더십 변경 정보를 포함하는 UpdateMetadata 요청을 보내서 각각의 캐시를 업데이트 하도록 함
KRaft 기반 컨트롤러
ZooKeeper → Kraft 기반 으로 컨트롤러 교체 이유?
- 컨트롤러 → 주키퍼에 메타데이터 쓰는 작업은 동기적, 컨트롤러 → 브로커에 메시지를 보내는 작업은 비동기적, 주키퍼로부터 업데이트 받는 작업 비동기적 .. 따라서 컨트롤러, 주키퍼, 브로커 간 메타데이터 불일치 발생 가능
- 컨트롤러가 재시작될 때마다 주키퍼로부터 메타데이터를 읽어오고 이 메타데이터를 브로커로 전송하게 되는데 이때 파티션, 브로커 수가 증가하게 되면 컨트롤러 재시작 시간이 늘어남
- 주키퍼, 카프카 두 개의 분산 시스템에 대해서 개발자가 배워야 함 ..
Kraft 설계의 핵심 아이디어는 사용자가 상태를 이벤트 스트림으로 나타내도록 하는 로그 기반 아키텍처 를 도입
즉 로그 기반 아키텍처이므로, 로그는 이벤트 사이에 명확한 순서를 부여하며 컨슈머들이 항상 하나의 타임라인을 따라 움직이도록 보장함. 다수의 컨슈머를 사용해서 이벤트를 재생함으로써 최신 상태를 빠르게 따라잡을 수 있음
새로운 아키텍처에서 컨트롤러 노드들은 메타데이터 이벤트 로그를 관리하는 래프트 쿼럼이 됨
(쿼럼: 의사 결정에 필요한 정족수*)***
메타데이터 이벤트 로그 는 클러스터 메타데이터의 변경 내역을 저장함 (= 주키퍼에 저장되어 있는 토픽, 파티션, ISR, 설정 등등이 저장됨)
즉 외부 시스템에 의존하지 않고 컨트롤러 노드들은 자체적으로 리더를 선출할 수 있음
메타데이터 로그의 리더 역할을 맡고 있는 컨트롤러는 액티브 컨트롤러
액티브 컨트롤러 는 브로커가 보내온 모든 RPC 호출 처리하게 됨
팔로워 컨트롤러는 액티브 컨트롤러에 쓰여진 데이터를 복제하며, 액티브 컨트롤러에 장애가 발생했을 때 즉시 투입될 수 있도록 준비 상태를 유지
즉 모든 컨트롤러가 모두 최신 상태를 가지고 있으므로, 컨트롤러 장애 복구는 긴 리로드 시간을 필요로 하지 않음
다른 브로커들이 MetadataFetch API 를 사용해서 액티브 컨트롤러로부터 변경 사항을 당겨옴(pull) 컨슈머의 읽기 요청과 유사하게 브로커는 마지막으로 가져온 메타 데이터 변경 사항의 오프셋 을 추적하고 그보다 나중 업데이트만 컨트롤러에 요청, 메타데이터를 디스크에 저장
브로커 프로세스는 시작 시 컨트롤러 쿼럼 에 등록 만약 브로커가 최신 메타데이터로 최신 상태를 유지하고 있지 않다면 이 경우 fenced state 가 되서 클라이언트 요청 처리하지 않음.
- KRaft 도입 이전, Kafka에는 두 가지 프로세스 존재
- 주키퍼 프로세스: 카프카 클러스터의 동작 메타데이터 저장
- 카프카 프로세스: 카프카 데이터를 저장하는 역할, 리더 파티션 결정하는 역할을 하는 프로세스를 컨트롤러라고 함
- KRaft 이후, 주키퍼 프로세스는 제거되고 2가지 role의 카프카 프로세스만 존재
- 컨트롤러: 카프카 클러스터의 동적 메타데이터 저장, 1개 이상의 프로세스가 하나의 쿼럼 구성, 저장된 데이터의 업데이트 및 조회 작업을 담당하는 프로세스가 액티브 컨트롤러
- 브로커: 카프카 데이터 저장
즉 KRaft 이전의 컨트롤러는 파티션 리더를 결정하는 특별한 브로커 를 의미한다면 Kraft 이후의 컨트롤러는 동적 메타데이터를 저장 하는 카프카 프로세스 의미
복제
복제는 카프카 아키텍처의 핵심
복제는 카프카가 신뢰성, 지속성 보장하는 방식
카프카에서 저장되는 데이터는 토픽을 단위로 해서 조직화
각 토픽은 1개 이상의 파티션으로 분할, 각 파티션은 다수의 레플리카를 가짐
각 레플리카는 브로커에 저장
레플리카의 종류는 2가지임
- 리더 레플리카: 각 파티션에는 리더 역할을 하는 레플리카가 있음. 일관성 보장을 위해 모든 쓰기 요청은 리더 레플리카로 주어짐. 클라이언트는 리더 레플리카나 팔로워로부터 레코드 읽어옴
- 팔로워 레플리카: 별도의 설정 없이는 팔로워는 클라이언트의 요청을 처리할 수 없음 리더 레플리카로 들어온 최근 메시지를 복제함으로써 최신 상태를 유지 (리더 레플리카가 크래쉬가 날 경우 팔로워 레플리카 중 하나가 리더로 승격됨)
팔로워 레플리카로부터 읽기 기능은 KIP-392 부터 추가, 이 기능의 주요 목표는 가장 가까이에 있는 in-sync 레플리카 로부터 읽을 수 있게 함으로써 네트워크 트래픽 비용 줄임
이 기능을 사용하기 위해서는 클라이언트의 위치를 지정하는 client.rack 컨슈머 설정값을 잡고, 브로커 설정 중에는 replica.selector.class 를 잡아 줘야 함 이 설정을 RackAwareReplicaSelector (client.rack 설정값과 일치하는 rack.id 설정값을 갖는 브로커에 저장된 레플리카로부터 읽음) 로 잡아줄 수 있음
복제 프로토콜은 클라이언트가 팔로워 레플리카로부터 메시지를 읽어올 경우에도 커밋된 메시지만 읽도록 확장 리더는 팔로워에게 high-water mark (마지막으로 커밋된 오프셋) 값을 포함시킴으로써 팔로워에게 알려줌
리더 레플리카는 팔로워 레플리카가 리더 레플리카의 최신 상태를 유지하고 있는지 확인
다음과 같은 과정으로 확인할 수 있음
- 팔로워 레플리카는 리더 레플리카에 읽기 요청 보냄 (컨슈머가 메시지 읽을 때 사용하는 요청과 동일)
- 리더 레플리카는 메시지를 되돌려 줌. 리더 레플리카는 팔로워 레플리카가 요청한 마지막 메시지까지 복제를 완료했는지 이후 추가된 메시지는 없는지 확인할 수 있음
- 리더 레플리카는 각 팔로워 레플리카가 마지막으로 요청한 오프셋 값을 확인함으로써 팔로워 레플리카 복제 상태를 알 수 있음
- 만약 팔로워가 10초 이상 메시지 요청 보내지 않거나, 10 초 이상 가장 최신의 메시지를 가져가지 않을 경우 동기화가 풀린 것으로 간주하고 해당 팔로워는 out-of-sync replica 가 됨 out-of-sync replica 로 판정되기 전, replica.lag.time.max.ms 설정 매개변수에 의해 결정 됨
각 파티션은 선호 리더 가 있음, 선호 리더 는 처음 토픽이 생성될 때 리더 레플리카였던 레플리카임. 선호 리더인 이유는 파티션이 처음 생성되던 시점에서는 리더 레플리카가 모든 브로커에 걸쳐 균등하게 분포되기 때문임.
auto.leader.rebalance.enable=true 설정이 잡힐 경우, 선호 리더가 현재 리더가 아니지만 in-sync replica 로 동기화가 되고 있을 경우 선호 리더를 현재 리더로 선출
요청 처리
카프카는 TCP로 전달되는 바이너리 프로토콜(https://kafka.apache.org/protocol.html)을 사용하고 있음 이 프로토콜은 요청의 형식과 브로커가 응답하는 방식을 정의함
특정 클라이언트가 브로커로 전송한 모든 요청은 브로커가 받은 순서대로 처리됨
모든 요청은 다음과 같은 표준 헤더를 가짐
- 요청 유형
- 요청 버전: 브로커는 각 다른 버전의 클라이언트로부터 요청을 받아 각 버전에 맞는 응답을 함
- Corrleation ID: 각 요청에 붙는 고유 식별자
- 클라이언트 ID: 요청 보낸 application 식별하기 위해 사용
- 요청의 종류
- 쓰기 요청: 프로듀서가 보낸 요청
- 읽기 요청: 메시지 읽어오는 컨슈머나, 팔로워 레플리카가 보낸 요청
- 어드민 요청: 토픽 생성, 삭제와 같이 메타데이터 작업을 수행중인 어드민 클라이언트가 보낸 요청
- 브로커의 요청 처리 과정
브로커는 각 포트별로 acceptor thread 하나씩 실행, 이 억셉터 스레드는 연결을 생성하고 들어온 요청을 네트워크 스레드에 넘겨서 처리
네트워크 스레드는 클라이언트로부터 받은 요청을 받아서 요청 큐에 넣고 응답 큐에서 응답 가져다 클라이언트로 보냄 클라이언트로 보낼 응답에 지연이 필요한 경우(컨슈머의 경우 브로커에 데이터가 준비되었을 때 응답을 보낼 수 있다든지 등등), 지연된 응답은 완료될 때까지 Purgatory 에 저장됨
요청이 요청 큐에 들어오면 IO 스레드가 요청을 가져와서 처리하게 됨
- 클라이언트의 요청 처리 과정
카프카의 클라이언트는 요청에 맞는 파티션의 리더 를 맡고 있는 브로커에 쓰기나 읽기 요청을 전송할 책임을 짐
클라이언트는 클라이언트가 하는 토픽에 관해 메타데이터 요청 을 하고, 서버는 이 토픽에 어떤 파티션이 있고, 각 파티션의 레플리카는 무엇이 있고, 어떤 레플리카가 리더인지 명시 하는 응답 보냄 브롤커들이 모두 이 정보를 포함하는 메타데이터 캐시를 포함하고 있기 때문에 어떤 브로커에 보내도 상관 없음
클라이언트는 이 메타데이터 요청을 캐시해 두고 metadata.max.age.ms 매개변수 값으로 새로고침 하게 됨. 만약 요청을 보낼 때 not a leader 에러를 리터받을 경우 요청 재시도 하기 전에 메타데이터를 새로고침함
쓰기 요청
acks 매개 변수는 쓰기 작업이 성공한 것으로 간주되기 전 메시지에 대한 응답을 보내야 하는 브로커의 수 가리킴
- acks=1 : 리더만이 메시지 받을 때
- acks=all : 모든 인-싱크 레플리카들이 메시지를 받았을 때
- acks=0 : 메시지가 보내졌을 때 (브로커의 응답을 기다리지 않음)
브로커는 쓰기 요청을 받게 되면
- 사용자가 토픽에 관한 쓰기 권한이 있는지
- acks 설정값이 올바른지
- acks=all 로 잡혀있는 경우 메시지를 안전하게 쓸 만큼 인-싱크 레플리카가 있는지(인 싱크 레플리카 수가 설정된 값 아래로 내려가면 새로운 메시지를 받지 않도록 하는 설정이 있음)
브로커는 새 메시지를 로컬 디스크에 씀
리눅스의 경우 파일 시스템 캐시에 우선 쓰여지고 이들이 언제 디스크에 반영될지는 보장 X
메시지 지속성 위해 복제에 의존하는 구조
acks=all 로 설정되어 있으면 요청을 퍼거토리라 불리는 버퍼에 저장하고, 모든 브로커가 복제가 되었다면 그제서야 응답을 보냄
읽기 요청
클라이언트는 브로커에 토픽, 파티션, 오프셋 목록, 그리고 각 파티션에 대해 브로커가 리턴할 수 있는 최대 데이터의 양 요청함
요청을 받은 파티션 리더는 요청 유효한지 확인 후 (너무 오래된 메시지나 존재하지 않는 오프셋 요청할 경우 에러를 응답으로 보냄), 클라이언트가 요청에 지정한 크기 한도만큼의 메시지를 읽어 클라이언트에게 전송
파일에서 읽어온 메시지를 중간 버퍼를 거치지 않고 바로 네트워크 채널로 보냄(zero-copy 최적화)
user 영역에 데이터를 복사하고 메모리 상에 버퍼를 관리하기 위한 오버헤드가 사라짐
클라이언트는 리턴될 데이터의 양의 하한 지정 가능 ⇒ 보낼 데이터가 N 바이트 쌓이면 결과 리턴
이 경우 트래픽이 그리 많지 않은 토픽들로부터 메시지를 읽어오고 있을 때 CPU, 네트워크 사용량 감소 가능
또한 클라이언트는 타임아웃 지정 가능 ⇒ X 밀리초 안에 N 바이트만큼의 데이터가 모이지 않으면 그냥 보냄.
클라이언트는 모든 인 싱크 레플리카에 쓰여진 메시지 만을 읽을 수 있음 충분한 수의 레플리카에 복제가 완료되지 않은 메시지는 불안전한 것으로 간주, 컨슈머들간 읽어온 메시지의 일관성을 보장하기 위함임. 레플리카에 복사될 때 지연될 수 있는 시간을 replica.lag.time.max.ms 설정값으로 관리
컨슈머는 읽고 있는 파티션의 목록과 그 메타데이터를 캐시하는 읽기 세션 캐시 을 사용 따라서 클라이언트는 요청 보낼 때마다 모든 파티션을 지정할 필요 없이 점진적으로 읽기 요청 보낼 수 있고, 브로커는 변경 사항이 있을 때마다 메타데이터를 응답으로 전송함
물리적 저장소
카프카의 기본 저장 단위는 파티션 레플리카
파티션은 같은 브로커 안에 있어야 함, 같은 브로커 안의 서로 다른 디스크에 분할 저장되는 것조차 불가. 파티션의 크기는 특정 마운트 지점에 사용 가능한 공간에 제한을 받음
log.dirs 매개변수에 파티션이 저장될 디렉토리 목록을 정의
각 마운트 지점 별로 하나의 디렉토리를 포함하도록 하는 것이 일반적
계층화된 저장소
대량의 데이터를 저장하기 위한 목적
카프카의 클러스터의 저장소를 로컬과 원격으로 나눔
- 로컬: 현재 카프카 저장소 계층과 똑같이 로컬 세그먼트를 저장하기 위해 카프카 브로커의 로컬 디스크 사용
- 원격: 완료된 로그 세그먼트를 저장하기 위해 S3 같은 전용 저장소 시스템 사용
지연에 민감한 애플리케이션은 로컬 계층에 저장되어 있는 최신 레코드를 읽어오게 됨 (데이터를 전달하기 위해 페이지 캐시를 효율적으로 사용하는 카프카 매커니즘에 의해 효율적으로 작동)
따라서 로컬 저장되어 있는 데이터의 양이 줄어들게 되고 복구, 리밸런싱 과정에서 복사되어야 하는 데이터의 양 줄어들게 됨 원격 계층에 저장된 로그 세그먼트는 굳이 브로커로 복사되지 않고 바로 클라이언트로 전달
원격 계층에서의 네트워크 읽기는 로컬 읽기와 디스크 I/O, 페이지 캐시를 놓고 경합할 필요가 없기 때문에 페이지 캐시는 온전히 새 데이터를 읽고 쓰는데 사용 가능
계층화된 저장소 기능은 무한한 저장 공간, 낮은 비용, 오래된 데이터를 읽는 작업과 실시간 데이터를 읽는 작업을 분리 시킴
파티션 할당
파티션 할당 시 주요 목표
- 파티션을 가능한 한 브로커 간에 고르게 분산 시킴
- 각 파티션에 대해 각각의 레플리카는 서로 다른 브로커에 배치될 수 있도록 함
- 브로커에 랙 정보가 있다면 각각의 레플리카는 서로 다른 랙에 할당
주로 라운드 로빈 방식으로 파티션 할당
랙 인식 기능 고려 시에는 서로 다른 랙의 브로커가 번갈아 선택되도록 순서 정해야 함
새 파티션을 저장할 디렉토리를 결정할 때는 각 디렉토리에 저장되어 있는 파티션의 수를 센 뒤 가장 적은 파티션이 저장된 디렉토리에 새 파티션을 저장
주의할 점은 파티션 수 많이 고려될 뿐 크기는 고려되지 않는다는 점
파일 크기
각 토픽에 대해 보존 기한 을 설정할 수 있음 (이만큼 오래된 메시지는 지운다 OR 이 용량이 넘어가면 지운다)
하나의 파티션은 여러 개의 세그먼트로 분할되서 저장
각 세그먼트는 기본적으로 1GB 데이터 혹은 1주일 치 데이터 중 적은 쪽을 저장함
세그먼트 한도가 다 차면 그 세그먼트를 닫고 새로운 세그먼트 생성
현재 쓰여지고 있는 세그먼트는 액티브 세그먼트 임. 액티브 세그먼트는 어떠한 경우에도 삭제되지 않음
카프카 브로커는 각 파티션의 모든 세그먼트에 대해 파일 핸들을 열게 됨. 파일 핸들 수가 매우 높게 유지될 수 있으며, 운영체제를 이에 맞게 튜닝해줘야 함
파일 관리
각 세그먼트는 하나의 데이터 파일 형태로 저장
파일 안에는 카프카의 메시지와 오프셋이 저장
디스크에 저장되는 형식은 브로커로 보내고 브로커가 컨슈머로 보내지는 메시지의 형식과 유사 저장되는 형식을 통일함으로써 제로카피 최적화 & 다시 압축하는 오버헤드 줄임
카프카 메시지는 시스템 헤더와 사용자 페이로드로 이루어짐
카프카 프로듀서는 메시지를 배치 단위로 전송함, 같은 쓰기 요청에 여러 개의 배치를 포함할 수 있음
메시지 배치 헤더에 포함되는 내용은 다음과 같음
- 메시지 형식의 현재 버전
- 배치에 포함된 첫 번째 메시지의 오프셋과 마지막 오프셋과의 차이
- 첫 번째 메시지 타임 스탬프와 배치에서 가장 큰 타임스탬프
- 배치의 크기
- 배치를 받은 리더의 에포크 값
- 프로듀서 ID, 프로듀서 에포크, 배치의 첫번째 시퀀스 넘버: 정확히 한 번 구현하기 위해 사용
등등.. 각각의 레코드 역시 다음과 같은 정보 포함
- 레코드 크기
- 현재 레코드 오프셋과 배치 내 첫번째 오프셋과의 차이
- 타임스탬프 현재 레코드와 배치 내 첫번째 레코드 차이
- 사용자 페이로드
차이만 저장함으로써 레코드의 오버헤드 줄임
인덱스
브로커가 주어진 오프셋의 메시지를 빠르게 찾을 수 있도록 하기 위해 카프카는 각 파티션에 대해 오프셋을 유지함 인덱스는 오프셋과 세그먼트 파일 및 그 안에서의 위치를 매핑함
또한 타임 스탬프와 메시지 오프셋을 매핑하는 인덱스 를 가지고 있으며, 타임 스탬프 기준으로 메시지 찾을 때 유용
인덱스 역시 세그먼트 단위로 분할.
인덱스 오염될 경우 세그먼트에 포함된 메시지를 다시 읽어서 오프셋과 위치를 기록
압착
https://developer.confluent.io/courses/architecture/compaction/
삭제 보존 정책: 지정된 보존 기한보다 더 오래된 이벤트들을 삭제
압착 보존 정책: 토픽에서의 각 키의 가장 최근값만 저장
클린: 이전에 압착된 적이 있었던 메시지. 하나의 키마다 하나의 값만 포함
더티: 마지막 압착 작업 이후 쓰여진 메시지
압착 기능이 활성화(log.cleaner.enabled ) 되어 있을 경우 각 브로커는 압착 매니저 스레드와 함께 다수의 압착 스레드를 시작
이 스레드는 압착 작업을 담당, 각 스레드는 전체 파티션 크기 대비 더티 메시지으 ㅣ비율이 가장 높은 파티션을 골라서 압착
파티션 압착하기 위해 스레드는 더티 영역을 읽어서 인 메모리 맵 형성
맵의 각 항목은 메시지 키의 16비트 해시와, 같은 키 값을 갖는 이전 메시지의 오프셋으로 이루어짐
압착 스레드가 이 오프셋 맵을 저장하기 위해 사용할 수 있는 메모리의 양을 운영자가 잡아줄 수 있음 이 맵에 전체 더티 영역이 들어갈 필요는 없지만 최소 하나 세그먼트 전체는 들어갈 수 있어야 함
클리너 스레드는 클린 세그먼트들을 오래된 것부터 읽어들여 오프셋 맵의 내용과 대조
현재 메시지의 키 값이 현재 오프셋 맵에 저장되어 있지 않다면 교체용 세그먼트로 복사
저장되어 있으면 파티션 안에 같은 키 값 가졌지만 최신 밸류 갖는 메시지가 있다는 의미이므로 해당 메시지는 건너 뛰게됨.
키 값에 대한 최신 밸류값을 갖는 모든 메시지들이 복사되고 나면 압착 스레드는 교체용 세그먼트와 원본 세그먼트를 바꾼 후 다음 세그먼트로 계속 진행하게 됨. 사
삭제된 이벤트
특정 키 값을 갖는 메시지를 삭제하고 싶을 때는 해당 키 값과 null 값을 갖는 메시지 써주면 됨(위의 이미지에서 Tim 과 같은 케이스)
카프카는 이 특별한 메시지(툼스톤(tombstone ) 을 보존하고 컨슈머는 이 메시지를 보고 해당 value는 삭제되었음을 알게 됨. 컨슈머가 툼스톤 메시지를 볼 수 있도록 하는 충분한 시간을 줘야 함. 그렇지 않으면 컨슈머가 데이터를 읽을 때 이 메시지가 카프카에서 삭제된 메시지인지, 데이터베이스에서 지워줘야 하는 메시지인지 구분하지 못함
카프카 어드민 클라이언트에서는 deleteRecords 메서드가 있음
이 메서드는 지정된 오프셋 이전의 모든 레코드 삭제, 동작 방식은 파티션의 첫 번째 레코드를 가리키는 low-water mark 를 해당 오프셋으로 이동 시켜 그 이전의 오프셋을 갖는 메시지는 접근 불가능하게 함 .
토픽은 언제 압착되는가?
액티브 세그먼트는 압착되지 않음.
토픽 내용물의 50%가 더티 레코드인 경우에만 압착 (토픽을 지나치게 자주 압착하지 않으면서 너무 많은 더티 레코드가 존재하는 것을 막는다)
- min.compaction.lag.ms: 메시지가 쓰여진 뒤 압착될 때까지 흘러야 하는 최소 시간
- max.compaction.lag.ms: 메시지가 쓰여진 뒤 압착이 가능해질 때까지 딜레이 될 수 있는 최대 시간