본문 바로가기

컴퓨터/아파치 카프카

5. 카프카 상세 개념

토픽과 파티션

토픽은 카프카에서 가장 중요한 것이다

토픽을 만들면서 카프카가 시작되고 토픽을 삭제하면 데이터가 삭제되고 파이프라인이 중단된다.

적정 파티션 개수

토픽의 파티션 개수는 카프카의 성능과 관련이 있다. 

따라서 토픽을 운영함에 있어 적절한 파티션 개수를 설정하고 운영하는 것이 매우 중요하다.

이때 고려해야할 점은 3가지이다.

1. 데이터 처리량

2. 메시지 키 사용 여부

3. 브로커, 컨슈머 영향도

 

파티션은 카프카의 병렬처리의 핵심이다.

파티션의 개수가 많아지면 많아질수록 매핑되는 컨슈머의 개수가 늘어나기 때문이다.

따라서 파티션의 개수를 정할 때 해당 토픽에 필요한 데이터 처리량을 측정하는 것이 중요하다.

 

데이터 처리 속도를 올리는 법 2가지

1. 컨슈머의 처리량을 늘리는 것

2. 컨슈머를 추가하여 병렬처리량을 늘리는 것

 

컨슈머 특성상 다른 시스템들과 연동되기 때문에 일정 수준 이상 처리량을 올리는 것은 매우 어렵다.

하지만 파티션 개수를 늘리고 파티션 개수만큼 컨슈머를 추가하는 방법은 데이터 처리량을 늘리는 확실한 방법이다.

따라서 프로듀서의 데이터 양과 컨슈머의 처리량을 계산해서 파티션 개수를 정하면 된다.

이때 전체 컨슈머 데이터 처리량이 프로듀서가 보내는 데이터보다 적다면 컨슈머 랙이 생기기 때문에 컨슈머 데이터 처리량이 프로듀서 데이터 처리량보다 많아야 한다.

 

파티션 개수를 무조건 늘리는 것만이 좋은것은 아니다.

파티션 수를 늘리게되면 컨슈머 브로커의 부담이 있기 때문에 데이터처리를 할 때 지연 발생에 따른 서비스 영향도 같이 고려하여 파티션 수를 구하는 것이 좋다.

 

메시지 키를 사용함과 동시에 데이터 처리 순서를 지켜야 하는 경우에 대비해 고려해야 한다.

메시지 키 사용 여부는 데이터 처리 순서와 관련이 있는데 메시지 키를 사용하면 프로듀서가 토픽으로 데이터를 보낼 때 메시지 키를 해시 변환하여 메시지 키를 파티션에 매칭한다.

만약 파티션 개수가 달라지면 이미 매칭된 파티션과 메시지 키의 매칭이 깨지고 다른 파티션에 할당이 되기 때문에 컨슈머는 특정 메시지 키의 순서를 보장받지 못한다.

따라서 메시지 처리 순서가 보장되어야 한다면 파티션의 개수를 변하지 않게 해야하고, 변해야 하는 경우 기존의 메시지 키의 매칭을 그대로 가져갈 수 있는 커스텀 파티셔너를 개발해야한다.

따라서 이를 막기위해 파티션 개수를 전송하는 데이터양보다 넉넉하게 잡고 생성하는 것이 좋다.

 

마지막으로 고려할 점은 브로커와 컨슈머의 영향도로 파티션이 늘어나는 만큼 브로커에서 접근하는 파일의 수가 많아지는데 운영체제에서 프로세스당 열 수 있는 파일의 최대 개수가 제한되기 때문에 안정성을 위해 너무 많이 파티션이 할당된 경우 분산을 위해 브로커의 수를 늘려야 한다.

토픽 정리 정책

토픽의 데이터는 시간 또는 용량에 따라 삭제 규칙을 적용할 수 있다. 

따라서 데이터를 사용하지 않을 경우 cleanup.policy 옵션을 사용해 데이터를 삭제할 수 있는데 delete의 완전 삭제와 compact의 동일 메시지 키의 가장 오래된 데이터 삭제가 있다.

 

토픽 삭제 정책(delete policy)

