[Kafka] 아파치 카프카 알아보기(4) - 카프카 디자인

2020. 8. 3. 16:39 Big Data/빅데이터

실시간 로그 집계와 같은 대용량 데이터 처리를 위해서는 애플리케이션의 처리량(Throughput)이 높아야 한다. 카프카에서는 대용량의 실시간 데이터 처리를 위해 배치 전송, 파티션, 분산 기능을 구현했다. 또한 중앙 시스템의 역할을 하는 중요한 서비스에서 서버 장애가 발생하더라도 서비스에 영향이 없도록 데이터의 안정적인 저장을 위해 리플리케이션 기능과 분산된 서버에서 자동으로 파티션의 리더를 선출하는 기능을 구현했다.

 

카프카는 분산된 데이터 파이프라인을 표준화하고 통합하길 원했고, 처리량에 중점을 두고 설계되었다. 이에 따라 카프카에는 높은 처리량과 빠른 메시지 전송, 운영 효율화 등을 위해 분산 시스템, 페이지 캐시, 배치 전송 처리 등의 기능이 구현되었다.

 

카프카 디자인의 특징

1) 분산 시스템

동일한 역할을 하는 서버를 추가해 부하를 분산할 수 있는 것이 분산 시스템의 장점이다. 하지만 불필요하게 서버만 계속 추가하면 불필요한 비용이 증가하는 단점도 있다. 무조건 서버를 늘려 부하를 분산하기보다는 장애 상황과 서버의 리소스 사용량 등을 고려해 적절한 수로 유지하는 것이 좋다. 높은 처리량이 요구된다면 그에 맞게 브로커의 수를 더 늘리면 된다.

 

2) 페이지 캐시

OS는 물리적 메모리에 애플리케이션이 사용하는 부분을 할당하고 남은 잔여 메모리 일부를 페이지 캐시로 유지해 OS의 전체적인 성능 향상을 높이게 된다. 이렇게 잔여 메모리를 이용해 디스크에 읽고 쓰기를 하지않고 페이지 캐시를 통해 읽고 쓰는 방식을 사용하면 처리 속도가 매우 빠르기 때문에 전체적인 성능을 향상시킬 수 있다. 이런 장점으로 카프카를 구성할 때는 디스크 중에서 가격이 가장 저렴한 SATA 디스크를 사용해도 무방하다.

 

2-1) JVM 힙 사이즈

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

 

3) 배치 전송 처리

서버와 클라이언트 사이 또는 서버 내부적으로 데이터를 주고받는 과정에서는 I/O가 발생하기 마련이다. 작은 I/O가 빈번하게 일어나게 되면 이 또한 속도를 저하시키는 원인이 될 수 있다. 따라서 카프카에서는 작은 I/O들을 묶어서 처리할 수 있도록 배치 작업으로 처리한다.

 

서버와 클라이언트 통신 사이에서 매우 작은 메시지를 한번에 하나씩 보내게 되면 그만큼 네트워크 왕복의 오버헤드가 발생하게 된다. 

 



카프카 데이터 모델

카프카가 고성능, 고가용성 메시징 애플리케이션으로 발전한 데는 토픽과 파티션이라는 데이터 모델의 역할이 컸다. 토픽은 메시지를 받을 수 있도록 논리적으로 묶은 개념이고, 파티션은 토픽을 구성하는 데이터 저장소로서 수평 확장이 가능한 단위이다.

 

1) 토픽의 이해

카프카 클러스터는 토픽이라 불리는 곳에 데이터를 저장한다. 토픽 이름은 249자 미만으로 영문, 숫자, '.', '_', '-'를 조합하여 자유롭게 만들 수 있다. 현재 운영하는 카프카 클러스터 또는 앞으로 운영하게 될 카프카 클러스터를 하나의 서비스에 독립적으로 사용하는 것이 아니라 여러 서비스에서 공통으로 사용하게 된다면, 각자 형식에 맞추어 토픽 이름을 구분해주는 것이 좋다.

 

2) 파티션의 이해

