Kafka 내부 메커니즘
카프카의 내부를 들여다보자
이번 시간에는 크게 세 가지 주제에 대해서 학습해볼 것이다.
카프카 복제가 동작하는 방법
카프카 프로듀서와 컨슈머의 요청을 처리하는 방법
카프카가 스토리지 (파일 형식이나 인덱스)를 처리하는 방법
클러스터 멤버십
클러스터 멤버십은 카프카 브로커, 주키퍼, 컨슈머, 프로듀서를 의미한다.
데이터 저장하는 노드를 znode라고 하며 이 znode들은 주키퍼에게 Watch를 요청하며, Watch의 콜백으로 해당 노드가 살아있는지 유무를 판단할 수 있다.
브로커가 중단되면 해당 브로커의 주키퍼 노드는 삭제된다.
컨트롤러
컨트롤러는 카프카 브로커 중 하나이며, 일반 브로커의 기능에 추가하여 파티션 리더를 선출하는 책임을 갖는다.
카프카는 주키퍼의 임시 노드를 사용해서 컨트롤러를 선출한다.
브로커가 추가, 또는 중단되어 임시 노드가 추가되거나 삭제될 대 주키퍼의 Watch를 통해 모든 브로커가 노드의 변경을 알 수 있다.
컨트롤러는 리더를 선출하는 책임을 가지며 주키퍼가 부여한 세대 번호를 컨트롤러에 사용하여 변경 전의 컨트롤러와의 혼동을 막고 컨트롤러를 인식할 수 있다.
복제
레플리카는 카프카 아키텍처의 핵심이다.
리더 레플리카는 각 파티션은 리더로 지정된 하나의 레플리카를 갖는다.
즉 파티션 별로 리더가 선출된다.
팔로어 레플리카는 파티션의 리더를 제외한 레플리카를 팔로어라고 부른다.
한번 리더로 선출된 리더는 선호 리더라고 불리며 나중에 리더로 선출 될 때 조금 더 우선순위가 높게 책정된다.
선호 리더들이 클러스터이 모든 파티션 리더일 경우 브로커의 파티션 배분이 고르게 된다고 한다.
요청 처리
카프카는 TCP로 전송되는 이진 프로토콜을 갖고 있다.
모든 요청은 다음의 내용을 포함하는 헤더를 갖는다.
요청 타입 ID : 어떤 요청인지를 나타내는 16비트 정수 형식의 고유 번호
요청 버전 : 이 요청의 프로토콜 API 버전을 나타냄
cID(correlation ID) : 각 요청의 고유 식별번호, 32비트 정수형 값
클라이언트 ID : 사용자가 지정한 문자열 형식의 값이며 null이 될 수 있다.
브로커는 자신이 리스닝하는 각 포트에 대해 acceptor
스레드를 실행하며, 이 스레드는 연결을 생성하고 processor 스레드가 그 다음을 처리하도록 넘겨준다.
processor
스레드는 클라이언트 연결로부터 요청을 받고, 요청 큐에 전달하면 응답 큐로부터 응답을 가져와서 클라이언트에게 전송하는 일을 수행한다.
쓰기 요청은 프로듀서가 전송하며 카프카 브로커에 쓰려는 메시지를 포함한다.
읽기 요청은 카프카 브로커로부터 메시지를 읽을 때 컨슈머와 팔로어 레플리카가 전송한다.
쓰기 요청, 읽기 요청 모두 파티션의 리더 레플리카에 전송되어야한다.
클라이언트는 어디로 요청을 전송할지 어떻게 알 수 있을까?
카프카 클라이언트는 메타데이터 요청이라는 또 다른 요청 타입을 사용하는데, 이것은 클라이언트가 관심을 갖는 토픽 내역을 포함한다.
메타데이터 요청은 어떤 브로커에도 전송할 수 있으며, 모든 브로커가 그런 정보를 포함하는 메타데이터 캐시를 갖고 있다.
쓰기 요청
특정 파티션의 리더 레플리카를 포함하는 브로커가 해당 파티션의 쓰기 요청을 받으면 다음 사항의 검사를 수행한다.
데이터를 전송한 사용자가 해당 토픽의 쓰기 권한을 갖는가?
해당 요청에 지정된 acks의 값이 0,1,all 중 하나를 갖는가?
만일 acks가 all이라면 메시지를 안전하게 쓰는데 충분한 동기화와 레플리카가 존재하는가?
읽기 요청
브로커는 쓰기 요청을 처리하는 방식과 유사하게 읽기 요청을 처리한다.
카프카는 제로 카피(zero-copy) 기법을 사용해서 클라이언트에게 메시지를 전송한다.
이 제로카피란 버퍼 메모리를 쓰지 않고 바로 네트워크 채널로 보낸다는 것이다.
이는 데이터를 클라이언트에게 전송하기 전에 로컬 캐시 메모리에 저장하는 대부분의 데이터베이스와는 다른 모습이다.
브로커에게 데이터의 상한, 하한 크기를 지정할 수 있다.
하한의 경우 최소 이정도 데이터가 될 때 까지만 전송해라라는 것이다.
이는 네트워크 트래픽이 많지 않은 토픽으로부터 클라이언트가 메시지를 읽을 때 CPU와 네트워크 사용을 줄일 수 있는 좋은 방법이다.
모든 동기화 레플리카들이 메시지를 쓸 때 까지 컨슈머에게 전송되지 않는다.
그 이유는, 레플리카들에게 복제되지 않은 메시지들은 불안전한 메시지이다.
만일 리더가 중단되어 다른 레플리카로 리더가 선출되면 모든 레플리카에 복제되지 않은 메시지들은 더 이상 카프카에 존재하게 되지 않기 떄문이다.
일관성이 결여될 수 있다.
스토리지
카프카의 기본적인 스토리지 단위는 파티션 레플리카다.
하나의 파티션은 여러 브로커 간에 분할될 수 없다.
파티션 할당
토픽을 생성할 때 카프카는 제일 먼저 여러 브로커 간에 파티션을 할당하는 방법을 결정한다.
파티션 할당은 다음과 같다.
- 파티션 레플리카를 브로커 간에 고르게 분산시킨다.
- 각 파티션의 레플리카는 서로 다른 브로커와 할당한다.
- 만일 브로커가 rack 정보를 갖고 있다면 가능한 한 각 파티션의 레플리카는 서로 다른 rack에 있는 것으로 간주한다.
파일 관리
보존(retention)은 카프카의 중요한 개념이다. 메시지를 삭제하기 전에 얼마나 보관해야할지에 대한 시간과 메시지의 크기를 설정할 수 있다.
큰 파일에서 제거해야하는 메시지를 찾아 파일의 일부분을 제거하는 것은 에러가 생길 수 있다.
따라서 카프카에는 각 파티션을 세그먼트로 나눈다.
메시지를 쓰기 위해 사용중인 세그먼트를 액티브 세그먼트라고 부른다.
파일 형식
각 세그먼트는 하나의 데이터 파일로 생성되며 카프카 메시지와 오프셋들이 저장된다.
이는 디스크에 저장, 관리 된다.
프로듀서가 압축된 메시지를 전송한다면 하나의 배치(batch)에 포함된 모든 메시지가 같이 압축되어 wrapper 메시지의 값으로 전송된다.
프로듀서가 압축을 사용한다면 더 큰 배치를 전송해도 네트워크와 브로커 디스크 모두에서 유리하다.
단 컨슈머가 사용하는 메시지를 변경하는 경우라면 전송 프로토콜과 디스크 수록 형식을 모두 변경해야하며, 이를 브로커가 알아야한다.
인덱스
카프카 컨슈머가 특정 오프셋부터 메시지를 빠르게 읽을 수 있게 도와준다.
압축
여기서의 압축은 보존 기간이 지난 메시지를 제거해서 가장 최근의 메시지만 남겨두는 기법을 의미한다.
압축 처리 방법
키와 값 형태로 메시지를 수록하는 각 로그 세그먼트는 다음의 두 부분으로 나뉜다.
클린 : 이전에 압축되었던 메시지들이 있다.
더티 : 직전 압축 이후에 추가로 쓴 메시지들이 저장된 부분
삭제된 메시지
툼스톤 메시지는 value를 null을 값으로 하는 것을 의미한다.
이러한 툼스톤 메시지가 갖는 의미는 나중에 압축 스레드에서 툼스톤 메시지들을 삭제할 것이고, 해당 키의 메시지는 파티션에서 없어지는 효과를 가져온다.
토픽은 언제 압축되는가?
압축 보존 정책에서도 현재 사용중인 세그먼트는 압축하지 않는다.
사용 중이 아닌 세그먼트의 메시지들이 압축 대상이 된다.
Reference
카프카 핵심 가이드