효율적인 Kafka 배치 전략을 위한 모범 사례
batch.size, linger.ms, fetch.min.bytes, max.poll.records를 사용하여 Kafka 프로듀서 및 컨슈머 배치를 조정합니다.
효율적인 Kafka 배치 전략을 위한 모범 사례
Kafka 배치는 클라이언트가 요청당 보내거나 가져오는 레코드 수를 제어합니다. 배치가 너무 작으면 CPU와 네트워크 왕복 시간을 낭비하고, 너무 크면 지연 시간이 추가되고 실패 시 재시도 비용이 더 많이 듭니다.
주요 조정 항목은 프로듀서의 batch.size와 linger.ms, 그리고 컨슈머의 fetch.min.bytes, fetch.max.wait.ms, max.poll.records입니다.
Kafka 배치와 오버헤드 이해하기
Kafka에서 데이터 전송은 TCP/IP를 통해 이루어집니다. 레코드를 하나씩 보내면 TCP 확인 응답, 각 요청의 네트워크 지연, 직렬화 및 요청 프레이밍에 따른 CPU 사용률 증가 등 상당한 오버헤드가 발생합니다. 배치는 로컬에서 레코드를 축적한 후 더 큰 연속 단위로 보내 이러한 문제를 완화합니다. 이를 통해 네트워크 활용도가 크게 향상되고 동일한 데이터 양을 처리하는 데 필요한 네트워크 왕복 횟수가 줄어듭니다.
프로듀서 배치: 전송 효율 극대화
프로듀서 배치는 성능 튜닝에서 가장 영향력 있는 영역입니다. 목표는 배치 크기가 네트워크 비용을 상쇄할 만큼 충분히 크지만, 종단 간 지연 시간을 허용할 수 없을 정도로 증가시키지 않는 최적점을 찾는 것입니다.
주요 프로듀서 구성 매개변수
프로듀서가 배치를 생성하고 전송하는 방식을 결정하는 몇 가지 중요한 설정이 있습니다.
batch.size: 보류 중인 레코드에 대한 프로듀서의 인메모리 버퍼 최대 크기를 바이트 단위로 정의합니다. 이 임계값에 도달하면 배치가 전송됩니다.- 모범 사례: 클라이언트 기본값에서 시작하여 64KB 또는 128KB와 같은 더 큰 값을 테스트합니다. 매우 큰 배치는 처리량에 도움이 될 수 있지만, 레코드, 파티션 및 지연 시간 목표가 이를 지원하는 경우에만 해당됩니다.
linger.ms: 새 레코드가 도착한 후 프로듀서가 버퍼를 채우기 위해 더 많은 레코드를 기다리는 시간(밀리초)을 지정합니다. 불완전한 배치를 보내기 전에 대기합니다.- 트레이드오프:
linger.ms가 높을수록 배치 크기가 커지지만(처리량 향상) 개별 메시지의 지연 시간도 증가합니다. - 모범 사례: 처리량 중심 워크로드의 경우 5-20ms와 같은 짧은 대기 시간을 테스트합니다. 지연 시간이 중요한 애플리케이션의 경우 이 값을 낮게 유지하고 더 작은 배치를 수용합니다.
- 트레이드오프:
buffer.memory: 단일 프로듀서 인스턴스에 대해 모든 토픽 및 파티션에서 전송되지 않은 레코드를 버퍼링하기 위해 할당된 총 메모리를 설정합니다. 버퍼가 가득 차면 이후의send()호출이 차단됩니다.- 모범 사례: 모든 활성 파티션의 최대 버스트를 처리할 수 있을 만큼 충분히 크게 유지합니다. 버퍼가 가득 차면
send()는max.block.ms까지 차단된 후 실패할 수 있습니다.
- 모범 사례: 모든 활성 파티션의 최대 버스트를 처리할 수 있을 만큼 충분히 크게 유지합니다. 버퍼가 가득 차면
프로듀서 배치 예제 구성 (Java)
Properties props = new Properties();
props.put("bootstrap.servers", "kafka-broker:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 성능 튜닝 매개변수
props.put("linger.ms", 10); // 최대 10ms 동안 더 많은 레코드 대기
props.put("batch.size", 65536); // 64KB 배치 크기 목표
props.put("buffer.memory", 33554432); // 32MB 전체 버퍼 공간
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
컨슈머 배치: 효율적인 풀링 및 처리
프로듀서 배치가 효율적인 전송에 초점을 맞추는 반면, 컨슈머 배치는 수신 및 처리 워크로드를 최적화합니다. 컨슈머는 파티션에서 배치 단위로 데이터를 가져오며, 이를 최적화하면 브로커에 대한 네트워크 호출 빈도가 줄어들고 애플리케이션 스레드에 필요한 컨텍스트 스위칭이 제한됩니다.
주요 컨슈머 구성 매개변수
fetch.min.bytes: 브로커가 단일 가져오기 요청에서 반환해야 하는 최소 데이터 양(바이트)입니다. 브로커는 이 데이터가 준비되거나fetch.max.wait.ms제한 시간에 도달할 때까지 응답을 지연시킵니다.- 이점: 프로듀서 배치와 유사하게 컨슈머가 더 큰 데이터 청크를 요청하도록 강제합니다.
- 모범 사례: 지연 시간보다 처리량이 더 중요할 때 값을 늘립니다. 조용한 기간 동안 브로커가 너무 오래 기다리지 않도록
fetch.max.wait.ms와 함께 사용합니다.
fetch.max.bytes: 컨슈머가 단일 가져오기 요청에서 수락할 최대 데이터 양(바이트)을 설정합니다. 이는 컨슈머의 내부 버퍼가 과부하되는 것을 방지하는 상한선 역할을 합니다.max.poll.records: 애플리케이션 처리량에 매우 중요합니다. 단일consumer.poll()호출에서 반환되는 최대 레코드 수를 제어합니다.- 맥락: 컨슈머 애플리케이션의 루프 내에서 레코드를 처리할 때 이 설정은 폴링 루프의 한 반복에서 처리되는 작업 범위를 제한합니다.
- 모범 사례: 파티션이 많고 볼륨이 높은 경우 이 값을 늘리면(예: 500에서 1000 이상) 컨슈머 스레드가
poll()을 다시 호출하기 전에 폴링 주기당 더 많은 데이터를 처리할 수 있어 폴링 오버헤드가 줄어듭니다.
컨슈머 폴링 루프 예제
레코드를 처리할 때 max.poll.records를 준수하여 폴링당 완료된 작업과 리밸런스에 신속하게 대응하는 능력 사이의 균형을 유지합니다.
while (running) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
// max.poll.records가 1000으로 설정된 경우 이 루프는 최대 1000번 실행됩니다.
for (ConsumerRecord<String, String> record : records) {
process(record);
}
// 배치 처리 후 오프셋 커밋
consumer.commitSync();
}
max.poll.records경고: 이 값을 너무 높게 설정하면 컨슈머 리밸런싱 중에 문제가 발생할 수 있습니다. 리밸런스가 발생하면 컨슈머는 그룹을 성공적으로 떠나기 전에 현재poll()에서 얻은 모든 레코드를 처리해야 합니다. 배치가 지나치게 크면 긴 세션 시간 초과와 불필요한 그룹 불안정이 발생할 수 있습니다.
고급 배치 고려 사항
배치 최적화는 특정 워크로드 특성(레코드 크기, 처리량 목표, 허용 가능한 지연 시간)에 따라 달라지는 반복적인 프로세스입니다.
1. 레코드 크기 변동
메시지 크기가 크게 다른 경우 고정된 batch.size는 불균일한 배치를 생성할 수 있습니다. 몇 개의 큰 레코드가 배치를 빠르게 채울 수 있는 반면, 작은 레코드는 효율적으로 그룹화하기 위해 linger.ms가 필요할 수 있습니다.
- 팁: 메시지가 지속적으로 큰 경우 낮은
linger.ms를 테스트하고 요청 지연 시간, 버퍼 가용성 및 브로커 요청 메트릭을 모니터링합니다.
2. 압축
배치와 압축은 함께 잘 작동합니다. 더 큰 배치를 압축하면 일반적으로 작은 요청을 압축하는 것보다 더 나은 압축률을 제공합니다. snappy, lz4 또는 zstd를 고려한 다음 클라이언트와 브로커의 CPU 비용을 측정합니다.
3. 멱등성 및 재시도
엄격히 배치는 아니지만 enable.idempotence=true를 보장하는 것이 중요합니다. 대규모 배치를 보낼 때 일시적인 네트워크 오류가 레코드의 하위 집합에 영향을 미칠 가능성이 높아집니다. 멱등성은 프로듀서가 일시적인 실패로 인해 배치 재전송을 시도할 때 Kafka가 메시지를 중복 제거하여 성공적인 전달 시 중복을 방지합니다.
배치 최적화 목표
| 구성 | 목표 | 처리량에 미치는 영향 | 지연 시간에 미치는 영향 |
|---|---|---|---|
프로듀서 batch.size |
요청당 데이터 최대화 | 높은 증가 | 중간 증가 |
프로듀서 linger.ms |
채워질 때까지 잠시 대기 | 높은 증가 | 중간 증가 |
컨슈머 fetch.min.bytes |
더 큰 청크 요청 | 중간 증가 | 중간 증가 |
컨슈머 max.poll.records |
폴링 오버헤드 감소 | 중간 증가 | 최소 변경 |
하나의 프로듀서 워크로드와 하나의 컨슈머 그룹으로 시작하여 한 번에 하나의 배치 설정을 변경하고 처리량, p95 지연 시간, 재시도 및 컨슈머 랙을 비교합니다. 효율적인 Kafka 배치는 측정 연습이지 설정하고 잊어버리는 구성 블록이 아닙니다.