카프카에서 파티션이란 토픽을 분할한 것이다. 메시징 큐 시스템의 경우, 메시지의 순서가 보장되어야 해서 이전 메시지 처리가 완료된 후 다음 메시지를 처리하게 된다. 결국 카프카에서 효율적인 메시지 전송과 속도를 높이려면 토픽의 파티션 수를 늘려줘야 한다. 빠른 전송을 위해서 토픽의 파티션을 늘려주는게 좋으며 그 수만큼 프로듀서 수도 늘려야 제대로 된 효과를 볼 수 있다. (초당 받을 수 있는 메시지의 양 고려해서, 파티션 수 조정한다.)

적절한 파티션 수를 측정하기 어려운 경우에는 일단 적은 수의 파티션으로 운영해보고, 프로듀서 또는 컨슈머에서 병목현상이 발생하게 될 때 조금씩 파티션 수와 프로듀서 또는 컨슈머를 늘려가는 방법으로 적정 파티션 수를 할당할 수 있다.

 

카프카에서는 브로커당 약 2,000개 정도의 최대 파티션 수를 권장하고 있기 때문에 과도한 파티션 수를 적용하기보다는 목표 처리량에 맞게 적절한 파티션 수로 유지, 운영하는 것이 좋다.

 

3) 오프셋과 메시지 순서

카프카에서는 각 파티션마다 메시지가 저장되는 위치를 오프셋이라고 부르고, 오프셋은 파티션 내에서 유일하고 순차적으로 증가하는 숫자(64비트 정수) 형태로 되어있다.

오프셋은 하나의 파티션 내에서만 유일한 숫자이다. 토픽 기준으로 오프셋이 0인 것을 찾아보면 전부 3개가 존재하게 된다. 하지만 0번 파티션 기준으로 보면 오프셋 0은 유일한 값이다. 카프카에서는 이 오프셋을 이용해 메시지의 순서를 보장한다. 만약 컨슈머가 파티션 0에서 데이터를 가져간다고 가정하면, 오프셋 0, 1, 2, 3, 4, 5 순서대로만 가져갈 수 있다. 절대로 오프셋 순서가 바뀐 상태로는 가져갈 수 없다.

고가용성과 리플리케이션

카프카는 분산 애플리케이션으로 서버의 물리적 장애가 발생하는 경우에도 높은 가용성을 보장한다. 이를 위해 카프카는 리플리케이션(복제) 기능을 제공한다. 카프카의 리플리케이션은 토픽 자체를 리플리케이션하는 것이 아니라, 토픽을 이루는 각각의 파티션을 리플리케이션하는 것이다.

 

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

카프카에서는 Replication Factor라는 것이 있다. 카프카의 기본값 설정은 1로 설정되어 있으며, 이를 변경하고 싶으면 카프카 설정 파일에서 수정할 수 있다. default.replication.factor 항목을 원하는 숫자로 변경하면 된다. 이 설정 값은 아무런 옵션을 주지 않고 토픽을 생성할때 적용되는 값이고, 각 토픽별로 다른 리플리케이션 팩터 값을 설정할 수 있다. 그리고 운영 중에도 토픽의 리플리케이션 팩터 값은 변경할 수 있다.

 

리플리케이션으로 구성된 대부분의 싯트메들은 원본과 그것을 리플리케이션한 복제본을 구분하기 위해 각기 다른 용어로 부른다. 이러한 용어는 애플리케이션마다 조금씩 달라, 주키퍼에서는 리더와 팔로워라고 부르며 RabbitMQ에서는 Master Queue와 Mirrored Queue로 부른다. 카프카에서는 주키퍼와 동일하게 리더(원본)와 팔로워(복제본)라고 부른다.

 

리더와 팔로워는 각자 역할이 나뉘어 있는데, 가장 중요한 핵심은 모든 읽기와 쓰기가 리더를 통해서만 일어난다는 점이다. 즉 팔로워는 리더의 데이터를 그대로 리플리케이션만 하고 읽기와 쓰기에는 관여하지 않는다. 리더와 팔로워는 저장된 데이터의 순서도 일치하고 동일한 오프셋과 메시지들을 갖게 된다.

 

카프카의 리플리케이션 기능을 이용해 리플리케이션된 토픽의 서버가 다운되는 상황이 발생하더라도 리더 변경으로 별다른 문제없이 프로듀서의 요청들을 처리할 수 있게 된다. 

 

