Producer 동작 방식
kafkaTemplate 객체의 send() 메서드를 통해, Broker로 record(message)를 전송할 수 있습니다.
kafkaTemplate.send() 메서드에는 topic / key / value / partition 등 다양한 인자 값을 넘길 수 있으며,
send() 인자로 넘어간 값을 토대로 ProducerRecord 객체를 생성한 뒤 전송됩니다.
참고) org.springframework.kafka.core.KafkaTemplate
1. kafkaTemplate.send() 시 ProducerRecord 객체를 전송합니다.
2. Serializer가 ProducerRecord 객체를 Byte[] 로 직렬화합니다.
3. Partitioner가 전략에 따라 특정 Topic의 RecordBatch에 쌓습니다.
4. Sender 스레드가 배치 크기(batch.size)에 도달하거나, 일정 시간(linger.ms)이 지나면 (2)에서 직렬화된 Byte[]을 전송합니다.
- batch.size: 파티션별로 레코드를 한 번에 묶어 보내는 배치(RecordBatch) 1개의 최대 크기(바이트)
- linger.ms: 배치를 더 채우려고 전송 전에 최대 몇 ms까지 기다릴지(조금 더 기다리면 작은 배치 줄어듬)
- buffer.memory: 전송 전 레코드/배치를 전체로 얼마나 쌓아둘 수 있는 총 메모리 한도(꽉 차면 send()가 막히거나 실패)