토픽의 데이터를 삭제하는 것으로 삭제할 때는 세그먼트 단위로 삭제를 진행한다.

여기서 세그먼트는 토픽의 데이터를 저장하는 명시적인 파일 시스템 단위이다.

세그먼트는 파티션마다 별개로 생성이 되고, 세그먼트의 파일 이름은 오프셋 중 가장 작은 값이 된다.

 

토픽과 세그먼트

 

삭제 정책이 실행되는 시점은 시간 또는 용량이 기준이 된다.

retention.ms를 통해 토픽의 데이터를 유지하는 기간을 밀리초로 설정할 수 있다.

카프카는 일정 주기마다 세그먼트 파일의 마지막 수정 시간과 retention.ms를 비교하여 이를 넘어가면 세그먼트를 삭제한다.

 

토픽 압축 정책(compact policy)

토픽의 압축 정책은 일반적인 압축과 다른 개념으로 메시지 키별로 해당 메시지 키의 레코드 중 오래된 데이터를 삭제하는 정책이다.

이러한 방식 때문에 1개 파티션에서 오프셋의 증가가 일정하지 않을 수 있다. 즉, 중간에 중복되는 데이터를 가진 메시지 키를 가지면 삭제가 될 수 있다.

토픽 압축 정책

 

압축 정책은 액티브 세그먼트를 제외한 나머지 세그먼트들에 한해서 데이터를 처리한다.

여기서 액티브 세그먼트를 제외한 세그먼트에 남아있는 데이터의 테일 영역은 브로커의 압축 정책에 의해 압축이 완료된 레코드들을 의미하는 것으로 클린 로그라고 한다.

그리고 헤드 영역은 압축이 되기 전 레코드들이 있는 부분을 의미하는 것으로 더티 로그라고 한다.

테일, 헤드 영역

더티 비율은 더티 영역의 메시지 개수를 압축 대상 세그먼트에 남아있는 데이터의 총 레코드 수로 나눈 비율을 뜻하는 것으로 토픽의 압축 옵션값을 설정하여 해당 값보다 더티 비율이 커지게 되면 압축을 수행한다.

ISR(In-Sync-Replicas)

ISR은 리더 파티션과 팔로워 파티션이 모두 싱크가 된 상태로 만약 리더 또는 팔로워 파티션의 브로커가 장애가 발생하더라도 데이터를 안전하게 사용할 수 있다.

프로듀서가 특정 파티션에 데이터를 저장하는 작업은 리더 파티션을 통해 처리가 되는데 리더 파티션에 새로운 레코드가 추가되어 오프셋이 증가하면 팔로워 파티션이 위치한 브로커는 리더 파티션의 데이터를 복제한다.

이때 복제하는데 시간차가 존재하기 때문에 파티션간 오프셋 차이가 발생한다.

이러한 차이를 모니터링하기 위해서 리더 파티션은 replica.lag.time.max.ms값 만큼의 주기를 가지고 팔로워 파티션이 데이터를 복제하는지 확인한다.

만약 이 시간보다 더 긴 시간 동안 데이터를 가져가지 않는다면 해당 팔로워 파티션에 문제가 생긴 것으로 판단하고 ISR그굽에서 제외한다.

 

ISR로 묶인 리더와 팔로워 파티션은 모든 데이터가 동일하기 때문에 팔로워 파티션은 리더 파티션으로 새로 선출될 자격을 가진다.

이때 ISR인 팔로워 파티션이 없다면 리더 파티션이 존재하는 브로커가 다시 실행될 때 까지 기다린다.

따라서 서비스가 중단되 장애가 발생하게 된다.

ISR이 아닌 팔로워를 리더로 선출

ISR이 아닌 팔로워 파티션을 리더로 선출할 수 있는데 이때 동기화 되지 않은 일부 데이터는 유실될 수 있다.

일부 데이터가 유실되지만 토픽을 사용하는 서비스의 중단을 발생하지 않게 된다.

카프카 프로듀서

프로듀서는 카프카에 데이터를 저장하는 첫 단계이다.

acks옵션

