Tiered Storage
By JW Song
카프카는 분산 시스템에서 메시지의 흐름에 매우 중요한 역할을 맡고 있는 시스템이다. 카프카 공식 홈페이지에서는 카프카를 이벤트 스트리밍 플랫폼이라 정의하고 있다. 카프카는 수신한 메시지를 여러 구독자에 전달하는 동시에 스토리지에 일정 기간 보관하는 특징을 갖고 있다. 따라서 스토리지의 역할은 매우 중요하다.
점점 더 많은 데이터가 카프카를 통해 이동하고 있으며 이로 인한 여러 가지 스토리지 문제를 해결하기 위해 KIP-405를 통해 계층 스토리지 도입을 논의하였고, 카프카 버전 3.6.0에서 릴리즈 예정이다.
TIERED STORAGE
Motivation
여유 공간
카프카에서 장애의 95%가 디스크 관련이라는 보고가 있을 정도로 스토리지는 매우 중요하다. 카프카 사용량이 많아질수록 디스크 사용도 늘어나게 되며 운영 도중 메시지 로그를 저장하는 디스크가 가득 찰 경우 브로커는 완전히 작동을 멈추게 되어 심각한 장애를 유발할 수 있다. 간혹 3대의 브로커로 운영되는 카프카 클러스터에서 모든 토픽이 중요해서 3개의 복제본으로 설정된 경우 모든 브로커가 한 번에 작동을 멈출 위험성도 있다. 4대의 브로커로 운영되는 클러스터에서 디스크 여유 공간 문제로 3대의 브로커가 한꺼번에 멈춘 사례도 있다.
참고 : 카프카, 대규모 클러스터 운영 후기
IO 성능
카프카의 성능에 관련된 사항은 여러 가지가 있으며 디스크 IO 성능은 그 영향이 매우 크다. 따라서, 성능을 끌어 올리기 위해서는 되도록 로컬 시스템에 직접 연결된 고성능 디스크를 사용할 필요가 있다. 하지만, NVMe나 SSD와 같은 플래시 메모리 기반의 디스크는 가격이 매우 비싸며 설치할 수 있는 용량도 매우 제한적이다. 네트워크로 접근할 수 있는 아마존 S3와 같은 오브젝트 스토리지는 비교적 저렴하고 충분히 큰 공간을 제공할 수 있다. 오랫동안 보관해야 하는 메시지이지만 자주 읽히지 않는 경우에는 IO 성능이 낮지만, 충분한 대안이 될 수 있으나 현재 카프카에서 이러한 종류의 스토리지를 지원하지 않고 있다.
장기 스토리지 서비스
근래에 모든 데이터를 수집 분배하는 시작점으로 카프카를 채택하는 사례가 매우 많다. 예를 들어 카프카를 통해 모든 시스템의 로그를 수집하고 향후 분석을 위해 HDFS 등의 느리지만 보관에 용이하고 저렴한 시스템으로 보내어 장기간 보관하는 형태로 구성하는 경우가 많다. 카프카는 실시간 데이터도 처리할 수 있고 보관 정책에 따라 오래된 데이터도 처리할 수 있지만, 일반적으로 카프카는 데이터를 장기간 보관하기에는 큰 비용이 필요하다. 이런 이유로 데이터 수집과 보관을 위해 복잡한 시스템을 구축해야 한다.
카프카 복구와 리밸런싱
카프카 브로커에 장애가 발생했을 때 가장 손쉬운 복구 방법은 동일 아이디를 가진 새 브로커 노드로 대체하고 기존 브로커는 삭제하는 것이다. 새로운 브로커 노드는 장애가 발생했던 기존 노드가 갖고 있어야 하는 데이터를 다른 브로커에 보관된 복제본에서 복사하게 된다. 마찬가지로, 클러스터 스토리지를 확장하기 위해 새로운 브로커 노드가 추가되면 클러스터 리밸런싱은 새 노드에 파티션을 할당하는데, 이 역시 많은 양의 데이터를 복사해야 한다. 복구 및 리밸런싱에 걸리는 시간은 카프카 브로커에 로컬로 저장된 데이터의 양에 비례한다. 많은 수의 브로커를 갖는 카프카 클러스터 구성에서 노드 장애가 흔히 발생하며, 복구에 많은 시간이 소요될 수 있다.
각 브로커에 저장되는 데이터의 양을 줄이면 복구/리밸런싱 시간을 줄일 수 있지만, 로그 보존 기간을 줄여야 하므로 이는 애플리케이션 유지 관리 및 장애 복구에 사용할 수 있는 시간에 영향을 미치게 된다.
카프카 in 클라우드
카프카는 일반적으로 여러 개의 대용량 디스크를 로컬에 장착한 단독 시스템으로 구축해서 사용해 왔다. 하지만 점차 모든 시스템이 클라우드 컴퓨팅 환경으로 옮겨가고 있으며, 클라우드 환경에서는 대용량 로컬 디스크를 제공하는 시스템이 없거나 비용이 매우 비싸다. 로컬 스토리지를 적게 쓰는 경우 카프카 브로커 노드로 사용할 수 있는 클라우드 시스템 선택의 폭이 넓으며 이는 클라우드 환경에 더 적합하다.
Solution — Tiered storage for Kafka
카프카 데이터는 대부분 테일 읽기를 사용해 스트리밍 방식으로 소비된다. 테일 읽기란 디스크에 저장된 데이터를 읽는 대신 OS의 페이지 캐시를 활용하여 데이터를 제공한다. 오래된 데이터는 일반적으로 장애 복구 목적으로 디스크에서 읽으며, 그 빈도는 매우 낮다.
계층형 스토리지 접근 방식에서, 카프카 클러스터는 로컬과 원격의 두 가지 스토리지 계층으로 구성된다. 로컬 계층은 로그 세그먼트를 저장하기 위해 카프카 브로커의 로컬 디스크를 사용하는 현재 카프카와 동일하다. 새로운 원격 계층은 완성된 로그 세그먼트를 저장하기 위해 HDFS 또는 S3와 같은 시스템을 사용한다. 메시지 보존 기간은 각각의 계층에 별도로 정해진다. 원격 계층을 활성화하면 로컬 계층의 보존 기간을 며칠에서 몇 시간으로 크게 줄일 수 있고 원격 계층의 보존 기간은 며칠 또는 몇 달까지 훨씬 더 길어질 수 있다. 로그 세그먼트가 로컬 계층에서 롤링 되면 해당 인덱스와 함께 원격 계층으로 복사된다. 지연 시간에 민감한 애플리케이션은 테일 읽기를 수행하며 페이지 캐시를 효율적으로 사용하여 데이터를 제공하는 기존 카프카 메커니즘을 활용하여 로컬 계층에서 서비스를 제공한다. 로컬 계층에 있는 것보다 오래된 데이터가 필요한 백필 및 장애로부터 복구하는 기타 애플리케이션은 원격 계층에서 제공된다.
계층형 스토리지는 카프카 클러스터의 메모리와 CPU에 관계없이 스토리지를 확장할 수 있어 카프카를 장기적인 스토리지 솔루션으로 사용할 수 있게 해준다. 또한 카프카 브로커에 로컬로 저장되는 데이터의 양이 줄어들어 복구 및 리밸런싱 중에 복사해야 하는 데이터의 양을 줄일 수 있고 원격 계층에서 사용할 수 있는 로그 세그먼트는 브로커에서 복원할 필요가 없으며 원격 계층에서 제공된다. 이를 통해 보존 기간을 늘리기 위해 더 이상 카프카 클러스터 스토리지를 확장하거나 새로운 노드를 추가할 필요가 없고 전체 데이터 보존 기간을 훨씬 더 길게 유지할 수 있으므로 현재 많은 배포에서 수행되는 것처럼 카프카에서 외부 저장소로 데이터를 복사하기 위해 별도의 데이터 파이프라인을 사용할 필요가 없어진다.
목표
카프카 내부에 미치는 영향을 최소화하면서 이전 데이터를 HDFS 또는 S3와 같은 외부 저장소에 유지하여 카프카 클러스터에서 사용할 수 있는 로컬 저장소 이상으로 카프카의 저장소를 확장하며 계층형 스토리지 기능을 구성하지 않은 기존 사용자의 경우 카프카 동작 및 운영 복잡성이 변경되지 않아야 한다.
예외
계층형 스토리지는 ETL(Extract Transform Load; 추출 변환 적재) 파이프라인과 작업을 대체하지 않으며 기존 ETL 파이프라인은 카프카의 데이터가 훨씬 더 긴 보존 기간을 가지고 있기는 하지만, 카프카의 데이터를 그대로 계속 소비한다.
계층형 스토리지가 있는 컴팩트 토픽은 지원하지 않으며 remote.storage.enable
을 true로 설정하여 생성된 토픽은 보존 정책을 삭제에서 압축으로 변경할 수 없다.
변경 사항
아키텍처
새로운 원격 로그 관리자(Remote Log Manager)는 공개 API가 아니고 내부 구성 요소이며 다음을 수행한다.
- 파티션의 리더십 변경 및 브로커에서 토픽 파티션의 중지/삭제 이벤트에 대한 콜백 이벤트를 수신
- 토픽 파티션 세그먼트의 복사, 읽기, 삭제는 플러그 가능한 스토리지 관리자(예: RemoteStorageManager) 구현에 위임하고, 각각의 원격 로그 세그먼트 메타데이터는 RemoteLogMetadataManager를 통해 유지 관리
RemoteStorageManager
는 원격 로그 세그먼트와 인덱스의 라이프사이클을 제공하기 위한 인터페이스이며 API에 대한 이해를 돕기 위해 RSM의 간단한 구현을 제공할 예정이다. HDFS와 S3 구현은 외부 리포지토리에서 호스팅될 계획이며 아파치 카프카 리포지토리에는 포함되지 않는다. 이는 카프카 커넥터에 대해 취한 접근 방식과 동일하다.
RemoteLogMetadataManager
는 원격 로그 세그먼트에 대한 메타데이터의 수명 주기를 제공하는 인터페이스다. 내부 토픽을 사용하는 기본 구현이 포함되며 다른 시스템을 사용하여 원격 로그 세그먼트 메타데이터를 저장하려는 경우 사용자는 자체 구현을 플러그인할 수 있다.
원격 로그 관리자(RLM)
RLM은 각 리더 또는 팔로워 주제 파티션에 대한 작업을 생성하며, 여기에 자세히 설명되어 있습니다.
- RLM 리더 작업 롤오버된 로그 세그먼트(마지막 메시지 오프셋이 해당 토픽 파티션의 마지막 안정 오프셋보다 작은)를 확인하고 오프셋/시간/트랜잭션/프로듀서-스냅샷 인덱스 및 리더 에포크 캐시와 함께 원격 계층에 복사하고 원격 계층에서 이전 데이터에 대한 가져오기 요청을 처리한다. 로컬 로그는 보존 시간/크기에 도달하더라도 해당 세그먼트가 원격으로 성공적으로 복사될 때까지 정리하지 않는다.
- RLM 팔로워 작업 원격 계층의 세그먼트와 인덱스 파일을 조회하여 원격 계층의 세그먼트와 인덱스 파일을 추적하고 원격 계층에서 이전 데이터를 읽는 역할도 수행할 수 있다.
RLM은 원격 저장소에서 여러 인덱스를 가져오는 것을 피하고자 원격 로그 세그먼트의 인덱스 파일에 대한 경계 캐시(LRU일 수 있음)를 유지한다. 이 인덱스 파일은 로그 디렉터리 아래의 remote-log-index-cache
디렉터리에 저장된다. 이 인덱스는 로컬 세그먼트 인덱스와 동일한 방식으로 사용할 수 있고 사용자는 remote.log.index.file.cache.total.size.mb
를 구성하여 이러한 인덱스 파일에 사용할 수 있는 총 크기를 설정할 수 있다.
아마존 S3의 경우, 빈번한 LIST API는 막대한 비용을 발생시킨다. 이런 이유로 원격 스토리지에서 메타데이터를 유지 관리하는데 큰 비용이 발생할 수 있어 원격 스토리지를 원격 로그 메타데이터 저장소와 분리하고 각각 RemoteStorageManager와 RemoteLogMetadataManager를 도입했다.
로컬 및 원격 로그 오프셋 제약 조건
다음은 리더 토픽 파티션의 로그 오프셋 제약 조건이다.
Lx = Local log start offset / Lz = Local log end offset / Ly = Last stable offset(LSO)
Ry = Remote log end offset / Rx = Remote log start offset
Lz >= Ly >= Lx and Ly >= Ry >= Rx
복제 관리자
RLM이 구성된 경우, 복제 관리자는 RLM을 호출하여 토픽 파티션을 할당하거나 제거한다. 브로커가 토픽 파티션에 대해 리더에서 팔로워로 상태가 변경되고 RLM이 세그먼트를 복사하는 중이면, 브로커는 토픽 파티션에 대한 복사를 포기하기 전에 복사를 완료한다. 이에 따라 중복된 세그먼트가 남을 수 있지만 원격 보존 설정에 따라 세그먼트가 삭제될 준비가 되면 자동으로 정리된다.
팔로워 복제
토픽 파티션의 팔로워는 리더의 데이터를 복제하고 리더의 로그 엔드 오프셋까지 따라잡아 동기화된 복제본이 되려고 노력한다. 팔로워는 필요한 경우 잘림(truncate)을 수행하여 리더와 동일한 로그 세그먼트 계보를 유지한다.
계층형 스토리지를 사용해도 팔로워는 리더와 동일한 로그 세그먼트 계보를 유지해야 한다. 팔로워는 사용할 수 있는 데이터를 리더의 로컬 스토리지에서만 복제합니다. 하지만 원격 세그먼트에 대한 리더 에포크 캐시 및 프로듀서 ID 스냅샷과 같은 상태를 유지해야 하며, 필요한 경우 잘라내기 작업도 수행해야 한다.
아래 다이어그램은 리더, 팔로워, 원격 로그 및 메타데이터 스토리지 간의 상호 작용에 대한 간략한 개요를 보여준다.
- 리더가 보조 상태(리더 에포크 캐시 및 프로듀서-ID 스냅샷 포함)의 로그 세그먼트를 원격 스토리지에 복사
- 리더는 복사된 원격 로그 세그먼트에 대한 원격 로그 세그먼트 메타데이터를 게시
- 팔로워는 리더로부터 메시지를 가져오려고 시도
- 팔로워는 필요한 원격 로그 세그먼트 메타데이터를 소비할 때까지 기다림
- 팔로워는 각 원격 로그 세그먼트 메타데이터를 가져와서 보조 상태를 구축