이 글은 “카프카, 데이터 플랫폼의 최강자” 책 내용을 정리한 글입니다.

만약 저작권 관련 문제가 있다면 “gunjuko92@gmail.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.

카프카, 데이터 플랫폼의 최강자

카프카에서는 대용량의 실시간 데이터 처리를 위해 배치 전송, 파티션, 분산기능을 구현했다. 또한 데이터의 안정적인 저장을 위해 리플리케이션 기능과 분산된 서버에서 자동으로 파티션의 리더를 선출하는 기능을 구현했다.

1. 카프카 디자인의 특징

카프카는 처리량에 중점을 두고 설계되었다. 카프카에는 높은 처리량과 빠른 메시지 전송, 운영 효율화 등을 위해 분산 시스템, 페이지 캐시, 배치 전송 처리 등의 기능이 구현되었다.

분산 시스템

분산 시스템은 네트워크로 이루어진 컴퓨터들의 그룹으로서 시스템 전체가 공통의 목표를 가지고 있다. 다시 말해 같은 역할을 하는 여러 대의 서버로 이뤄진 서버 그룹을 분산 시스템이라고 한다. 분산 시스템의 장점은 아래와 같다.

  • 단일 시스템보다 더 높은 성능을 얻을 수 있다.
  • 분산 시스템 중 하나의 서버 또는 노드 등이 장애가 발생하면 다른 서버 또는 노드가 대신 처리한다.
  • 시스템 확장이 용이하다.

무조건 서버를 늘려 부하를 분산하기보다는 장애 상황과 서버의 리소스 사용량 등을 고려해 적절한 수로 유지하는 것이 좋다.

카프카도 분산 시스템이기 때문에 유동적으로 서버를 늘리 수 있다.

페이지 캐시

  • OS는 물리적 메모리에 애플리케이션 사용하는 부분을 할당하고 남은 잔여 메모리 일부를 페이지 캐시로 유지해 OS의 전체적인 성능 향상을 높이게 된다.
  • 잔여 메모리를 이용해 디스크에 읽고 쓰기를 하지 않고, 페이지 캐시를 통해 읽고 쓰는 방식을 사용하면 처리 속도가 매우 빠르기 때문에 전체적인 성능을 향상시킬 수 있다.
  • 카프카는 이러한 특징을 이용해 빠른 액세스를 하기 위해 OS의 페이지 캐시를 이용하도록 디자인되었다.

이런 장점 덕분에 카프카를 구성할 때는 디스크 중에서 가격이 가장 저렴한 SATA 디스크를 사용해도 무방하다.

JVM 힙 사이즈

  • 카프카는 JVM을 사용하는 애플리케이션으로서 자바 기반 애플리케이션들은 시작할 때 메모리가 할당되는 영역인 힙이 만들어진다.
  • 카프카는 기본값으로 1GB의 힙 메모리를 사용하도록 설정되어 있다. 그리고 설정 파일에서 이 값을 변경할 수 있다.
    • KAFKA_HEAP_OPTS=”-Xmx6G -Xms6G” 추가
  • 도큐먼트에서는 5GB의 힙 메모리면 충분하고, 남아있는 메모리는 페이지 캐시로 사용하기를 권장함
  • 페이지 캐시를 서로 공유해야 하기 때문에 하나의 시스템에 카프카를 다른 애플리케이션과 함께 실행하는 것은 권장하지 않음

배치 전송 처리

  • 카프카에서는 I/O 들을 묶어서 처리할 수 있도록 배치 작업으로 처리한다.
  • I/O를 배치 작업으로 처리하면 네트워크 왕복 오버헤드 등을 줄일 수 있다.

2. 카프카 데이터 모델

토픽의 이해

  • 토픽은 메시지를 받을 수 있도록 논리적으로 묶음 개념이다.
  • 토픽의 이름은 249자 미만으로 영문, 숫자, ‘.’, ‘_’, ‘-‘ 를 조합하여 자유롭게 만들 수 있다.

파티션의 이해

  • 토픽을 구성하는 데이터 저장소로서 수평 확장이 가능한 단위
  • 카프카에서 효율적인 메시지 전송과 속도를 높이려면 토픽의 파티션 수를 늘려줘야 한다.

파티션 수를 무조건 늘려야하나?