RecordAccumulator와 ProducerBatch
Kafka Producer는 send()로 받은 레코드를 바로 Broker로 전송하지 않습니다. 먼저 Producer 내부의 RecordAccumulator에 레코드를 쌓아두고, 같은 topic-partition으로 전송될 레코드들을 배치로 묶습니다.
RecordAccumulator는 Producer의 전송 대기 버퍼입니다. 레코드가 들어오면 topic-partition별 배치 큐를 찾고, 기존 배치에 추가할 수 있으면 레코드를 넣습니다. 기존 배치가 없거나 가득 찼다면 새로운 배치를 만듭니다.
이때 실제 전송 단위가 ProducerBatch입니다. ProducerBatch는 특정 topic-partition 하나에 대한 record 묶음입니다. 하나의 배치에는 여러 record가 들어갈 수 있고, 유입량이 적으면 record 1개만 담긴 채 전송될 수도 있습니다.
배치는 주로 batch.size와 linger.ms에 의해 전송 시점이 결정됩니다. batch.size에 도달하면 바로 전송 대상이 되고, 가득 차지 않았더라도 linger.ms 시간이 지나면 Sender 스레드가 가져가 Broker로 전송할 수 있습니다.
전송이 완료되고 ack 처리가 끝나면 해당 배치는 제거되고, 사용했던 메모리는 다시 반환됩니다. 따라서 배치 개수는 고정되어 있지 않습니다. 레코드 유입 속도가 전송 속도보다 빠르면 대기 중인 배치가 늘어나고, 전송이 원활하면 빠르게 줄어듭니다.
정리하면, RecordAccumulator는 Producer 내부에서 topic-partition별 배치를 관리하는 버퍼이고, ProducerBatch는 특정 topic-partition으로 실제 전송되는 record 묶음입니다.
KafkaProducer
├─ RecordAccumulator
│ └─ topic-partition별 ProducerBatch 큐
│ ├─ topicA-0: [ProducerBatch A][ProducerBatch B]...
│ ├─ topicA-1: [ProducerBatch C]...
│ └─ topicB-0: [ProducerBatch D]...
│
└─ Sender Thread
└─ 전송 가능한 ProducerBatch를 꺼내 ProduceRequest로 전송
Producer 파티셔닝 전략
Kafka Producer가 record 전송 시 파티션을 선택하는 전략에 대해 알아봅니다. 디폴드 파티셔닝 전략의 3가지 케이스를 알아보면서 keyless 상황에서 왜 Sticky 파티셔닝 전략이 등장했는지, 그리고 언제 어떤 파티셔너를 선택해야하는지까지 실무적인 관점에서도 알아보겠습니다.
Producer에서 파티셔닝이 성능에 영향을 주는 이유
앞서 알아본 것처럼 Producer 동작 방식은 대략 다음과 같은 흐름입니다.
1. kafkaTemplate.send(producerRecord) 호출
2. Partitioner가 topic-partition 결정
3. 같은 topic-partition으로 가는 record끼리 RecordAccumulator에 모여서 RecordBatch로 묶음
4. Sender 스레드가 RecordBatch를 broker로 전송
여기서 중요한 포인트는 RecordBatch는 파티션 단위로 커집니다. 즉, record가 여러 Partition으로 흩어지면 RecordBatch가 잘 안커져서 전송해야 하는 RecordBatch 개수가 늘어납니다. 그 결과 Producer 요청 처리 부담이 늘어나기 때문에 체감상 요청이 잦아지게 됩니다. 그래서 키가 없을 때 어떻게 파티션을 고르느냐는 단순 균등분산을 넘어, 네트워크 요청 수, 지연, 브로커 CPU까지 영향을 줍니다. (이 배경으로 sticky 파티셔닝이 기본이 된 이유 중 하나입니다.)
디폴트 파티셔닝 전략
Kafka 클라이언트는 partitioner.class를 지정하지 않으면, 디폴트 파티셔닝 전략(org.apache.kafka.clients.producer.internals.DefaultPartitioner)을 사용합니다. 디폴트 전략은 레코드 전송 시 partition과 key 값의 유무에 따라 다음 3가지 케이스로 동작합니다.
Kafka 4.0부터는 DefaultPartitioner 클래스 자체가 제거되어 더 이상 partitioner.class 로 지정할 수 없습니다. 다만 “기본 파티셔닝 동작”은 프로듀서의 기본 동작으로 계속 존재하며, 최신 버전에서는 설정과 내부 구현 구조가 일부 변경되었습니다.
Case1) partition 지정
- partition 을 지정하면, record는 해당 파티션으로 고정됩니다.
- record를 특정 파티션으로 강제할 수 있어 '고정 라우팅'이 필요할 때 사용합니다.
- 파티션 지정을 잘못 쓰면 특정 파티션(또는 그 파티션을 리드하는 브로커/컨슈머)에 트래픽·부하가 과도하게 몰리는 상태)이 생기기 쉽고 특정 키/그룹이 한 파티션에 몰리면 컨슈머 병렬성이 사실상 죽습니다. 따라서, 실무에서는 특정 키를 partition으로 설정하기 보단, 보통 key 기반 해시를 사용합니다.
- partition 과 key 를 모두 지정하면, partition 지정이 우선됩니다.
- 기본 partitioning 로직은 partition이 없고 key가 있으면 key hash 기반으로 선택, partition도 key도 없으면 sticky partition 사용이라고 설명합니다.
Case2) partition 미지정 + key 지정
- partition이 없고 key가 있으면, key hash 기반으로 파티션이 결정됩니다.
- (파티션 수가 동일한 경우) 같은 key는 같은 파티션으로 가는 특성을 갖습니다.
- 언제 사용할까?
- 같은 주문, 사용자, 정산키, 집계키 단위 등으로 순서를 지켜야 한다.
- 동일 키끼리 같은 파티션에서 처리되게 해서 상태, 캐시, 집계를 단순화하고 싶다.
- 파티션 수 변경시 주의 ⚠️
- key 기반 해시를 사용한 partition 매핑은 보통 파티션 수에 의존합니다.
- 파티션 수가 바뀌면 같은 key가 다른 파티션으로 이동할 수 있고, 그 결과 '키 단위 정렬/집계' 가 깨질 수 있습니다.
Case3) partition 미지정 + key 미지정(keyless)
- partition과 key 모두 없으면, Sticky Partitioner가 적용됩니다.
- sticky는 한 파티션을 고정(stick)해서 레코드를 쌓다가, 그 파티션의 현재 배치가 전송되면(= batch.size로 꽉 차거나 linger.ms로 타임아웃되거나 flush/close 등으로 완료되면) 다음 파티션을 선택합니다
- key가 없는 메시지를 보낼 때 매번 Round-Robin 방식으로 파티션을 바꾸면, 얼핏 보기에는 파티션 간 분산이 공평해 보입니다. 하지만 Producer 관점에서는 배치 효율이 떨어질 수 있습니다.
Kafka Producer는 메시지를 즉시 브로커로 보내는 것이 아니라, RecordAccumulator에 topic-partition 단위로 레코드를 모아 배치로 전송합니다. 그런데 keyless 메시지를 매번 다른 파티션으로 보내면 각 파티션에 쌓이는 레코드 수가 적어지고, 배치가 충분히 커지기 전에 작은 배치들이 자주 전송될 수 있습니다.
이 경우 브로커로 보내는 요청 수가 증가하고, 네트워크 I/O와 요청 처리 비용도 함께 증가합니다. Sticky Partitioner는 이 문제를 줄이기 위해 도입된 방식입니다. key가 없는 메시지에 대해 일정 시간 동안 하나의 파티션에 “붙어서” 레코드를 모으고, 해당 배치가 가득 차거나 전송 가능한 상태가 되면 다른 파티션으로 이동합니다.
즉, Sticky Partitioner의 목적은 keyless 메시지를 매 record마다 균등하게 흩뿌리는 것이 아니라, 일정 단위로 파티션에 모아 더 큰 배치를 만들고 Producer 처리량을 높이는 것입니다. 단기적으로는 특정 파티션에 메시지가 몰려 보일 수 있지만, 시간이 지나면 여러 파티션으로 분산되도록 동작합니다.
디폴트 파티셔닝 전략 외
Round-Robin
org.apache.kafka.clients.producer.RoundRobinPartitioner
- RoundRobinPartitioner는 record에 partition이 명시되지 않은 경우, key 유무와 관계없이 partition을 순서대로 돌려가며 선택하는 파티셔너입니다. 따라서 같은 key를 가진 record라도 같은 partition으로 간다는 보장은 없고, 파티션 간 분산을 직관적으로 균등하게 만드는 데 초점이 있습니다.
- Producer는 topic-partition 단위로 record를 모아 batch를 만듭니다. Round-Robin 방식처럼 record마다 partition이 계속 바뀌면 각 partition에 쌓이는 record 수가 적어져 작은 batch가 자주 만들어질 수 있습니다. 이 경우 브로커로 보내는 요청 수가 증가하고, 네트워크 I/O와 요청 처리 비용이 커져 처리량이나 지연 시간 측면에서 불리할 수 있습니다.
UniformStickPartitioner (Deprecated)
org.apache.kafka.clients.producer.UniformStickyPartitioner
- UniformStickyPartitioner는 record에 partition이 명시되지 않은 경우, key 유무와 상관없이 sticky 방식으로 partition을 선택하는 파티셔너입니다. 즉, key가 있어도 key hash 기반 파티셔닝을 하지 않기 때문에 같은 key를 가진 record가 항상 같은 partition으로 간다는 보장은 없습니다.
- 다만 Kafka clients 3.3.0부터 UniformStickyPartitioner는 deprecated 되었고, Kafka 4.0에서는 기존 DefaultPartitioner와 함께 제거되었습니다.
- 따라서 현재는 partitioner.class=UniformStickyPartitioner를 직접 설정하기보다, partitioner.class를 설정하지 않고 Kafka Producer의 기본 파티셔닝 로직을 사용하되 partitioner.ignore.keys=true를 설정하는 방식이 권장됩니다.
Custom Paritioner
- partitioner.class로 org.apache.kafka.clients.producer.Partitioner 구현체를 지정합니다.
- 예: 센터/테넌트/샤딩키 기반 라우팅, 핫키 완화(키+salt), 특정 파티션 우선 사용 등
실무에서 선택하는 파티셔닝 전략
Kafka Producer 파티셔닝 전략은 record를 분산시키는 것으로 끝나지 않습니다. 실무에서는 내 record에서 가장 중요한 것은 무엇인가에 따라서 방향이 정리됩니다.
- 순서/키 단위 처리인가
- 처리량/지연 최적화인가
- 균등 분산인가
- 도메인 제약(샤딩/테넌트/핫키)인가
순서/키 단위 처리가 중요하면 → Key hash (= 디폴트 케이스 2)
주문ID, 유저ID, 정산키처럼 의미 있는 key를 넣고, 디폴트의 key-hash 파티셔닝을 활용하는 것이 일반적 입니다.
같은 key는 같은 파티션으로 라우팅되는 특성이 있어 결과적으로 같은 컨슈머 인스턴스에서 순서대로 처리하기 쉬워집니다.
집계/상태 기반 처리(예: 사용자별 상태 머신, 주문별 워크플로우)에도 구조가 단순해집니다.
“순서”가 중요하면 파티션 수를 늘리는 것보다 키 설계를 먼저 보는 편이 안전합니다.
키가 없다 + 처리량/지연 최적화가 목적이면 → Sticky Partitioner (= 디폴트 케이스 3)
로그/트래킹/모니터링 같은 keyless 이벤트는 보통 완벽한 균등 분산보다 전송 효율이 더 중요합니다. 이때 디폴트의 sticky 파티셔닝이 강점을 발휘합니다. 한 파티션에 붙어서 레코드를 모아 배치(RecordBatch)를 크게 만들기 쉬워지고 작은 배치가 자주 생기는 현상을 줄여 전송 오버헤드를 낮추는 방향으로 동작합니다. 결과적으로 같은 트래픽에서도 처리량(throughput)과 지연(latency) 측면에서 유리한 경우가 많습니다.
keyless라면 “골고루”보다 “잘 묶어서 보내기”가 성능에 더 큰 영향을 주는 경우가 많습니다.
균등 분산이 절대적이고 배치 효율 손해를 감수할 수 있으면 → Round-Robin Partitioner
특정 상황에서는 '배치 효율'보다 파티션 간 '부하 균등'이 최우선이 될 때가 있습니다. 작업량이 파티션 간 정말 고르게 분산되어야 하거나 핫스팟을 예방하는 것이 최우선이고 그 대가로 요청 증가 / 지연 상승(배치가 작아질 가능성)을 감당할 수 있다면 Round-robin 파티셔닝을 고려할 수 있습니다.
균등 분산은 보기엔 좋아 보이지만, keyless 환경에서는 배치가 작아질 수 있어 성능 비용이 생길 수 있습니다. 균등이 목적일 때만 선택하는 편이 좋습니다.
핫키/테넌트/샤딩 등 도메인 제약이 있으면 → Custom Partitioner
실무에서 가장 까다로운 케이스는 “키는 있는데, 그 키가 너무 커서 한 파티션이 터지는” 핫키(hot key) 상황입니다. 또는 멀티테넌트/샤딩 정책처럼 도메인 제약이 명확한 경우도 있습니다. 이때는 partitioner.class로 커스텀 파티셔너를 붙이거나, 데이터 모델 자체를 조정하는 전략을 검토합니다. 대표적인 접근은 아래와 같습니다.
- 키 쪼개기(splitting): key + salt / time-bucket key 등으로 핫키를 분산
- 헤더 기반 라우팅: 테넌트/센터/지역 등을 헤더로 넣고 라우팅 규칙 적용
- 특정 키만 별도 정책: 특정 키만 별도 토픽/별도 파티션 정책(격리)으로 운영 안정성 확보
도메인 제약이 강하면 기본 파티셔닝을 잘 쓰는 것보다, 키/토픽/파티션 전략을 함께 설계하는 것을 고려해보는 것이 좋습니다.
결론
Producer 파티셔닝은 단순히 메시지를 균등하게 분산하는 문제가 아닙니다. 실무에서는 순서 보장, 컨슈머 병렬 처리, 배치 효율을 함께 고려해야 합니다.
key가 있으면 같은 key를 같은 partition으로 보내 순서를 보장하기 쉽고, key가 없으면 sticky partitioning을 통해 batch 효율을 높일 수 있습니다. 반대로 partition을 직접 지정하면 key나 sticky 전략과 무관하게 해당 partition으로 강제 라우팅됩니다.
따라서 파티셔닝 전략을 선택할 때는 균등 분산만 볼 것이 아니라, 우리 시스템이 무엇을 우선하는지 먼저 정리해야 합니다. 순서 보장이 중요한지, 처리량과 지연 시간이 중요한지, 컨슈머 병렬성이 중요한지, 특정 partition으로 보내야 하는 도메인 제약이 있는지에 따라 적절한 전략이 달라집니다.
1. partition 지정
→ 지정된 partition으로 강제 라우팅
2. partition 미지정 + key 지정
→ key hash 기반 라우팅
→ 같은 key를 같은 partition으로 보내기 쉬움
→ key 단위 순서 보장에 유리
3. partition 미지정 + key 지정
→ sticky partitioning
→ 일정 시간 한 partition에 모아 batch 효율 개선
→ 처리량 / 요청 수 / 지연 시간 측면에서 유리'Server > Kafka' 카테고리의 다른 글
| [Kafka] Producer 메시지 전송 보장 수준 (최대/최소/정확히 한 번) (0) | 2026.06.17 |
|---|---|
| [Kafka] Producer acks, Consumer AckMode 그리고 Listener (0) | 2026.05.24 |
| [Kafka] Producer 메시지 전송 방식 (0) | 2026.05.03 |
| [Kafka] Consumer 동작 방식과 리밸런싱 (0) | 2026.03.31 |
| [Kafka] 핵심 개념 빠르게 알아보기 (0) | 2026.01.11 |