2) 리더와 팔로워의 관리

리더는 모든 데이터의 읽기 쓰기에 대한 요청에 응답하면서 데이터를 저장해나가고, 팔로워는 리더를 주기적으로 보면서 자신에게 없는 데이터를 리더로부터 주기적으로 가져오는 방법으로 리플리케이션을 유지한다. 

 

(1) 프로듀서는 A메시지를 토픽의 리더에게 보낸다.

(2) 프로듀서가 보내는 A메시지는 토픽의 리더만이 읽고 쓰기를 할 수 있기 때문에 프로듀서의 요청을 받고 저장한다. 팔로워들은 매우 짧은 주기로 리더에 새로운 메시지가 저장된 것이 없는지 확인한다. 리더는 팔로워들이 주기적으로 데이터를 확인하고있는지 확인하여 만약 설정된 일정 주기(replica.lag.time.max.ms)만큼 확인요청이 오지 않는다면, 리더는 해당 팔로워의 이상을 감지하고, 해당 팔로워는 더이상 리더의 역할을 대신할 수 없다고 판단해 ISR 그룹에서 해당 팔로워를 추방시키게 된다. ISR에서 추방당한 팔로워는 추방과 동시에 ISR 그룹에서의 리더 자격도 박탈당하게 된다.

(3) 리더가 팔로워2를 ISR 그룹에서 추방시키자 ISR 그룹 구성원은 3에서 2로 축소되었다. 팔로워1은 리더에게 새로운 메시지가 있음을 확인하고 컨슈머들이 메시지를 가져가는 방법처럼 리더의 메시지를 가져와 저장한다.

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

 

1. 마지막 리더가 살아나기를 기다린다.



마지막 리더에게는 메시지 A, B, C가 모두 저장되어 있어 현재는 카프카 클러스터가 다운된 상태이지만 나중에 살아난다면 메시지 손실없이, 프로듀서의 요청들을 처리하면서 서비스를 지속적으로 제공할 수 있다. 클러스터 전체가 다운되는 최악의 경우가 발생했음에도 메시지 손실 없이 장애 상황을 넘길 수 있는 가장 좋은 방법처럼 보이지만 여기서 "재시작시 마지막 리더가 반드시 시작되어야 한다."는 필수 조건이 하나 있다.

 

장애 상황이 자신의 의도와 다르게 동작하는 경우도 있기 때문에, 리더가 있는 브로커는 알 수 없는 이유로 되살아지 않을 수 있다. 결국 마지막 리더가 정상화될 때까지 전체 카프카 클러스터의 장애가 길어지게 된다.

 

2. ISR에서 추방되었지만 먼저 살아나면 자동으로 리더가 된다.

마지막 리더가 아닌 클러스터 내에서 가장 먼저 살아나는 리더 또는 팔로워가 새로운 리더가 된다. 하지만 마지막 리더가 아닌 ISR에서 추방당한 팔로워가 새로운 리더가 되면 메시지 손실이 발생한다. 



(1) 카프카 클러스터 전체가 다운되는 현상이 발생했고, 1대씩 다운되면서 리더, 팔로워1, 팔로워2가 가지고 있는 메시지는 서로 모두 다른 상태이다.

(2) 팔로워2가 있는 브로커가 먼저 정상화되었고, 토픽의 리더가 아무도 없기 때문에 자동으로 팔로워2가 새로운 리더가 되었다. 새로운 리더가 되면서 프로듀서가 보내는 D에 대한 요청을 처리하고 D메시지를 저장하게 된다.

(3) 올드 리더가 있는 브로커와 팔로워1이 있는 브로커도 뒤늦게 정상화되었으나, 토픽의 리더를 확인했을때 이미 리더가 존재하기 때문에 모두 팔로워가 되면서 리더와 동기화 작업을 시작하게 되고, 결국 새로운 리더와 동일한 상태를 유지하게 된다.

 

비록 메시지는 일부 손실되었지만 클러스터 전체가 다운된 상황에서 브로커 하나만이라도 빠르게 정상화되어 서비스 역시 빠르게 정상화가 될 수 있었다. 

 