파티션 수가 늘어나면 오히려 카프카에 안좋은 영향을 미칠 수도 있다.

  • 파일 핸들러 낭비 : 각 파티션은 브로커의 디렉토리와 매핑되고, 저장되는 데이터마다 2개의 파일 (인덱스와 실제 데이터)이 있다. 카프카에서는 모든 디렉토리의 파일들에 대해 파일 핸들을 열게 된다. 결국 파티션의 수가 많을수록 파일 핸들 수 역시 많아지게 되어 리소스를 낭비하게 된다.
  • 장애 복구 시간 증가 : 카프카는 높은 가용성을 위해 리플리케이션을 지원한다. 각 파티션마다 리플리케이션이 동작하게 되며, 하나는 파티션의 리더이고, 나머지는 파티션의 팔로워가 된다. 만약 브로커가 다운되면 해당 브로커에 리더가 있는 파티션은 일시적으로 사용할 수 없게 된다. 카프카는 리더를 팔로워중 하나로 이동시켜 클라이언트 요청을 처리할 수 있게 한다. 이와 같은 장애 처리는 컨트롤러로 지정된 브로커가 수행한다. 컨트롤러는 카프카 클러스터내 하나만 존재하고, 만약 컨트롤러의 역할을 수행하는 브로커가 다운되면 살아 있는 브로커 중 하나가 자동으로 컨트롤러 역할을 대신 수행한다. 파티션의 개수가 많을수록 브로커가 다운되었을때 컨트롤러가 각 파티션별로 새로운 리더를 선출하는데까지 걸리는 시간은 증가하게 된다. 따라서 장애 복구 시간이 증가하게 된다.
    • 컨트롤러가 다운되는 경우, 새로운 컨트롤러가 초기화된다. 초기화하는 동안 주키퍼에서 모든 파티션의 데이터를 읽어오게 된다. 따라서 파티션의 개수가 많을수록 초기화하는 시간도 증가한다.

파티션 수는 예상 목표치를 가지고 결정하는게 가장 이상적이다. 카프카에서는 파티션 수의 증가는 필요한 경우 아무때나 변경이 가능하지만, 반대로 파티션의 수를 줄이는 것은 불가능하다. (토픽을 삭제하는 방법외에는) 따라서 적절한 파티션 수를 측정하기 어려운 경우에는 일단 적은 수의 파티션으로 운영해보고, 병목 현상이 발생하게 될 때 조금씩 파티션 수를 늘려가는 방법으로 적정 파티션 수를 할당할 수 있다.

참고로 브로커당 약 2,000개 정도의 최대 파티션 수를 권장하고 있다. 따라서 과도한 파티션 수를 적용하기보다는 목표 처리량에 맞게 적절한 파티션 수로 유지, 운영하기를 추천한다.

오프셋과 메시지 순서

  • 각 파티션마다 메시지가 저장되는 위치를 오프셋이라고 부른다. 오프셋은 파티션 내에서 유일하고 순차적으로 증가하는 숫자 형태로 되어있다.
  • 카프카는 오프셋을 이용해 메시지의 순서를 보장한다.

3. 카프카의 고가용성과 리플리케이션 팩터

카프카는 높은 고가용성을 위해 리플리케이션 기능을 제공한다. 카프카의 리플리케이션은 토픽 자체를 리플리케이션 하는 것이 아니라, 토픽을 이루는 각각의 파티션을 리플리케이션 한다.

리플리케이션 팩터와 리더, 팔로워의 역할

  • 리플리케이션 팩터
    • default.replication.factor : 아무런 옵션을 주지 않고 토픽을 생성할 때 적용되는 값이고, 각 토픽별로 다른 리플리케이션 팩터 값을 설정할 수 있다.
    • 클러스터 내 모든 브로커에 동일하게 설정해야 한다.
    • 운영중에도 토픽의 리플리케이션 팩터값은 변경할 수 있다.
  • 리더 : 모든 읽기와 쓰기가 리더를 통해서만 일어난다.
  • 팔로워 : 리더의 데이터를 그대로 리플리케이션만 하고 읽기와 쓰기에는 관여하지 않는다.
  • 리더와 팔로워는 저장된 데이터의 순서도 일치하고 동일한 오프셋과 메시지들을 갖게 된다.
  • 카프카는 리플리케이션 기능을 이용해 브로커가 다운되는 상황이 발생하더라도 리더 변경으로 별다른 문제 없이 요청들을 처리할 수 있게 된다.
  • 단점
    • 브로커의 디스크 사용량이 늘어난다.
    • 브로커의 리소스 사용량이 늘어난다. 브로커에서는 완벽한 리플리케이션을 보장하기 위해 비활성화된 토픽의 상태를 체크하는 등의 작업이 이뤄진다. 데이터를 동일하게 보장하는 처리 비용은 크지 않지만 브로커의 일부 리소스 사용량을 증가시키게 된다.

