카프카 프로듀서 필수 설정값
기본적인 프로듀서 설정값들을 정리한 글이다.
실무에서 쓰일만한 설정값들을 대충 훑어보고 개념으로 넘어가자.
마이크로서비스에서 카프카를 이용할 때는 속도가 중요하다. 그럴 때는 아래와 같은 설정이 필요하다.
1
2
3
4
5
6
7
// 마이크로서비스
batch.size=32768
linger.ms=5
acks=1
max.in.flight.requests.per.connection=5
enable.idempotence=true
compression.type=snappy
반면에 로그를 처리하는 용도로 카프카를 이용한다면 아래와 같은 설정이면 충분하다.
1
2
3
4
5
6
7
// 로깅용
batch.size=262144
linger.ms=300
acks=0
max.in.flight.requests.per.connection=10
enable.idempotence=false
compression.type=zstd
위 6가지 설정은 아주 기본적인 프로듀서 옵션들인데 성능 vs 안정성에 따라 위 옵션들의 값이 바뀌게 된다.
하나하나 알아보자.
사전 지식 Record: 하나의 이벤트, 하나의 메시지. Batch: Record 묶음. 카프카는 보통 이벤트 하나하나를 전송하지않고 Batch로 묶어서 보낸다.
한번 메시지 보낼때 얼마나 보낼지
“batch.size”
프로듀서가 메시지를 보낼 때의 최대 크기를 설정한다. 기본 값은 16KB.
기본적으로 프로듀서는 메시지를 보내기전에 batch buffer에 쌓는다. 그리고 batch.size 값에 도달하면 바로 전송된다. 혹은 아래 설명할 linger.ms 시간에 도달하면 batch.size에 도달하지않아도 브로커로 batch를 전송된다.
당연하게도 프로듀서와 브로커 간의 커뮤니케이션이 많으면 많아질수록 하드웨어에 부담이 늘어난다. 커뮤니케이션 횟수가 많아지면 네트워크 부하는 당연히 커지고. “네트워크 패킷 -> 카프카 객체”로 직렬화하는 일도 많아지니 CPU 사용량도 늘어난다.
그래서 신속한 처리가 필요할 때는 batch.size를 작게 설정한다.
카프카가 마이크로서비스에서 이용된다면 batch.size를 최소화(32kb이하)해서 이용하고 로그처리와 같이 급하지않은 것들은 꽤 큰 값(256kb 정도)을 이용한다.
메시지를 얼마 시간 동안 모아서 보낼까
“linger.ms”
batch.size랑 똑같다고 생각하면 된다. 다만 사이즈가 아니라 시간일뿐이다. batch를 보내는 텀을 설정하는 옵션이고. 작게 설정하면 batch.size 설정 값만큼 데이터가 차지않아도 시간이 되면 batch를 보낸다. 나머지는 똑같다.
보낸 메시지를 잘 저장했는지 확인
“acks”
acks는 브로커가 메시지를 저장한 후, 프로듀서에게 응답을 보내는 조건을 설정하는 옵션이다. 기본값은 1이다. 초반엔 헷갈릴수 있는 점이 하나 존재한다. acks는 몇개의 복제본을 만드는지에 대한 설정이 아니라, “전체 복제본 생성 과정중에 몇개의 복제본이 생긴 뒤에 응답하느냐”를 설정하는것이다. 아직도 비슷하다 느낄수 있어 더 설명하자면 카프카에서 복제본은 ISR 갯수만큼 만들게 된다. ISR이 복제본을 만드는 과정에서 몇개를 만든 시점에 프러듀서에게 응답을 보낼까 라는 설정이다. ISR 갯수가 5개일때 acks가 1개라면 ISR 5개 모두 복제본 생기는 와중에 1개만 복제되면 응답을 보낸다. 카프카는 브로커 시작 시 설정된 “replication.factor” 값만큼 메시지의 복제본을 만든다. replication.factor만큼 복제하기전에 acks 설정값만큼 복제되면 중도에 미리 응답하게 된다.
이 값을 작게 하면 리더만 저장해도 되므로 처리 속도가 빠르지만, 리더 브로커가 망가졌을 때 데이터 유실 위험이 있다. 반면, 크게 설정하면 ISR(동기화된 복제본) 모두가 저장해야 응답을 보내므로 속도는 느리지만 안정성이 높아진다.
설정 가능값은 0, 1, all. 3가지 존재한다.
프로듀서가 동시에 몇개의 메시지를 보낼수 있는지 설정하기.
“max.in.flight.requests.per.connection”
max.in.flight.requests.per.connection 설정이 작동하는 방식은 꽤나 재밌다. 이 설정은 파티션이 얼만큼의 메시지를 동시에 보낼 수 있는지 조절하는 설정값이다. 기본 값은 5로 설정돼있다. 값이 5로 설정돼있다면 동시에 진행될수 있는 메시지 전송 프로세스는 최대 5개로 제한된다. 조절하는 방식은 Queue를 이용한다. 설정값만큼의 길이를 가지는 in.flight.queue가 따로 존재한다. 그리고 프로듀서가 메시지를 보내기전에 queue에 메시지를 넣을수 있는지 확인한다. (queue.length < max.in.flight.request.per.connection 값) 전달이 시작된 메시지들은 in.flight 큐로 들어가게 되고 완료 처리 될때까지 queue안에서 관리된다.
기본 작동 방식은 아래와 같다.
1
2
3
4
5
1. in flight 큐에 들어가있는 메시지 갯수가 max.in.flight.requests.per.connection 수보다 작은지 확인한다.
2. 작다면 브로커에 메시지를 보내고 in flight 큐에 넣는다.
3. 브로커에서 ask를 받았다.
4. 프로듀서에서 브로커에 전달 완료했음을 처리한다.
5. 완료 처리된 메시지는 queue에서 제거한다.
여기서 중요한 점은 다른 설정들에 따라 작동방식이 달라진다는 점이다.
1. “acks”에 따른 변화
acks = 0 이 경우에는 브로커가 메시지를 받았다는 응답을 기다리지않는다. 프로듀서에서 브로커에 메시지를 보내버리고 끝이다. (브로커가 제대로 받던 말던 신경안씀) 그렇기때문에 이 상황에서는 max.in.flight 설정이 무의미해진다. queue를 확인하는 절차도 queue에 메시지를 넣는 작업도 하지않게된다.
acks > 0 이 경우에는 프로듀서는 브로커가 메시지를 잘저장했는지에 대한 응답을 기다리기때문에 메시지를 전송하고 해당 응답을 기다리게된다. 응답을 기다리는 곳이 in.flight.queue인 것이다. 그러므로 in.flight 옵션이 정상 작동된다.
2. enable.idempotence에 따른 변화
enable.idempotence == true 이 경우가 꽤나 재미있다. true로 설정돼있을 때는 메시지 전송시에 sequence number를 같이 보내고 브로커로부터 완료 메시지를 받을 때도 동일한 sequence number를 받게된다. in.flight 큐에 들어간 순서대로 sequence number가 커지게 되니까 당연히 head(맨앞) 값이 제일 작다. 이때 여러개의 메시지를 비동기로 같이 보내지만 순서의 안정성을 이룰수 있는 방법이 생기게된다. 브로커로 받은 메시지의 sequence number가 큐 맨앞(head)의 sequence number보다 크다면 순번이 후순위이기 때문에 head의 메시지가 완료되기까지 보류된다.
후순위가 먼저 응답됐을 때 보류됨으로써 얻어지는 이점은 꽤나 강력하다. “선순위의 메시지가 완료되어 큐에서 빠지기전까지는 후순위 메시지들도 빠질수 없기때문에 보내는 양이 자동으로 조절된다.” 인프라 관리 측면, 순서 관리측면에서 알아서 안정화되는 것이다.
enable.idempotence == false 이 경우에는 sequence number를 이용하지않는다. 그러므로 먼저 응답온 메시지는 큐에서 먼저 빠진다. 순서 보장도 없고 중복 방지도 불가하다. 단순히 동시에 보내는 갯수만 조절할뿐이다.
- (참고) true일 때는 max.in.flight 값이 5 이하로 강제된다. 5이상이라면 OutOfOrderSequenceException 가 발생할 확률이 너무 높아진다.
batch를 압축해서 보낼지. 어떤 종류로 압축할지
“compression.type”
가능한 값으로는 none, gzip, snappy, lz4, zstd. 최근 버전에서는 zstd(Zstandard)를 권장한다고 한다. 압축 단위는 당연히 batch이다.
보통 snappy, lz4 알고리즘이 빠르게 압축해야할 때 이용된다. 마이크로서비스와 같이 응답이 빨라야할때 이용. gzip, zstd는 압축률이 커야할 때 이용한다. 로깅 처리와 같이 천천히 해도 될때 이용.