Kafka/Producer

Kafka Producer 성능 테스트, 튜닝

재심 2023. 4. 22. 22:58

목차

    [개요]

    • 카프카에서는 프로듀서 튜닝이 가장 핵심적인 요소이다.
    • Throughput, latency는 프로듀서가 얼마나 효율적인 작업을 하는가에 따라 달라진다. 
    • Biz 요구사항을 가장 먼저 파악하고 그에 맞는 성능튜닝을 진행한다. 
      • Durability, Availability, Throughput, Latency 의 우선순위를 정해본다. 
    • 작은 변화를 위해 설정값을 변경하는 건 권고하지 않는다. 큰 변화일 때만 설정값을 변경한다. : https://www.confluent.co.uk/blog/configure-kafka-to-minimize-latency/
     

    Tail Latency at Scale with Apache Kafka | UK

    Apache Kafka allows you to achieve both high throughput and low latency. Learn to configure and scale Kafka clients to minimize latency.

    www.confluent.io

     

    [Producer 내부 동작과정]

    Producer의 튜닝을 하려면 내부동작도 잘 알아야 한다.

     

    Producer 내부 동작. 출처: Confluent
    Producer 내부 동작. 출처: Confluent

    • Serializer : byte array 로 변환.
    • Partitioner : 변환한 byte를 토픽의 어느 파티션으로 보낼 것인가를 결정.
    • Record Accumulator : 메시지가 쌓이는 배치. 내부적인 Heap Memory를 사용한다.
    • Compression : 압축, 브로커로 더 빠르게 전송 가능하나 압축하는 만큼의 cpu 사용률이 늘어날 수 있다.
    • Sender Thread : sender request를 생성해 보냄.

     

    [Producer Batch]

    Producer에서 가장 중요한 것은 batch 처리이다. → 프로듀서 성능 개선의 중요한 포인트이다..!

    배치 처리의 이점에 대한 Confluent 블로그.  ( https://www.confluent.co.uk/blog/configure-kafka-to-minimize-latency/ )

    • 배치 처리를 통해 네트워크 대역폭 사용을 감소시킬 수 있다.
      • producer 에서 broker
      • broker 에서 broker (replication)
      • broker 에서 consumer
    • 추가적인 이점
      • 브로커 디스크의 스토리지 요구 사항 감소.
      • 리퀘스트 감소로 인한 CPU 요구 사항 감소.

    [성능 테스트 도구. Producer Perf Test]

    • kafka에서는 Producer, Consumer를 위한 성능 테스트 도구를 제공한다.
    • Producer용은 producer-perf-test이다.
    • Apache Kafka 3.0미만까지는 RandomString이 적용되지 않아 압축을 테스트하는것이 적절하지 않았는데, 그 이후버전부터는 RandomString으로 메시지 내용이 생성되어 압축 테스트도 가능하다. (매번 같은 문자열일 경우 압축률이 일정하여 실사용과 차이가 크다)

    다운로드

    경로: https://kafka.apache.org/downloads 

     

    Apache Kafka

    Apache Kafka: A Distributed Streaming Platform.

    kafka.apache.org

     

    다운로드 후 압축을 풀고 bin 디렉토리에 스크립트 파일이 있다.

     

    커맨드 예시

    sudo ./kafka-producer-perf-test.sh \
        --topic test-topic \
        --num-records 1000 \
        --record-size 100 \
        --throughput 1000 \
        --print-metrics \
        --producer-props acks=-1 \
        bootstrap.servers=localhost:9092 \
        buffer.memory=67108864 batch.size=8196
    • topic: 토픽명
    • num-records: 전송할 메시지 수
    • record-size: 메시지 크기
    • throughput: TPS조절. -1로 할 경우 머신에서 낼 수 있는 최대 속도를 낸다. 위의 예시는 num-records가 1,000이고 throughput도 1,000이므로 1초만에 끝난다. TPS 1,000으로 1분간 지속하고 싶을 경우 throughput은 1,000으로 설정하고, num-records를 60,000으로 설정하면 된다.
    • print-metrics: 상세한 메트릭 결과를 마지막에 노출
    • producer-props: producer의 설정값들. 배치크기, acks 등등 조절하고 싶은 것들을 조절한다.

     

    [성능 테스트 방법론]

    접근 방법 ( Consumer 도 유사한 수준으로 진행 )

    • kafka-producer-perf-test 를 사용한다.
    • 기본값만으로 테스트를 먼저 한다.
    • 메트릭 관찰
    • 실행할 때마다 병목이 일어나는 포인트를 찾는다.
    • 설정을 조정하고 다시 테스트하여 영향 측정한다.
    • 반복..

    즉, 기본값으로 테스트를 먼저 돌리고 거기에서 발생하는 병목 포인트를 찾아 점진적으로 개선해 나가는 방식으로 테스트하는 것이 좋다고 한다.

     

    [성능 테스트 결과 해석하기]

    주요 메트릭들

    Metric Meaning MBean
    record-size-avg 평균 레코드 크기 kafka.producer:type=producer-metrics,client-id=([-.w]+)
    batch-size-avg 평균 배치 크기 kafka.producer:type=producer-metrics,client-id=([-.w]+)
    bufferpool-wait-ratio Appender가 공간할당을 기다리는 시간의 비율 kafka.producer:type=producer-metrics,client-id=([-.\w]+)
    compression-rate-avg 압축율 (1이면 압축안됐다는 뜻. 낮을수록 압축률이 높음) kafka.producer:type=producer-metrics,client-id=([-.w]+),topic=([-.w]+)
    record-queue-time-avg 배치가 send 버퍼에서 머무른 시간 평균 (ms) kafka.producer:type=producer-metrics,client-id=([-.w]+)
    request-latency-avg 평균 request 지연 시간 (ms) kafka.producer:type=producer-metrics,client-id=([-.w]+)
    produce-throttle-time-avg 브로커에 의해 요청이 스로틀링 된 평균 시간 (ms) kafka.producer:type=producer-metrics,client-id=([-.w]+)
    record-retry-rate 토픽에 대해 전송을 다시 시도한 초당 평균 레코드 전송 횟수 kafka.producer:type=producer-metrics,client-id=([-.w]+),topic=([-.w]+)
    • bufferpool-wail-ratio : buffer에 넣는데 걸리는 시간
    • record-queue-time-avg : 실제로 레코들르 전송하는데 걸리는 시간 
      • 결과가 좋다면 데이터가 들어오자마자 브로커로 최대한 빨리 전송을 한다 
      • 해당 값을 개선시켜도 Linger.ms 를 준 경우 레코드가 계속 대기할 수 있다. = 처리시간이 늦어질 수 있다
    • request-latency-avg : 브로커에서 ack를 받는데 걸리는 시간
      • TPS를 늘렸을 때 지연시간 (request-latency-avg)이 늘어난다 → 클러스터가 문제, broker를 튜닝한다
      • TPS를 늘렸을 때 지연시간은 많이 늘어나지 않는다 →  프로듀서 클라이언트가 문제 
    • request-size-avg : 브로커에 보낸 요청 크기
      • batch-size-avg 와 유사하다면 batch 마다 reqeust 를 보낸다
      • sender가 batch 를 쌓아서 보내지 않고 즉시 전송한다. (이미 Request가 다 찼으니깐 )
    • linger.ms: batch 를 바로 보내지 않고 다른 Record들을 기다린다. 
      • 해당값을 늘려줘도 batch size가 다 찬다면 linger 값만큼 기다리지 않고 바로 전송한다.
    • batch.size : request 로 보낼 record들을 얼마나 찼을때 보낼지 설정한다.
      • batch-size-avg가 설정한 batch 보다 작다면 Linger 를 늘린다.
    • max.in.flight.requests.per.connection 통해 request 한 번에 전송할 배치 수 결정. (기본값 5)

    [예제 시나리오]

    실제 예시는 아니고, 아래처럼 튜닝과정을 거친다고 가정한 시나리오이다.

     

    먼저 100만 레코드를 보내도록 세팅하고, 기본 값으로 producer-perf-test를 수행한다.

    1차 테스트. (기본값)

    결과가 아래 처럼 나왔다고 가정하자.

    bufferpool-wait-ratio = 0.666 (버퍼에 넣는데 60%의 시간을 썼다)
    batch-size-avg = 16000 (배치를 항상 거의 꽉 채웠다)
    compression-rate-avg = 1 (압축X)
    record-queue-time-avg = 2391 (send버퍼에서 머무른 시간. 2391이라는건 2초이므로 여기서 지연이 많다)
    request-size-avg = 21534 (배치가 쌓이는 대로 나갔다 정도로 이해)

    => 평균 지연시간이 높은편이다. 이 문제를 어떻게 해결할 수 있을까?

     

    메트릭을 확인해보면 send 버퍼에서 머무른 시간이 주요 원인이다.Producer 큐에 넣는 것이 큐에서 나가는 것보다 많다는 뜻이며 이 말은 배치처리를 좀 더 개선할 필요가 있다는 뜻일 수도 있다.

     

    배치 처리를 개선할 수 있는 요소들

    • linger.ms = 배치를 채울 때 기다리는 시간 (기본값은 0이며 0일 경우 메시지가 올 때마다 가능한한 배치를 바로 전송해버린다)
    • batch.size = 배치 크기 (기본값은 16384이며 꽉 차면 배치를 전송한다)

    두 값에 의해 배치가 전송되며, linger.ms가 100이고 batch.size가 16384이면 배치가 꽉 차지 않더라도 100ms가 지난 후 배치를 전송하며 100ms안에 16384를 채우면 배치가 전송되기도 한다는 뜻이다.

     

    만약 linger.ms를 기본값인 0으로 쓴다면 메시지가 올 때 마다 전송하게되어 배치를 효율적으로 활용하지 못하는 것이다.

     

    이 시나리오에서는 이미 배치를 꽉 채우면서 보내고 있기 때문에 linger.ms를 늘려도 큰의미가 없다. 배치 크기를 늘려본다.

    => 16K -> 300K

     

    2차 테스트. (배치 크기 증가 16K -> 300K)

    결과가 아래처럼 나온다고 가정한다.

    bufferpool-wait-ratio = 0.12
    batch-size-avg = 250200 (300K를 채우지는 못했다)
    ...생략

    기존 66%에서 12%로 줄었다. 하지만 배치를 꽉 채우지는 못했다.

    linger.ms와 batch.size를 조합해서 테스트해볼 수 있다.

     

    3차 테스트. (linger.ms = 100, batch.size = 300K)

    결과가 아래처럼 나온다고 가정한다.

    bufferpool-wait-ratio = 0.04
    ...
    record-queue-time-avg = 1000 (많이 줄었다)

    버퍼 관련값과 지연시간등이 많이 개선되었다.

    하지만 아직 TPS를 -1로 주고있기 때문에 실사용과 맞지 않다.

    실사용TPS값을 대략적으로 산정하여 테스트한다. 또한 압축도 해본다.

     

    4차 테스트. (linger.ms =1000, batch.size=300K,TPS=1000, compression.type=lz4)

    buffer-wait-ratio = 0.01 (더 이상 대기하지 않는다)
    compression-rate-avg = 0.029 (2.90%면 매우 높은 압축률이다)

    여기까지 하니 대부분의 수치가 개선되었고, producer에서는 더 이상 튜닝이 없는 것이라 판단한다.

     

    [Client 성능지표]

    클라이언트 성능지표를 통해 어느 포인트에서 병목이 발생하는지 짐작할 수 있다.

    • io-wait-ratio: 이 값이 높다면 브로커에 문제가 있다고 판단할 수 있다. 즉, 브로커 튜닝 필요.
    • io-ratio: io가 많이 발생한다.
      • Producer: 메시지를 보내는 시간이 길다 -> producer의 batch size를 늘리거나 압축을 사용.
      • Consumer: max.partition.fetch.size를 늘리거나 consumer group의 consumer를 추가 투입한다.
    • 클라이언트 처리시간: 실제 브로커에서는 제대로 처리하고 있어서 클라이언트의 수정이 필요하다.
      • Producer/Consumer의 로직 처리 시간이 너무 길지 않은지 확인한다.