리더와 팔로워의 관리

  • 팔로워에 문제가 있어 리더로부터 데이터를 가져오지 못하면 정합성이 맞지 않는 문제가 발생한다.
  • 이러한 현상을 방지하고자 ISR(In Sync Replica)라는 개념을 도입했다.
  • ISR이라는 것은 현재 리플리케이션되고 있는 리플리케이션 그룹이다. ISR에는 중요한 규칙이 있다. 그 규칙은 ISR에 속해 있는 구성원만이 리더의 자격을 가질 수 있다는 것이다.
  • 리더가 자신의 역할을 하지 못하게 되는 경우 팔로워가 그 역할을 대신해야 하기 때문에 리더와의 데이터 동기화 작업을 매우 중요하게 처리하고 있으며 이것을 유지하는 것이 바로 ISR이다. 즉 ISR이라는 그룹을 만들어 리플리케이션의 신뢰성을 높이고 있다.
  • 리더는 팔로워들이 주기적으로 데이터를 확인을 하고 있는지 확인을 하여 만약 설정된 주기 (replica.lag.time.max.ms)만큼 확인 요청이 요지 않는다면, 리더는 해당 팔로워의 이상을 감지하고, 해당 팔로워는 더 이상 리더의 역할을 대신할 수 없다고 판단해 ISR 그룹에서 해당 팔로워를 추방시키게 된다.

4. 모든 브로커가 다운된다면

카프카 클러스터가 다운되는 최악의 상황이 발생한 경우, 선택할 수 있는 방법은 두 가지가 있다.

  1. 마지막 리더가 살아나기를 기다린다. 클러스터 전체가 다운되는 최악의 경우가 발생했음에도 메시지 손실 없이 장애 상황을 넘길 수 있는 가장 좋은 방법처럼 보인다. 하지만 리더가 있는 브로커가 되살아나지 않는다면 장애 복구 시간이 길어진다는 단점이 있다.
  2. ISR에서 추방되었지만 먼저 살아나면 자동으로 리더가 된다. 클러스터 내에서 가장 먼저 살아나는 리더 또는 팔로워가 새로운 리더가 된다. 만약에 팔로워가 새로운 리더가 되는 경우라면 메시지 손실이 발생할 수 있다.

두가지 방안 모두 장단점이 분명 존재하기 때문에 어느방법이 옳다고 말하기는 어렵다. 카프카 0.11 이하 버전에서는 기본값으로 2번 방안을 선택했고, 카프카 0.11 버전부터는 1번 방법을 선택했다. 이는 설정을 통해 수정할 수 있다.

  • unclean.leader.election.enable
    • true : 메시지 손실이 발생하더라도 빠른 서비스를 제공하기 위한 방법 (2번)
    • false : 마지막 리더를 기다리는 방법 (1번)

일관성과 가용성 중 어느쪽에 더 초점을 두느냐의 차이다.

5. 카프카에서 사용하는 주키퍼 지노드 역할

아래는 지노드 리스트 중 중요한 몇 가지 지노드를 나타냈다

/kafka

ㄴ /controller

ㄴ /brokers

ㄴ /ids (임시노드)

ㄴ /topics

​ ㄴ /foo-topic

ㄴ /consumers

ㄴ /foo-consumer

​ ㄴ /offsets

ㄴ /config

카프카의 지노드는 /kafka를 사용했다.

  • controller : 클러스터의 컨트롤러 정보를 확인할 수 있다. 카프카는 클러스터 내 브로커 중 하나를 컨트롤러로 선정해 컨트롤러는 브로커 레벨에서 실패를 감지하고 실패한 브로커에 의해 영향을 받는 모든 파티션의 리더 변경을 책임진다. 컨트롤러를 통해 많은 수의 파티션들에 대해 매우 빠르게 배치 형태로 리더을 변경할 수 있다. 만약 컨트롤러인 브로커가 다운되면, 남아 있는 브로커 중 하나가 새로운 컨트롤러가 된다.
  • brokers : 브로커 관련된 정보들이 저장된다.
    • brokers/ids : 브로커는 시작시 /brokers/ids에 broker.id로 지노드를 작성해 자신을 등록한다. 만약 이미 사용중인 broker.id를 등록하려고 시도하면 오류가 발생한다. 주키퍼의 임시 노드를 사용해 등록하고, 만약 브로커가 종료되거나 다운되면 지노드는 사라진다.
    • topics : 클러스터 내 토픽 정보를 확인할 수 있다.
  • consumers : 카프카 0.9 버전 이후부터는 오프셋 저장 장소로 카프카의 토픽을 사용한다. (__consumr_offsets)
  • config : 토픽의 상세 설정 정보를 확인할 수 있다.

주키퍼의 지노드는 두 가지 노드가 존재한다. 영구 노드와 임시 노드이다. 영구 노드는 delete를 호출하여 삭제할 수 있다. 임시 노드는 생성한 클라이언트의 연결이 끊어지거너가 장애가 발생하게 되면 삭제된다.