ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [카프카 핵심 가이드] CH6. 카프카 내부 메커니즘 - 물리적 저장소
    Study/카프카 핵심 가이드 2025. 3. 8. 00:04

    6.5 물리적 저장소

    • 카프카의 기본 저장 단위는 '파티션 레플리카'
      • 하나의 파티션은 서로 다른 브로커들 사이에 분리될 수 없고, 같은 브로커의 서로 다른 디스크에 분할 저장될 수 없음
      • 따라서 파티션의 크기는 특정 마운트 지점에 사용 가능한 공간의 제한을 받음
    • 카프카를 설정할 때 운영자가 파티션들이 저장될 디렉토리 목록을 정의
      • log.dirs 매개변수에 지정
      • 카프카가 사용할 각 마운트 지점별로 하나의 디렉토리를 포함하도록 설정하는 것이 일반적

     

    추가) log.dirs

    더보기

    log.dirs

    • 카프카는 모든 메시지를 로그 세그먼트(log segment) 단위로 묶어 log.dir 설정에 지정된 디스크 디렉토리에 저장
    • 다수의 디렉토리를 지정하고자 할 때, log.dirs 사용하는 것이 좋음 (log.dirs가 설정되어 있지 않을 경우, log.dir이 사용됨)
      • log.dirs는 쉼표로 구분된 로컬 시스템 경로의 목록
    • 1개 이상의 경로가 지정되었을 경우, 브로커는 가장 적은 수의 파티션이 저장된 디렉토리에 새 파티션을 저장
      • 같은 파티션에 속하는 로그 세그먼트는 동일한 경로에 저장됨
    • 사용된 디스크 용량 기준이 아닌 저장된 파티션 수 기준으로 새 파티션의 저장 위치를 배정한다는 점에 유의해야 함
      • 이로 인해 다수의 디렉토리에 균등한 양의 데이터가 저장되지 않음 (파티션별 용량 차이 존재하니까)

     

    6.5.1 계층화된 저장소

    등장 배경

    • 카프카를 대량의 데이터를 저장하기 위한 목적으로 사용하고자 함
    • 이는 다음과 같은 문제를 야기함
      • 파티션별로 저장 가능한 데이터에 한도가 존재한다.
        • 최대 보존 기한과 파티션 수는 제품의 요구 조건이 아닌 물리적인 디스크 크기의 제한을 받을 수 있음
      • 디스크와 클러스터 크기는 저장소 요구 조건에 의해 결정된다.
        • 저장소 크기를 늘리는 것은 비용과 직결됨
      • 파티션의 크기에 따라 클러스터의 탄력성이 좌우된다.
        • 클러스터의 크기를 키우거나 줄일 때, 파티션의 위치를 다른 브로커로 옮기는 데 걸리는 시간은 파티션의 크기에 따라 결정됨
        • 파티션의 크기가 클수록 클러스터의 탄력성을 줄어듦
        • Q. 클러스터의 탄력성? A. 분산 데이터 저장소에서 노드를 추가하거나 제거하는 용이성

     

    적용 (카프카 3.6부터 사용 가능)

    • 계층화된 저장소 기능은 카프카 클러스터의 저장소를 로컬과 원격, 두 계층으로 나눔
      • 로컬 계층 : 현재의 카프카 저장소 계층과 똑같이 로그 세그먼트를 저장하기 위해 카프카 브로커의 로컬 디스크 사용
      • 원격 계층 : 완료된 로그 세그먼트를 저장하기 위해 HDFS(하둡 분산 파일 시스템)나 S3 같은 전용 저장소 시스템 사용
    • 계층별로 서로 다른 보존 정책 설정 가능
      • 로컬 저장소가 리모트 계층 저장소에 비해 비싸므로 로컬 계층의 보존 기한은 짧게, 원격 계층의 보존 기한 더 길게 설정
    • 로컬 저장소는 원격 저장소에 비해 지연이 훨씬 짧은 특징 고려
      • 지연에 민감한 어플리케이션들은 로컬 계층에 저장되어 있는 최신 레코드를 읽어오도록 함
      • 빠진 처리 결과를 메꾸는 작업이나 장애에서 복구되고 있는 어플리케이션들은 로컬 계층에 있는 것보다 더 오래된 데이터가 필요하므로 원격 계층에 있는 데이터가 전달됨
    • 계층화된 저장소 기능의 이중화 구조로 카프카 클러스터의 메모리와 CPU에 상관없이 저장소 확장이 가능해짐
      • 따라서 카프카는 장기간용 저장 솔루션으로서의 역할을 할 수 있게 됨
      • 장점
        • 카프카 브로커에 로컬 저장되는 데이터 양이 줄어, 복구와 리밸런싱 과정에서 복사되어야 할 데이터의 양 감소
        • 원격 계층에 저장되는 로그 세그먼트들은 굳이 브로커로 복원될 필요 없이 원격 계층에서 바로 클라이언트로 전달
        • 모든 데이터가 브로커에 저장되는 것이 아니므로 전체 데이터 보존 기한 또한 길게 잡아줄 수 있음

     

    정리하면,

    • 카프카 클러스터 내 카프카 브로커들의 로컬 저장공간 용량의 한계를 개선하기 위해 원격 저장공간 사용하도록 함
    • 이러한 계층화된 저장소 기능으로 무한한 저장 공간, 더 낮은 비용, 탄력성 제공 및 오래된 데이터와 실시간 데이터를 읽는 작업 분리시킴

     

    참고)

     

    6.5.2 파티션 할당

    • 토픽을 생성하면, 파티션을 브로커 중 하나에 할당
    • 파티션을 할당할 때 목표는 다음과 같음 
      • 예시) 브로커 6개 존재, 파티션 10개 + 복제 팩터 3인 토픽 생성 --> 30개의 파티션 레플리카를 6개의 브로커에 할당하는 것
      • 레플리카들을 가능한 한 브로커 간에 고르게 분산시킨다.
        • 브로커별로 5개의 레플리카 할당해 줘야 함
      • 각 파티션에 대해 각각의 레플리카는 서로 다른 브로커에 배치되도록 한다.
        • 동일한 브로커에 리더와 팔로워가 배치되거나, 팔로워가 여러 개 배치될 수 없음
      • 만약 브로커에 랙 정보가 설정되어 있다면, 가능한 한 각 파티션의 레플리카들을 서로 다른 랙에 할당한다.
        • 카프카 버전 0.10.0 이상에서 가능
        • 하나의 랙 전체가 작동 불가하더라도 파티션 전체가 사용 불가해지는 상황 방지 (랙 장애 발생 시에도 가용성 보장)
    • Q. 파티션을 어느 브로커에 할당할까?
      • 기본
        • 임의의 브로커부터 시작해서 각 브로커에 라운드 로빈 방식으로 파티션 할당
          • 1) 리더 레플리카 할당
            • 리더 레플리카들이 각 브로커에 순차적으로 할당됨
            • ex) 임의의 브로커가 4일 때
              • 브로커가 6대
              • 파티션 0의 리더 -> 브로커 4
              • 파티션 1의 리더 -> 브로커 5
              • 파티션 2의 리더 -> 브로커 0
          • 2) 팔로워 레플리카 할당
            • 각 파티션의 팔로워 레플리카는 리더로부터 증가하는 순서로 배치됨
            • ex) 파티션 0의 리더가 브로커 4에 배치되었다면,
              • 첫 번째 팔로워 -> 브로커 5
              • 두 번째 팔로워 -> 브로커 0
      • 브로커에 랙 정보 설정되어 있을 때
        • 서로 다른 랙의 브로커가 번갈아 선택되도록 하여 파티션 할당
        • ex) 랙1 : 브로커 0, 1 / 랙2 : 브로커 2, 3
          • 파티션 0의 리더가 브로커 2에 할당된다면, 첫 번째 팔로워는 브로커 3이 아닌 다른 랙에 위치한 브로커 1에 할당
    • Q. 파티션을 브로커 내 어느 디렉토리에 저장할까?
      • 각 파티션과 레플리카를 할당할 브로커 선택 후 새 파티션을 저장할 디렉토리를 결정
      • 파티션별로 독립적으로 수행
      • 규칙
        • 각 디렉토리에 저장되어 있는 파티션의 수 센 뒤, 가장 적은 파티션이 저장된 디렉토리에 새 파티션 저장

     

    6.5.3 파일 관리  --> 세그먼트 관리(세그먼트는 하나의 데이터 파일 형태)

    • 토픽에 대해 보존 기한(retention period)을 설정할 수 있음
    • 삭제 단위는 파티션의 세그먼트
      • 하나의 파티션을 여러 개의 세그먼트로 분할
      • 각 세그먼트는 1GB의 데이터 or 최근 1주일치의 데이터 중 적은 쪽만큼을 저장함
      • 파티션 단위로 메시지를 쓰기 때문에 각 세그먼트 한도가 차면 세그먼트를 닫고 새 세그먼트를 생성함
    • 현재 쓰여지고 있는 세그먼트를 '액티브 세그먼트(active segment)' 라고 함
      • 액티브 세그먼트는 어떠한 경우에도 삭제되지 않음 (즉, 세그먼트가 닫히기 전까지는 데이터 삭제 불가)
    • 참고) 카프카 브로커는 각 파티션의 모든 세그먼트에 대해 파일 핸들을 엶 (액티브 세그먼트를 포함한 모든 세그먼트 대상)
      • 따라서 사용중인 파일 핸들 수가 매우 높게 유지될 수 있음

     

    6.5.4 파일 형식 --> 세그먼트 형식(세그먼트는 하나의 데이터 파일 형태)

    • 각 세그먼트는 하나의 데이터 파일 형태로 저장됨
    • 파일 안에는 카프카의 메시지와 오프셋이 저장
    • 특징
      1. 표준화된 binary message format 사용
        • 디스크에 저장되는 데이터의 형식은 네트워크를 통해 전달되는 형식과 동일함
          • 즉, 사용자가 프로듀서를 통해 브로커로 보내고, 나중에 브로커로부터 컨슈머로 보내지는 메시지의 형식과 동일함
          • 데이터 형식 통일의 장점
            • 카프카는 컨슈머에 메시지를 전송할 때 제로카피(zero-copy) 최적화를 달성할 수 있음
            • 프로듀서가 이미 압축한 메시지들을 압축 해제한 후 다시 압축하지 않아도 됨
          • Q. 메시지 형식을 변경하고자 한다면?
            • 네트워크 프로토콜과 디스크 저장 형식이 모두 변경되어야 함 --> 카프카 프로듀서, 브로커, 컨슈머가 사용하는 메시지 형식이 모두 변경되어야 하는 것
            • 카프카 브로커들은 업그레이드로 인해 2개의 파일 형식이 뒤섞여 있는 파일을 처리할 방법을 알아야 함
      2. 메시지 배치 사용
        • 카프카 버전 0.11(v2 메시지 형식)부터 카프카 프로듀서는 메시지를 배치 단위로 전송함
        • 메시지 배치 헤더에 대부분의 시스템 정보가 저장됨
          • 하나의 카프카 메시지는 사용자 페이로드, 시스템 헤더 두 부분으로 나뉘어짐
            • 사용자 페이로드 : key, value, headers
          • 각 메시지 역시 자체적인 시스템 헤더를 가지지만 대부분의 시스템 정보는 메시지 배치 헤더에 저장됨
    • 참고)
      • 카프카 브로커에 포함되어 있는 DumpLogSegment 툴을 사용해 파일시스템에 저장된 파티션 세그먼트를 읽을 수 있음
        • $ bin/kafka-run-class.sh kafka.tools.DumpLogSegments

     

    추가) 카프카 시스템의 비효율 및 해결방안 (위의 1, 2 방식을 사용하게 된 이유)

    더보기

    1) 빈번한 작은 I/O 작업

    • 방안 : 메시지 배치 사용

    2) 과도한 byte copying

    • 방안 : 표준화된 binary message format 제공
      • 해당 메시지 형식을 카프카 브로커, 프로듀서, 컨슈머 모두 공유해서 사용하도록 함
      • 브로커에서 메시지 저장, 전송 시 메시지 변환이 필요없어 zero-copy 달성할 수 있음
      • 브로커 설정 파일의 log.message.format.version 값으로 설정 가능
    • 주의)
      • 카프카 프로듀서, 컨슈머가 사용하는 메시지 형식 버전 < 카프카 브로커의 버전이면,  zero-copy 사용 불가
        • 컨슈머에 메시지 전송하기 전 메시지 형식 변환하여 응답해야함 (Down Conversion)
      • 브로커의 메시지 형식 버전 업그레이드 시에는 클라이언트들의 메시지 형식 버전도 함께 업그레이드 해야 함

     

    참고) https://kafka.apache.org/27/documentation.html#maximizingefficiency

     

    6.5.5 인덱스

    • 2가지의 인덱스 제공
      • 1) 오프셋과 세그먼트 파일 및 그 안에서의 위치 매핑하는 인덱스
        • 브로커가 요청한 오프셋의 메시지를 빠르게 찾을 수 있도록 하기 위함
      • 2) 오프셋과 타임스탬프와 매핑하는 인덱스
        • 타임스탬프 기준으로 메시지를 찾을 때 사용
    • 특징
      • 인덱스 역시 세그먼트 단위로 분할
        • 파티션의 메시지를 삭제할 때 오래된 인덱스 항목 역시 삭제할 수 있음
      • 인덱스가 오염될 경우 해당하는 로그 세그먼트에 포함된 메시지들을 다시 읽어 오프셋과 위치를 기록하는 방식으로 재생성
        • 인덱스 세그먼트를 삭제해도 자동으로 재생성됨

     

    6.5.6 압착 --> 토픽 단위로 설정

    • 카프카는 두 가지 보존 정책 제공
      • 삭제(delete) 정책
        • 지정된 보존 기한보다 더 오래된 이벤트를 삭제함
      • 압착(compact) 정책
        • 토픽에서 각 키의 가장 최근값만 저장하도록 함
        • 토픽에 키 값이 null인 메시지가 있을 경우 압착은 실패함
        • 사례)
          • 고객의 배송지 주소를 저장할 때
            • 삭제 정책 적용하여 지난 1주일 동안, 혹은 1년 동안의 변경 내역의 데이터를 저장하는 것보다 가장 마지막의 것만 저장하는 것이 더 합리적임
          • 어플리케이션의 현재 상태를 저장할 때
            • 크래시가 발생한 뒤 복구 과정에서 어플리케이션은 이 메시지를 카프카에서 읽어와 최근 상태를 복원함
            • 이때 중요한 것은 크래시 나기 직전의 마지막 상태임
    • 두 가지 정책 동시에 적용 가능
      • 보존 기한과 압착 설정 동시에 가능함
      • 지정된 보존 기한보다 오래된 메시지들은 키에 대한 최근 값인 경우에도 삭제될 것임

     

    6.5.7 압착의 작동 원리 --> 세그먼트 단위로 수행

    • 파티션 로그는 다음과 같이 두 영역으로 나누어짐
      • 클린(Clean)
        • 이전에 압착된 적이 있었던 메시지들이 저장됨
        • 하나의 키마다 하나의 값만을 포함하고 있음 (이전 압착 작업 시점에서의 최신값들)
      • 더티(Dirty)
        • 마지막 압착 작업 이후 쓰여진 메시지들이 저장됨

     

    압착 수행 여부

    • 압착 기능이 활성화되어 있을 경우(log.cleaner.enabled=true) 각 브로커는 압착 매니저 스레드와 함께 다수의 압착 스레드를 시작함 (해당 스레드들이 압착 작업 담당)
    • 각 스레드는 전체 파티션 크기 대비 더티 메시지의 비율이 가장 높은 파티션을 골라 압착한 뒤 클린 상태로 만듦

     

    압착 과정

    1. 압착할 세그먼트들 선정

     

    2. 선정된 세그먼트들 대상으로 인-메모리 맵 생성

    • 맵의 각 항목은 약 24바이트 사용함 (key : 16byte, offset : 8byte)
    • 참고)
      • 압착 스레드가 해당 맵을 저장하기 위해 사용할 수 있는 메모리의 양을 직접 설정도 가능함
      • 스레드마다 개별 맵을 생성하기는 하지만, 해당 설정은 전체 스레드가 사용할 수 있는 메모리의 총량을 의미함
      • 압착은 세그먼트 단위로 수행되므로 최소한 하나의 세그먼트 전체가 들어갈 수 있어야 함
        • 그렇지 않을 경우 카프카는 에러 메시지 로깅함

    3. 메시지 삭제 및 보류

    • 클린 세그먼트 내 각각의 메시지에 대해, 해당 메시지의 키값이 현재 오프셋 맵에 저장되어 있는지 확인함
      • 저장되어 있다면 삭제 대상, 저장되어 있지 않다면 보류 대상

    • 보류 대상 메시지들만 교체용 세그먼트로 복사됨

    4. 세그먼트 교체

    • 메시지 복사가 완료되면, 압착 스레드는 교체용 세그먼트와 원본 세그먼트를 바꾼 뒤 다음 더티 세그먼트를 대상으로 압착을 계속 진행함
    • 작업이 완료되면, key별로 (최신 value값을 포함하는) 하나의 메시지만 남게 됨

    cf) https://developer.confluent.io/courses/architecture/compaction/

     

    6.5.8 삭제된 이벤트

    상황1) 특정 키를 갖는 모든 메시지를 삭제하고 싶을 때

    • 토픽의 압착 기능 사용
    • 방식
      • key: 삭제하고자 하는 키 값, value: null을 갖는 메시지를 발행함
      • 클리너 스레드가 이 메시지를 발견하면 평소대로 압착 작업을 한 뒤 value: null 값을 가진 메시지만 보존하게 됨
      • 카프카는 설정된 기간동안 해당 메시지를 보존하게 됨 (이러한 메시지를 'tombstone'이라고 함)
      • 이 기간 동안, 컨슈머는 해당 메시지를 보고 해당 값이 삭제되었음을 알 수 있음
      • 보존 기간 이후 클리너 스레드는 메시지를 삭제하며, 해당 키 역시 카프카 파티션에서 완전히 삭제되게 됨
    • 예시) 사용자가 서비스를 탈퇴해서 해당 사용자의 모든 흔적을 시스템에서 지워야하는 경우

     

    상황2) 지정된 오프셋 이전의 모든 레코드를 삭제할 때

    • 카프카 어드민 클라이언트의 deleteRecords 메서드 사용 (지정된 오프셋 이전의 모든 레코드를 삭제함)
    • 방식
      • 해당 메서드를 호출하면, 카프카는 파티션의 첫 번째 레코드를 가리키는 로우 워터마크(low-water mark)를 해당 오프셋으로 이동시킴
      • 컨슈머는 업데이트된 로우 워터마크 이전 메시지들을 읽을 수 없게 되므로 접근 불가능하게 됨
      • 해당 레코드들은 나중에 클리너 스레드에 의해 실제로 삭제됨

     

    6.5.9 토픽은 언제 압착되는가?

    • 대상
      • 삭제 정책과 마찬가지로 압착 정책 역시 현재의 액티브 세그먼트를 절대 압착하지 않음
      • 액티브 세그먼트가 아닌 세그먼트에 저장되어 있는 메시지만이 압착의 대상이 됨
    • 시점
      • 기본적으로, 카프카는 토픽 내용물의 50% 이상이 더티 레코드인 경우에만 압착을 시작함
        • 압착 기능의 목표는
          • 토픽을 자주 압착하지 않으면서(압착은 토픽의 읽기/쓰기 성능에 영향을 줄 수 있음),
          • 너무 많은 더티 레코드가 존재하지 않도록 하는 것(디스크 공간 낭비 줄이기 위함)
      • 아래 두 개의 설정 매개변수를 사용해 압착이 시작되는 시점을 조절할 수 있음
        • min.compaction.lag.ms
          • 메시지가 쓰여진 뒤 압착될 때까지 지나가야 하는 최소 시간 지정 (해당 시간 이후부터 압착 가능)
        • max.compaction.lag.ms
          • 메시지가 쓰여진 뒤 압착이 가능해질 때까지 딜레이 될 수 있는 최대 시간 지정
          • 특정 기한 안에 압착이 반드시 실행된다는 것을 보장해야 하는 상황에 자주 사용됨

    댓글

Designed by Tistory.