ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 카프카 핵심 가이드 4 : 내부 메커니즘
    책책책 책을 읽읍시다/프로그래밍 2023. 6. 11. 15:47

    6. 내부 메커니즘


    복제

     복제는 카프카 아키텍처의 핵심이다. 실제로 카프카는 '분산되고, 분할되고, 복제된 커밋 로그 서비스'로 표현되기도 한다. 복제가 중요한 이유는 개별적인 노드에 필연적으로 장애가 발생할 수밖에 없는 상황에서 카프카가 신뢰성과 지속성을 보장하는 방식이기 때문이다.

     카프카에 저장되는 데이터는 토픽을 단위로 해서 조직화한다. 각 토픽은 1개 이상의 파티션으로 분할되며, 각 파티션으 다시 다수의 레플리카를 가질 수 있다. 각각의 레플리카는 브로커에 저장되는데, 대개 하나의 브로커는 (서로 다른 토픽과 파티션에 속하는) 수백 개에서 심지어 수천 개의 레플리카를 저장한다.

     레플리카에는 두 종류가 있다.

    • 리더 레플리카 : 각 파티션에는 리더 역할을 하는 레플리카가 하나씩 있다. 일관성을 보장하기 위해, 모든 쓰기 요청은 리더 레플리카로 주어진다. 클라이언트들은 리더 레플리카나 팔로워로부터 레코드를 읽어올 수 있다.
    • 팔로워 레플리카 : 파티션에 속한 모든 레플리카 중에서 리더 레플리카를 제외한 나머지를 팔로워 레플리카라고 한다. 별도로 설정을 잡아주지 않는 한, 팔로워는 클라이언트의 요청을 처리할 수 없다. 이들이 주로 하는 일은 리더 레플리카로 들어온 최근 메세지들을 복제함으로써 최신 상태를 유지하는 것이다. 만약 해당 파티션의 리더 레플리카에 크래쉬가 날 경우, 팔로워 레플리카 중 하나가 파티션의 새 리더 파티션으로 승격된다.

     리더 역할 레플리카(를 저장하는 브로커)가 수행하는 또 다른 일은 어느 팔로워 레플리카가 리더 레플리카의 최신 상태를 유지하고 있는지를 확인하는 것이다. 팔로워 레플리카(를 저장하는 브로커)는 새로운 메세지가 도착하는 즉시 리더 레플리커로부터 모든 메세지를 복제해 옴으로써 최신 상태를 유지할 수 있도록 하지만, 다양한 원인으로 인해 동기화가 깨질 수 있다. 네트워크 혼잡(Network Congestion)으로 인해 복제 속도가 느려진다거나, 브로커가 크래시가 나는 바람에 브로커가 재시작되어 복제 작업을 다시 시작할 수 있게 될 때까지 해당 브로커에 저장되어 있는 모든 레플리카들의 복제 상태가 뒤처지는 사태가 여기에 해당한다.

     리더 레플리카와의 동기화를 유지하기 위해 팔로워 레플리카들은 리더 레플리카에 읽기 요청을 보낸다. 이 요청은 컨슈머가 메세지를 읽어오기 위해 사용하는 바로 그 요청이기도 하다. 이러한 요청에 대한 응답으로 리더 레플리카(를 저장하고 있는 브로커)는 메세지를 되돌려 준다. 이 읽기 요청들은 복제를 수행하는 입장에서 다음번에 받아야 할 메세지 오프셋을 포함할 뿐만 아니라 언제나 메세지를 순서대로 돌려 준다. 즉, 리더 레플리카 입장에서는 팔로워 레플리카가 요청한 마지막 메세지까지 복제를 완료했는지 이후 새로 추가된 메세지가 없는지의 여부를 알 수 있는 것이다. 리더 레플리카는 각 팔로워 레플리카가 마지막으로 요청한 오프셋 값을 확인함으로써 각 팔로워 레플리카가 얼마나 뒤쳐져 있는지를 알 수 있다. 만약 팔로워 레플리카가 10초 이상 메세지 요청을 보내지 않거나 10초 이상 가장 최근의 메세지를 가져가지 않을 경우 해당 레플리카는 동기화가 풀린 것으로 간주된다('아웃-오브-싱크 레플리카 out-of-sync replica'). 팔로워 레플리카가 리더 레플리카를 따라가는 데 실패한다면 해당 레플리카는 더 이상 장애 상황에서 리더가 될 수 없다. 어찌 되었든 해당 레플리카가 모든 메세지를 가지고 있는 것은 아니기 때문이다.

     반대로 지속적으로 최신 메세지를 요청하고 있는 레플리카는 '인-싱크 레플리카(in-sync replica)'라고 부른다. 현재 리더에 장애가 발생할 경우 인-싱크 레플리카만이 파티션 리더로 선출될 수 있다.

     팔로워 레플리카가 아웃-오브-싱크 레플리카로 판정되기 전, 비활성 상태이거나 뒤쳐진 상태일 수 있는 시간은 replica.lag.time.max.ms 설정 매개변수에 의해 결정된다. 이렇게 허용될 수 있는 랙의 양은 클라이언트의 작동이나 리더 선출 과정에 있어서의 데이터 보존에도 영향을 미친다.

     '현재 리더'에 더하여, 각 파티션은 '선호 리더(preferred leader)'를 갖는다. '선호 리더'란 토픽이 처음 생성 되었을 때 리더 레플리카였던 레플리카를 가리킨다. 파티션이 처음 생성되던 시점에서는 리더 레플리카가 모든 브로커에 걸쳐 균등하게 분포되기 때문에 '선호'라는 표현이 붙었다. 결과적으로 클러스터 내의 모든 파티션에 대해 선호 리더가 실제 리더가 될 경우 부하가 브로커 사이에 균등하게 분배될 것이라고 예상할 수 있다. 카프카에는 auto.leader.rebalance.enable=true 설정이 기본적으로 잡혀 있다. 이 설정은 선호 리더가 현재 리더가 아니지만, 현재 리더와 동기화가 되고 있을 경우 리더 선출을 실행시킴으로써 선호 리더를 현재 리더로 만들어 준다.

    요청 처리

     카프카 브로커가 하는 일의 대부분은 클라이언트, 파티션 레플리카, 컨트롤러가 파티션 리더에게 보내는 요청을 처리하는 것이다. 카프카는 TCP로 전달되는 이진 프로토콜을 가지고 있다. 이 프로토콜은 요청의 형식과 (요청이 성공적으로 처리되었거나 아니면 요청을 처리하는 와중에 에러가 발생했을 경우) 브로커가 응답하는 방식을 정의한다.

     아파치 카프카 프로젝트는 기여자들에 의해 구현되고 유지보수되는 자바 클라이언트를 포함한다. 물론 C, 파이썬, Go 등 다른 언어에서 사용 가능한 클라이언트도 있다. 전체 목록은 아파치 카프카 웹사이트(https://cwiki.apache.org/confluence/display/KAFKA/Clients)에서 볼 수 있다. 이 클라이언트들 모두가 이 프로토콜을 사용해서 카프카 브로커와 통신한다.

     언제나 클라이언트가 연결을 시작하고 요청을 전송하며, 브로커는 요청을 처리하고 클라이언트로 응답을 보낸다. 특정 클라이언트가 브로커로 전송한 모든 요청은 브로커가 받은 순서대로 처리된다. 즉, 그렇기 때문에 카프카가 저장하는 메세지는 순서가 보장되며, 카프카를 메세지 큐로 사용할 수도 있는 것이다.

     모든 요청은 다음과 같은 내용을 포함하는 표준 헤더를 갖는다.

    • 요청 유형 : API 키라고도 불린다.
    • 요청 버전 : 브로커는 서로 다른 버전의 클라이언트로부터 요청을 받아 각각의 버전에 맞는 응답을 할 수 있다.
    • Correlation ID : 각각의 요청에 붙는 고유한 식별자. 응답이나 에러 로그에도 포함된다(트러블슈팅에 사용할 수 있다).
    • 클라이언트 ID : 요청을 보낸 애플리케이션을 식별하기 위해 사용한다.

     프로토콜의 상세한 내용은 카프카 공식 문서(https://kafka.apache.org/protocol.html)에 기술되어 있다.

     브로커는 연결을 받는 각 포트별로 억셉터(acceptor) 스레드를 하나씩 실행시킨다. 억셉터 스레드는 연결을 생성하고 돌아온 요청을 프로세서(processor) 스레드에 넘겨 처리하도록 한다. 프로세스 스레드(네트워크 스레드라고 부르기도 한다)의 수는 설정이 가능하다. 네트워크 스레드는 클라이언트 연결로부터 들어온 요청들을 받아서 요청 큐에 넣고, 응답 큐에서 응답을 가져다 클라이언트로 보낸다. 가끔은 클라이언트로 보낼 응답에 지연이 필요한 때가 있다. 즉, 컨슈머의 경우 브로커 쪽에 데이터가 준비되었을 떄에만 응답을 보낼 수 있고, 어드민 클라이언트의 경우 토픽 삭제가 진행중인 상황에서만 DeleteTopicrequest 요청에 대한 응답을 보낼 수 있는 것이다. 지연된 응답들은 완료될 때까지 퍼거토리(pugatory, https://www.confluent.io/blog/apache-kafka-purgatory-hierarchical-timing-wheels/)에 저장된다.

    아파치 카프카 내부의 요청 처리

     일단 요청이 요청 큐에 들어오면, I/O 스레드('요청 핸들러 Request Handler'라고도 불린다)가 요청을 가져와서 처리하는 일을 담당한다. 가장 일반적인 형태의 클라이언트 요청 유형은 다음과 같다.

    • 쓰기 요청 : 카프카 브로커로 메세지를 쓰고 있는 프로듀서가 보낸 요청
    • 읽기 요청 : 카프카 브로커로부터 메세지를 읽어오고 있는 컨슈머나 팔로워 레플리카가 보낸 요청
    • 어드민 요청 : 토픽 생성이나 삭제와 같이 메타데이터 작업을 수행중인 어드민 클라이언트가 보낸 요청

     쓰기 요청과 읽기 요청 모두 파티션의 리더 레플리카로 전송되어야 한다. 만약 브로커가 다른 브로커가 리더를 맡고 있는 파티션에 대한 쓰기 요청을 받을 경우, 쓰기 요청을 보낸 클라이언트는 'Not a Leader for Partition' 에러를 응답으로 받을 것이다. 해당 브로커가 리더를 맡고 있지 않은 파티션에 대한 읽기 요청을 받을 경우에도 동일한 에러가 발생한다. 카프카의 클라이언트는 요청에 맞는 파티션의 리더를 맡고 있는 브로커에 쓰기나 읽기 요청을 전송할 책임을 진다.

     클라이언트는 어디로 요청을 보내야 하는지 어떻게 아는 것일까? 카프카 클라이언트는 메타데이터 요청이라 불리는 또 다른 유형의 요청을 사용한다. 이 요청은 클라이언트가 다루고자 하는 토픽들의 목록을 포함한다. 서버는 이 토픽들에 어떤 파티션들이 있고, 각 파티션의 레플리카에는 무엇이 있으며, 어떤 레플리카가 리더인지를 명시하는 응답을 리턴한다. 메타데이터 요청은 아무 브로커에나 보내도 상관이 없다. 모든 브로커들이 이러한 정보를 포함하는 메타데이터 캐시를 가지고 있기 때문이다.

     클라이언트는 보통 이 정보를 캐시해 두었다가 이 정보를 사용해서 각 파티션의 리더 역할을 맡고 있는 브로커에 바로 쓰거나 읽는다. 클라이언트는 토픽 메타데이터가 변경될 경우에도 최신값을 유지해야 하는 만큼 때때로 새로운 메타데이터 요청을 보내서 이 정보를 새로고침할 필요가 있다(새로고침 간격은 metadata.max.age.ms 설정 매개변수로 조절 가능하다). 예를 들어서, 새 브로커가 추가되거나 일부 레플리카가 새 브로커로 이동한 경우가 여기에 해당한다. 또한, 만약 클라이언트가 요청에 대해 'Not a Leader' 에러를 리턴받을 경우 요청을 재시도하기 전에 메타데이터 먼저 새로고침한다. 이 에러는 클라이언트가 이미 만료된 정보를 사용중이라는 것 그리고 잘못된 브로커에게 요청을 전송중이라는 것을 의미하기 때문이다.

    클라이언트의 요청 라우팅

    쓰기 요청

     acks 설정 매개변수는 쓰기 작업이 성공한 것으로 간주되기 전 메세지에 대한 응답을 보내야 하는 브로커의 수를 가리킨다. 어느 시점에서 메세지가 '성공적으로 쓰여졌다'라고 간주되는지는 프로듀서 설정을 통해 바꿀 수 있다.

    • acks=1 : 리더만이 메세지를 받았을 때
    • acks=all : 모든 인-싱크 레플리카들이 메세지를 받았을 때
    • acks=0 : 메세지가 보내졌을 때, 즉, 브로커의 응답을 기다리지 않음

    파티션의 리더 레플리카를 가지고 있는 브로커가 해당 파티션에 대한 쓰기 요청을 받게 되면 몇 가지 유효성 검증부터 한다.

    • 데이터를 보내고 있는 사용자가 토픽에 대한 쓰기 권한을 가지고 있는가?
    • 요청에 지정되어 씨는 acks 설정값이 올바른가? (0, 1, 아니면 'all'만이 사용 가능)
    • 만약 acks 설정값이 all로 잡혀 있을 경우, 메세지를 안전하게 쓸 수 있을 만큼 충분한 인-싱크 레플리카가 있는가?(현재 인-싱크 레플리카 수가 설정된 값 아래로 내려가면 새로운 메세지를 받지 않도록 브로커를 설정해 줄 수 있다.)

     그러고 나서 브로커는 새 메세지들을 로컬 디스크에 쓴다. 리눅스의 경우 메세지는 파일시스템 캐시에 쓰여지는데, 이들이 언제 디스크에 반영될지에는 보장이 없다. 카프카는 데이터가 디스크에 저장될 때까지 기다리지 않는다. 즉, 메세지의 지속성을 위해 복제에 의존하는 것이다.

     메세지가 파티션 리더에 쓰여지고 나면, 브로커는 acks 설정에 따라 응답을 내려보낸다. 만약 0이나 1로 설정되어 있다면 바로 응답을 내려보내지만, all로 설정되어 있다면 일단 요청을 퍼거토리라 불리는 버퍼에 저장한다. 그리고 팔로워 레플리카들이 메세지를 복제한 것을 확인한 다음에야 클라이언트에 응답을 돌려보낸다.

    읽기 요청

     브로커는 쓰기 요청이 처리되는 것과 매우 유사한 방식으로 읽기 요청을 처리한다. 클라이언트는 브로커에 토픽, 파티션 그리고 오프셋 목록에 해당하는 메세지들을 보내 달라는 요청을 보낸다. 대략 "Test 토픽의 파티션 0 오프셋 53부터의 메세지와 파티션 3의 오프셋 64부터의 메세지를 보내주세요."라는 식이다. 클라이언트는 각 파티션에 대해 브로커가 리턴할 수 있는 최대 데이터의 양 역시 지정한다. 이것이 필요한 이유는, 클라이언트는 브로커가 되돌려준 응답을 담을 수 있을 정도로 충분히 큰 메모리를 할당해야 하기 때문이다. 이러한 한도값이 없을 경우 브로커는 클라이언트가 메모리 부족에 처할 수 있을 정도로 큰 응답을 보낼 수도 있는 것이다.

     요청은 요청에 지정된 파티션들의 리더를 맡고 있는 브로커에 전송되어야 하며, 클라이언트는 읽기 요청을 정확히 라우팅할 수 있도록 필요한 메타데이터에 대한 요청을 보내게 된다. 요청을 받은 파티션 리더는 먼저 요청이 유효한지를 확인한다(지정된 오프셋이 해당 파티션에 존재하는지?). 만약 클라이언트가 너무 오래되어 파티션에서 삭제된 메세지나 아직 존재하지 않는 오프셋의 메세지를 요청할 경우 브로커는 에러를 응답으로 보내게 된다.

     만약 오프셋이 존재한다면 브로커는 파티션으로부터 클라이언트가 요청에 지정한 크기 한도만큼의 메세지를 읽어서 클라이언트에게 보내 준다. 카프카는 클라이언트에게 보내는 메세지에 제로카피(zero-copy) 최적화를 적용하는 것으로 유명하다. 즉, 파일(좀 더 정확히 이야기하자면, 리눅스의 파일시스템 캐시)에서 읽어 온 메세지들을 중간 버퍼를 거치지 않고 바로 네트워크 채널로 보내는 것이다. 이 점이 클라이언트에게 데이터를 보내기 전에 로컬 캐시에 저장하는 대부분의 데이터베이스와의 차이점이다. 이 방식을 채택함으로써 데이터를 복사하고 메모리 상에 버퍼를 관리하기 위한 오버헤드가 사라지며, 결과적으로 성능이 향상된다.

     브로커가 리턴할 수 있는 데이터 양의 상한을 지정하는 것에 더해서, 클라이언트는 리턴될 데이터의 양의 하한 역시 지정할 수 있다. 예를 들어서 하한을 10K로 잡아준다면, 클라이언트가 브로커에게 "보낼 데이터가 최소한 10K 바이트 쌓이면 결과를 리턴해라."라고 이야기하는 셈이 되는 것이다. 이것은 클라이언트가 트래픽이 그리 많지 않은 토픽들로부터 메세지를 읽어오고 있을 때 CPU와 네트워크 사용량을 감소시키는 좋은 방법이다. 클라이언트가 몇 밀리초마다 브로커에게 데이터 요청을 보내서 메세지가 몇 개만 들어 있거나 심지어 텅 빈 응답을 받는 대신, 클라이언트가 요청을 보냈을 때 브로커는 충분한 양의 데이터가 모일 때까지 기다린 뒤 리턴하면 그제서야 클라이언트가 추가 데이터를 요청하는 것이다. 전체적으로는 같은 양의 데이터를 읽지만, 데이터를 주고받는 횟수는 훨씬 적으므로 오버헤드도 그만큼 적다.

    충분한 데이터가 누적될 떄까지 브로커가 응답을 유보하는 상황

     물론 브로커가 충분한 데이터를 가질 때까지 클라이언트가 마냥 기다리기만 하는 것을 원하지는 않을 것이다. 시간이 조금 지난 후에는 있는 데이터라도 가져다 처리를 하는 편이 계속해서 기다리는 편보다 낫다. 따라서, 클라이언트 입장에서는 브로커에게 요청을 할 때 타임아웃 역시 지정해 줄 수 있다. 즉, "만약 x밀리초 안에 하한만큼의 데이터가 모이지 않으면 그냥 있는 것이라도 보내라."라고 할 수 있다.

     파티션 리더에 존재하는 모든 데이터를 클라이언트가 읽을 수 있는 건 아니라는 걸 알아두자. 대부분의 클라이언트는 모든 인-싱크 레플리카에 쓰여진 메세지들만을 읽을 수 있을 뿐이다. (팔로워 레플리카들 역시 컨슈머이기는 하지만, 이 룰에서는 예외다. 그렇지 않으면 복제 기능이 작동하지 않을 것이다.) 앞에서 본 것처럼, 파티션 리더는 어느 메세지가 어느 레플리카로 복제되었는지 알고 있으며, 특정 메세지가 모든 인-싱크 레플리카에 쓰여지기 전까지는 컨슈머들이 읽을 수 없다(이 메세지들을 읽으려 하면 에러가 발생하는 게 아니라 빈 응답이 리턴되게 된다).

     이러한 작동의 이유는 다음과 같다. 충분한 수의 레플리카에 복제가 완료되지 않은 메세지는 '불안전한' 것으로 간주된다. 만약 리더에 크래시가 발생해서 다른 레플리카가 리더 역할을 이어받는다면, 이 메세지들은 더 이상 카프카에 존재하지 않게 된다. 만약 클라이언트가 이렇게 리더에만 존재하는 메세지들을 읽을 수 있도록 한다면, 크래시 상황에서 일관성이 결여될 수 있는 것이다. 예를 들어서, 컨슈머가 어떤 메세지를 읽은 상태에서 리더 브로커가 크래시 나고, 다른 브로커에 해당 메세지가 복제된 적이 없다면, 이 메세지는 사라진다. 이제 다른 컨슈머들 입장에서는 해당 메세지를 읽을 길이 없기 때문에 컨슈머들이 읽어 온 메세지들 사이에 불일치가 발생한다. 대신, 모든 인-싱크 레플리카가 메세지를 받을 때까지 기다린 뒤에야 컨슈머가 읽을 수 있도록 한다. 그렇기 때문에 어떠한 이유로 브로커 사이의 복제가 늦어지면, 새 메세지가 컨슈머에 도달하는 데 걸리는 시간도 길어진다(메세지 복제가 완료될 때까지 기다려야 하니까). 이렇게 지연되는 시간은 replica.lag.time.max.ms 설정값에 따라 제한된다. 이 값은 인-싱크 상태로 판정되는 레플리카가 새 메세지를 복제하는 과정에서 지연될 수 있는 최대 시간이기도 하다(즉, 이 시간 이상으로 지연되면 아웃-오브0싱크 레플리카가 된다).

    컨슈머는 모든 인-싱크 레플리카에 복제된 메세지들만 읽을 수 있다

     컨슈머가 매우 많은 수의 파티션들로부터 이벤트를 읽어오는 경우가 있다. 이 경우가 읽고자 하는 파티션의 전체 목록을 요청을 보낼 때마다 브로커에 전송하고, 다시 브로커는 모든 메타데이터를 돌려 보내는 방식은 매우 비효율적일 수 있다. 즉, 읽고자 하는 파티션의 집합이나 여기에 여과된 메타데이터는 여간해서는 잘 바뀌지 않는 데다가, 많은 경우 리턴해야 할 메타데이터가 그렇게 많지도 않다. 이러한 오버헤드를 최소화하기 위해 카프카는 읽기 세션 캐시(fetch session cache)를 사용한다. 컨슈머는 읽고 있는 파티션의 목록과 그 메타데이터를 캐시하는 세션을 생성할 수 있다. 세션이 한 번 생성되면, 컨슈머들은 더 이상 요청을 보낼 때마다 모든 파티션을 지정할 필요 없이 점진적으로 읽기 요청을 보낼 수 있다. 브로커는 변경 사항이 있는 경우에만 응답에 메타데이터를 포함하면 된다. 세션 캐시의 크기에도 한도가 있는 만큼 카프카는 팔로워 레플리카나 읽고 있는 파티션의 수가 더 많은 컨슈머를 우선시할 수밖에 없으며, 따라서 어떤 경우에는 캐시된 세션이 아예 생성되지 않거나 아니면 생성되었던 것이 해제될 수도 있다. 어떠한 경우에도 브로커는 적절한 에러를 클라이언트에게 리턴하고, 컨슈머는 사용자가 개입하지 않아도 알아서 모든 파티션 메타데이터를 포함하는 읽기 요청을 보낼 것이다.

     

    물리적 저장소

    계층화된 저장소

     2018년 말부터, 아파치 카프카 커뮤니티는 카프카에 계층화된 저장소(tiered storage) 기능을 추가하기 위한 작업을 시작하였다. 이 프로젝트는 버전 3.0에 탑재될 예정이다.

     이 프로젝트의 동기는 꽤나 단순하다. 카프카는 (초당 생성되는 양이 많아서 그렇든 오랫동안 보존해서 그렇든 간에) 현재 대량의 데이터를 저장하기 위한 목적으로 사용되고 있다. 이것은 다음과 같은 문제를 야기한다.

    • 파티션별로 저장 가능한 데이터에는 한도가 있다. 결과적으로, 최대 보존 기한과 파티션 수는 제품의 요구 조건이 아닌 물리적인 디스크 크기에도 제한을 받는다.
    • 디스크와 클러스터 크기는 저장소 요구 조건에 의해 결정된다. 지연과 처리량이 주 고려사항일 경우 클러스터는 필요한 것 이상으로 커지는 경우가 많다. 이는 곧 비용으로 직결된다.
    • 클러스터의 크기를 키우거나 줄일 때, 파티션의 위치를 다른 브로커로 옮기는 데 걸리는 시간은 파티션의 수에 따라 결정된다. 파티션의 크기가 클수록 클러스터의 탄력성은 줄어든다. 클라우드 환경의 유연한 옵션을 활용할 수 있도록 최대한의 탄력성을 가지는 것이 아키텍처 설계의 추세다.

     계층화된 저장소 기능에서는 카프카 클러스터의 저장소를 로컬과 원격, 두 계층으로 나눈다. 로컬 계층은 현재 카프카 저장소 계층과 똑같이 로컬 세그먼트를 저장하기 위해 카프카 브로커의 로컬 디스크를 사용한. 새로운 원격 계층은 완료된 로그 세그먼트를 저장하기 위해 HDFS나 S3와 같은 전용 저장소 시스템을 사용한다.

     사용자는 계층별로 서로 다른 보존 정책을 설정할 수 있다. 로컬 저장소가 리모트 계층 저장소에 비해 훨씬 비싼 것이 보통이므로 로컬 계층의 보존 기한은 대개 몇 시간 이하로 설정하고, 원격 계층의 보존 기한은 그보다 길게(며칠이나 몇 달로) 설정하는 것이다.

     로컬 저장소는 원격 저장소에 비해 지연이 훨씬 짧다. 지연에 민감한 애플리케이션들은 로컬 계층에 저장되어 있는 최신 레코드를 읽어오는 만큼, 데이터를 전달하기 위해 페이지 캐시를 효율적으로 활용하는 카프카의 메커니즘에 의해 문제없이 작동한다. 빠진 처리 결과를 메꾸는 작업이나 장애에서 복구되고 있는 애플리케이션들은 로컬 계층에 있는 것보다 더 오래된 데이터를 필요로 하는 만큼 원격 계층에 있는 데이터가 전달된다.

     계층화된 저장소 기능의 이중화된 구조 덕분에 카프카 클러스터의 메모리와 CPU에 상관없이 저장소를 확장할 수 있다. 이에 따라 카프카는 장기간용 저장 솔루션으로서의 역할을 할 수 있게 되었다. 카프카 브로커에 로컬 저장되는 데이터의 양 역시 줄어들며, 복구와 리밸런싱 과정에서 복사되어야 할 데이터의 양 역시 줄어든다. 원격 계층에 저장되는 로그 세그먼트들은 굳이 브로커로 복원될 필요없이 원격 계층에서 바로 클라이언트로 전달된다. 모든 데이터가 브로커에 저장되는 것은 아닌 만큼, 보존 기한을 늘려잡아 주더라도 더 이상 카프카 클러스터 저장소를 확장하거나 새로운 노드를 추가해 줄 필요가 없다. 동시에, 전체 데이터 보존 기한 역시 더 길게 잡아줄 수 있다. 즉, 지금 흔히들 하고 있는 것처럼 카프카의 데이터를 외부 저장소로 복사하는 별도의 데이터 파이프라인을 구축할 필요가 없어지는 것이다.

     계층화된 저장소의 설계에 대해서는 KIP-405(https://cwiki.apache.org/confluence/display/KAFKA/KIP-405:+Kafka+Tiered+Storage)에서 자세한 내용을 볼 수 있다. 이 문서에서는 새로운 컴포넌트인 RemoteLogManager가 무엇인지, 레플리카의 리더 데이터 복제 기능이나 리더 선출과 같은 기존 기능과 어떻게 상호작용하는지에 대해서도 다룬다.

     KIP-405에 기술된 것 중 재미있는 결과 하나는 계층화된 저장소의 도입으로 인한 성능 변화다. 이 기능을 개발하던 팀은 여러 활용 사례에 대해 성능을 측정하였다. 첫 번째는 상시적으로 막대한 작업 부하가 걸린 상황에서 실행되었다. 이 경우, 브로커들이 원격 저장소로 로그 세그먼트를 전송해야 하는 만큼 지연이 약간 증가했다(p99값이 21밀리초에서 25밀리초로 증가함). 두 번째는 일부 컨슈머들이 오래된 데이터를 읽어 오는 상황이었다. 계층화된 저장소가 설정되지 않은 상태에서 컨슈머가 오래된 데이터를 읽어 오는 것은 지연에 큰 영향을 미치지만(p99 기준. 21밀리초에서 60밀리초), 이 기능을 활성화할 경우 오히려 영향이 줄어든다(p99 기준, 25밀리초에서 42밀리초). 계층화된 저장소는 네트워크 경로를 통해서 HDFS나 S3에서 데이터를 읽어오기 때문이다. 네트워크 읽기는 로컬 읽기와 디스크 I/O나 페이지 캐시를 놓고 경합할 일이 없기 때문에 페이지 캐시는 온전히 새 데이터를 읽고 쓰는 데 사용될 수 있다.

     즉. 계층화된 저장소 기능은 무한한 저장 공간, 더 낮은 비용, 탄력성뿐만 아니라 오래 된 데이터와 실시간 데이터를 읽는 작업을 분리시키는 기능이 있다.

    댓글

Designed by Tistory.