acks옵션은 프로듀서가 전송한 데이터가 카프카 클러스터에 얼마나 신뢰성을 높게 저장할지 지정할 수 있다. 그리고, acks옵션에 따라 성능이 달라질 수 있으므로 acks옵션에 따른 카프카의 동작 방식을 상세히 알고 설정해야 한다.

 

acks = 0인경우

프로듀서가 리더 파티션으로 데이터를 전송했을 때 리더 파티션으로 데이터가 저장되었는지 확인하지 않는다는 뜻이다.

리더 파티션은 데이터가 저장된 후 데이터가 몇 번째 오프셋에 저장되었는지 리턴하는데 acks가 0으로 설정되어있으면 데이터가 저장되었는지 여부에 대한 응답을 받지 않는다.

따라서 네트워크 오류나 브로커의 이슈 등으로 인해 데이터가 유실되더라도 응답값을 받지 않기 때문에 지속적으로 다음 데이터를 보낼 수 있고, 전송 속도가 빠르다.

 

acks = 1인 경우

프로듀서가 보낸 데이터가 리더 파티션에만 정상적으로 적재 되었는지 확인한다.

이때 복제 개수가 2이상으로 운영할 경우 리더에는 적재가 되어도 팔로워 파티션에 동기화가 안되었을 수 있는데 복제하기 전에 리더 파티션이 있는 브로커에 장애가 발생하면 동기화 못한 일부 데이터가 유실될 수 있다.

acks = all or -1인 경우

보낸 데이터가 리더 파티션과 팔로워 파티션에 모두 정상적으로 적재되었는지 확인한다.

이때 min.insync.replicas 옵션값에 따라 ISR에 포함된 파티션만 적재를 확인할 수 있어 옵션값에 따라 데이터의 안정성이 달라진다.

다만 이때 카프카 브로커의 수가 옵션값보다 작은 경우 프로듀서가 더는 데이터를 전송할 수 없기 때문에 이를 고려해야한다.

멱등성 프로듀서

멱등성은 여러 번 연산을 수행해도 동일한 결과를 나타내는 것으로 동일한 데이터를 여러 번 전송해도 카프카 클러스터에 단 한 번만 저장됨을 뜻한다.

이는 기본 프로듀서와 달리 데이터를 브로커로 전달할 때 프로듀서 PID와 시퀀스 넘버를 함께 전달하여 동일한 메시지의 적재 요청이 오더라도 한 번만 적재하게 한다. 

다만 멱등성 프로듀서로 동작하는 프로듀서 애플리케이션이 종료되고 재시작하면 PID가 달라지기 때문에 동일한 데이터를 보내도 다른 PID로 인식해 중복해 적재되게 된다.

트랜잭션 프로듀서

다수의 파티션에 데이터를 저장할 경우 모든 데이터에 대해 동일한 원자성을 만족시키기 위해 사용된다.

원자성을 만족시킨다는 의미는 다수의 데이터를 동일한 트랜잭션으로 묶음으로써 전체 데이터를 처리하거나 전체 데이터를 처리하지 않도록 하는 것을 의미한다.

트랜젝션은 파티션의 레코드로 구분하는데 트랜잭션 프로듀서는 사용자가 보낸 데이터를 레코드로 파티션에 저장하고, 트랜젝션의 시작과 끝을 표현하기 위해 트랜잭션 레코드를 한개 더 보낸다.

트랜잭션 컨슈머는 파티션에 저장된 레코드를 보고 완료되었음을 확인하고 데이터를 가져간다.

여기서 레코드는 실질적인 데이터는 가지지않고, 끝난 상태만 표시하는 정보만 가지고 있는다.

카프카 컨슈머

멀티 스레드 컨슈머 

카프카는 처리량을 늘리기 위해 파티션과 컨슈머 개수를 늘려 운영할 수 있다.

파티션을 여러개로 운영하는 경우 데이터를 병렬처리하기 위해 파티션의 수와 컨슈머 개수를 동일하게 맞추는 것이 가장 좋다.

토픽의 파티션은 1개 이상이고 1개의 파티션은 1개의 컨슈머가 할당되어 데이터를 처리한다.

즉, n개의 파티션이 있으면 컨슈머 스레드를 최대 n개 운영할 수 있다.

 