카프카에서는 0.11.0.0 이하 버전에서는 기본값으로 2번 방안을 선택해 일부 데이터 손실이 발생하더라도 서비스적인 측면에서 빠르게 서비스를 제공할 수 있게 해준다. 하지만 0.11.0.0 버전부터는 기본값을 1번 방안으로 선택해 데이터 손실 없이 마지막 리더가 살아나기를 기다린 후 서비스를 되게 대처한다.

 

해당 항목(unclean.leader.election.enable)은 true, false로 설정을 변경할 수 있는데 false는 메시지 손실의 관점에서 마지막 리더를 기다리는 방법, true는 서비스적 관점에서 메시지 손실이 발생하더라도 빠른 서비스를 제공하기 위한 방법이다. 해당 옵션값은 가용성과 일관성 중 어느 쪽에 더 초점을 두느냐의 차이다. 카프카를 운영하는 환경에 따라 빠른 서비스 제공을 선호하는지, 데이터 무손실을 선호하는지에 따라 다르기 때문이다.

카프카의 주키퍼 지노드 역할

주키퍼의 CLI(Command Line Interface)에 접근하는 방법은 준비한 주키퍼 서버에 접속한 후 zkCli.sh를 실행하면 된다. 접속한 후 리스트를 확인하는 ls / 명령어를 이용하여 지노드들을 확인한다. 



/peter-kafka/controller

현재 카프카 클러스터의 컨트롤러 정보를 확인할 수 있다. 리더를 선정하는 프로세스는 굉장히 중요하며 브로커의 실패 등으로 인해 리더가 변경되면 모든 파티션에 대해 파티션마다 리더 선출작업이 필요하게 된다. 카프카에서는 클러스터 내 브로커 중 하나를 컨트롤러로 선정해 컨트롤러는 브로커 레벨에서 실패를 감지하고 실패한 브로커에 의해 영향받는 모든 파티션의 리더 변경을 책임지고 있다. 컨트롤러를 통해 많은 수의 파티션들에 대해 매우 바르게 배치 형태로 리더십을 변경할 수 있다. 컨트롤러는 클러스터 내 브로커가 임의로 선정되고 만약 컨트롤러인 브로커가 다운되면, 남아있는 브로커 중 하나가 새로운 컨트롤러가 된다.

 

/peter-kafka/brokers

브로커 관련된 정보들이 저장되며 카프카 설치할 때 브로커 컨피그에서 수정한 broker.id를 확인할 수 있다. 브로커는 시작시 /brokers/ids에 broker.id로 지노드를 작성해 자신을 등록한다. 만약 이미 사용중인 broker.id를 등록하려 시도하면 오류가 발생한다.

 

주키퍼의 임시노드를 사용해 등록하고, 만약 브로커가 종료되거나 다운되면 지노드는 사라진다. 추가로 topics라는 지노드의 정보를 확인해보면 클러스터 내 토픽 정보들도 확인할 수 있다. 토픽의 파티션 수, ISR 구성 정보, 리더 정보 등을 확인할 수 있다.

 

/peter-kafka/consumers

컨슈머 관련된 정보들이 저장되며, 컨슈머가 각각의 파티션들에 대해 어디까지 읽었는지 기록하는 오프셋 정보가 이곳에 저장된다. 오프셋 정보는 지워지면 안되는 정보이기 때문에 오프셋 관련 정보들은 주키퍼의 영구노드로 저장된다.

 

카프카 0.9 버전 이후 부터는 컨슈머 오프셋 저장 장소를 주키퍼 또는 카프카의 도픽을 선택할 수 있도록 제공하고 있지만, 향후 카프카의 릴리스 버전에서는 주키퍼에 오프셋을 저장하는 방법은 서비스가 종료될 예정이다. 오프셋 저장방식을 주키퍼가 아닌 카프카의 토픽으로 저장해 사용하는것이 권장된다.

 

/peter-kafka/config

토픽의 상세 설정 정보를 확인할 수 있다. 토픽 생성시 기본값으로 생성한 작업 외에 상세 설정 추가를 통해 retention.ms 등을 별도로 설정한 경우 해당 옵션 정보를 확인할 수 있다.

 

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



출처: https://12bme.tistory.com/532?category=737765 [길은 가면, 뒤에 있다.]