멀티 스레드로 컨슈머를 안전하게 운영 하기 위해서는 컨슈머 스레드에서 예외적인 상황이 발생할 경우 비정상적인 종료가 각 컨슈머 스레드 간에 영향이 미치지 않도록 스레드 세이프 로직, 변수를 적용해야 한다.

 

카프카 컨슈머 멀티 워커 스레드 전략

브로커로부터 전달받은 레코드들을 병렬로 처리한다면 1개의 컨슈머 스레드로 받은 데이터들을 더욱 향상된 속도로 처리할 수 있다.

데이터를 반복문으로 처리하면 이전의 레코드의 처리가 끝날 때 까지 기다려야하는데 레코드별 처리 시간이 길 경우 처리 속도가 느려지게 된다.

따라서 멀티 스레드를 사용하면 각기 다른 레코드들의 데이터 처리를 동시에 실행할 수 있기 때문에 처리 시간을 현저히 줄일 수 있다.

 

이때 데이터 처리 순서가 다르면 안 되는 이유는 스트리밍 데이터 프로세싱에서 데이터 처리가 민감한 경우가 있기 때문이다.

순서에 따라 조건에 부합여부가 달라져 코드의 실행 결과가 달라지게 될 수 있기 때문이다.

 

카프카 컨슈머 멀티 스레드 전략

하나의 파티션은 동일 컨슈머 중 최대 1개까지 할당이 되고, 하나의 컨슈머는 여러 파티션에 할당될 수 있다.

이런 특징을 살려 1개의 애플리케이션에 구독하고자 하는 토픽의 파티션 개수만큼 컨슈머 스레드 개수를 늘려 운영하면 된다.

컨슈머의 수를 늘리면 각 스레드가 각 파티션에 할당되어 파티션의 레코드들을 병렬처리할 수 있다.

단, 컨슈머의 수가 파티션의 수보다 많아지면 할당할 파티션 개수가 더는 없으므로 파티션에 할당되지 못한 컨슈머 스레드는 데이터 처리를 하지 않게 된다.

 

컨슈머 랙

컨슈머 랙은 토픽의 최신 오프셋과 컨슈머 오프셋 간의 차이이다.

프로듀서는 계속 새로운 데이터를 파티션에 저장하고, 컨슈머는 자신이 처리할 수 있는 만큼 데이터를 가져가기 때문에 컨슈머 랙을 확인하여 컨슈머가 정상 동작하는지 확인할 수 있다.

파티션 3개의 토픽을 컨슈머가 구독

만약 프로듀서가 전송하는 데이터양이 늘어나게 되더라도 최대 처리량은 한정되어있어 지연을 줄이기 위해 일시적으로 파티션의 수와 컨슈머 개수를 늘려 병렬처리량을 늘리는 방법을 사용할 수 있다.

 

카프카 버로우

링크드인에서 공개한 오픈소스 컨슈머 랙 체크 툴로 카프카 클러스터와 연동을 통해 컨슈머 그룹별 컨슈머 랙을 조회할 수 있다.

이는 다수의 카프카 클러스터에 동시에 연결하여 컨슈머 랙을 확인하는데 한 번의 설정으로 다수의 카프카 클러스터 컨슈머 랙을 확인할 수 있다는 장점이 있다.

버로우 기능중에 가장 돋보이는 것은 파티션의 상태태를 단순히 컨슈머 랙의 임계치로 나타내지 않았다는 점이다.

특정 파티션의 컨슈머 랙이 특정 시점에 많아졌다고 이슈가 있다고 단정시킬수 없다.

왜냐하면 데이터를 많이 보내면 일시적으로 임계치가 넘어가는 현상이 발생할 수 있기 때문이다.

따라서 임계치가 아닌 슬라이딩 윈도우 계산을 통해 문제가 생긴 파티션과 컨슈머의 상태를 표현한다.

처음 이미지는 자주 볼 수 있는 상태로 파티션과 컨슈머 모두 OK인 상태이다.

두 번째 이미지는 프로듀서가 추가하는 최신 오프셋에 컨슈머가 따라가지 못하는 것으로 파티션은 OK, 컨슈머는 WARNING으로 나타낸다.

마지막은 오프셋이 지속적으로 증가하지만 컨슈머 오프셋이 멈춘것으로 파티션이 STALLED상태이고, 컨슈머는 ERROR인 경우이다.

컨슈머가 ERROR인 경우는 확실히 비정상 동작을 하고 있으므로 알람을 주어 즉각 조치해야한다.

 

컨슈머 배포 프로세스

중단 배포

중단 배포는 컨슈머 애플리케이션을 완전히 종료한 이후 개선된 코드를 가진 애플리케이션을 배포하는 방식으로 한정된 서버 자원을 운영하는 기업에 적합하다.

이는 중단을 하기 때문에 기존 컨슈머 애플리케이션이 종료되면 컨슈머 랙이 늘어나게 되어 지연이 발생하게 된다.

따라서 해당 파이프라인을 운영하는 서비스는 중단되기 때문에 신뢰성 있는 배포 시스템을 가진 기업에서 중단 배포를 사용하는 것을 추천한다.

 

무중단 배포

컨슈머의 중단이 불가능한 애플리케이션의 신규 로직 배포가 필요할 경우 무중단 배포가 필요하다.

이는 인스턴스 발급과 반환이 유연한 가상 서버를 사용하는 경우 유용하다.

 

블루/그린 배포는 이전 버전 애플리케이션과 신규 버전 애플리케이션을 동시에 띄워놓고 트래픽을 전환하는 방법이다.

이는 파티션의 수와 컨슈머 개수를 동일하게 실행하는 애플리케이션을 운영할 때 유리하다.

왜냐하면 신규 버전 애플리케이션을 배포하고 동일 컨슈머 그룹으로 파티션을 구독하도록 실행하면 신규 애플리케이션의 컨슈머들은 파티션을 할당받지 못하고 유휴상태로 기다릴 수 있기 때문이다.

그리고 신규 애플리케이션이 준비가 되면 기존의 애플리케이션을 모두 중단한다.

 

롤링 배포는 파티션의 개수가 인스턴스 개수와 같아지거나 그 보다 많은 경우에 쓸 수 있다.

이는 먼저 하나의 인스턴스를 신규 버전으로 실행하고 모니터링한 이후 다음 인스턴스를 신규 버전으로 배포하는 방식으로 진행된다.

따라서 파티션 개수가 많을수록 리밸런스 시간도 길어져 파티션의 수가 적은 경우 유용하다.

 

카나리 배포는 작은 위험을 통해 큰 위험을 예방하는 방법으로 많은 데이터 중 일부분을 신규 버전의 애플리케이션에 먼저 배포하여 이슈가 없는지 사전에 탐지한다.

만약 문제가 없으면 나머지 부분에 대해 롤링 또는 블루/그린 배포를 통해 무중단 배포를 하면 된다.

 

스프링 카프카

카프카를 스프링 프레임워크에서 효과적으로 사용할 수 있도록 만들어진 라이브러리로 카프카 클라이언트에서 사용하는 여러 패턴을 미리 제공한다.

스프링 카프카 프로듀서

스프링 카프카는 카프카 템플릿이라고 불리는 클래스를 사용하여 데이터를 전송할 수 있다.

카프카 템플릿은 프로듀서 팩토리 클래스를 통해 생성이 가능한데, 여기서 스프링 카프카의 기본 템플릿을 사용할 수도 있고, 사용자가 직접 템플릿을 프로듀서 팩토리로 생성할 수 있다.

스프링 카프카 컨슈머

컨슈머는 기존 컨슈머를 2개의 타입으로 나누고 커밋을 7가지로 나누어 세분화했다.

타입은 레코드 리스너와 배치 리스너가 있는데 레코드 리스너의 경우 1개의 레코드를 호출하여 처리하고, 배치 리스너는 한 번에 여러 개의 레코드들을 처리한다.

그리고 커밋을 RECORD, BATCH, TIME, COUNT, COUNT_TIME, MANUAL, MANUAL_IMMEDIATE로 세분화하고 미리 로직을 만들어놓았다.

커밋에 대한 설명