안녕세계
[Kafka] Producer 동작 방식과 파티셔닝 전략 본문
[Kafka] Producer 동작 방식과 파티셔닝 전략
Junhong Kim 2026. 2. 2. 16:32Producer 동작 방식
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란?
RecordAccumulator는 Producer가 send()로 받은 레코드를 바로 네트워크로 보내지 않고, topic-partition별로 레코드를 모으고 batch.size에 맞춰 배치를 만들고(상자를 만들고) Sender 스레드가 가져가서 전송할 수 있게 대기열을 관리합니다.
- 파티션별로 현재 열려있는 배치가 있는지 확인
- 배치가 꽉 찼는지(또는 닫혀야 하는지) 판단
- buffer.memory 한도 내에서 메모리(버퍼 풀) 할당/회수
- 전송 가능한 배치(ready) vs 아직 더 모을 배치 분리
- 메타데이터(리더 브로커, 파티션 정보) 바뀌면 라우팅 재평가
RecordBatch란?
말 그대로 특정 topic-partition 하나에 대해서만 여러 record를 담아서 한 번에 보내기 위해 만든 “묶음 객체”입니다.
- 한 배치에는 여러 record가 들어갈 수 있고, 상황에 따라 1개만 들어갈 수도 있음.
- 배치는 보통 batch.size (바이트) 기준으로 커지다가 닫힘
- Sender가 이 배치를 꺼내서 ProduceRequest에 담아 전송
RecordBatch는 미리 정해진 개수(N개)를 만들어두고 돌려 쓰는 게 아니라, 레코드가 send()로 들어오는 순간, 해당 Topic-Partition의 버퍼(= RecordAccumulator)에 넣을 배치가 없거나, 현재 배치가 꽉 찼으면 그때 새 RecordBatch를 만들어서 넣습니다. 그리고 Sender가 전송해서 ack까지 끝나면 그 배치는 메모리에서 제거됩니다. 즉 유입 속도(레코드가 들어오는 속도)와 전송 속도(빠져나가는 속도)의 차이에 따라 RecordBatch가 늘었다가 줄었다가 합니다.
--
KafkaProducer
└─ RecordAccumulator (전체 버퍼/큐 관리자)
┠─ partition 0: [RecordBatch A][RecordBatch B]...
┣─ partition 1: [RecordBatch C]...
└─ partition 2: [RecordBatch D][RecordBatch E]...
*RecordAccumulator: 파티션별로 배치들을 관리
*RecordBatch: 파티션 하나에 대한 실제 전송 단위

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 기반 해시를 사용합니다.
case2) partition 미지정 + key가 있는 경우
- partition이 없고 key가 있으면, key hash 기반으로 파티션이 결정됩니다.
- (파티션 수가 동일한 경우) 같은 key는 같은 파티션으로 가는 특성을 갖습니다.
- 언제 사용할까?
- 같은 주문, 사용자, 정산키, 집계키 단위 등으로로 순서를 지켜야 한다
- 동일 키끼리 같은 파티션에서 처리되게 해서 상태, 캐시, 집계를 단순화하고 싶다
- 파티션 수 변경시 주의 ⚠️
- key 기반 해시를 사용한 partition 매핑은 보통 파티션 수에 의존합니다.
- 파티션 수가 바뀌면 같은 key가 다른 파티션으로 이동할 수 있고, 그 결과 '키 단위 정렬/집계' 가 깨질 수 있습니다.
case3) partition 미지정 + key가 없는 경우(keyless)
- partition도 없고 key도 없으면 sticky 파티셔닝이 적용됩니다.
- sticky는 한 파티션을 고정(stick)해서 레코드를 쌓다가, 그 파티션의 현재 배치가 전송되면(= batch.size로 꽉 차거나 linger.ms로 타임아웃되거나 flush/close 등으로 완료되면) 다음 파티션을 선택합니다
- keyless 상황에서 파티션을 round-robin 방식으로 분산하는 것이 공평해 보이지만, 이 방식은 배치가 커지기 어렵습니다. record가 저장되는 파티션을 매 전송마다 바꾸면, 한 파티션에 쌓이는 레코드가 적어서 작은 배치가 자주 생기고 결과적으로 브로커로 보내는 요청 수가 증가합니다. sticky는 이러한 문제를 줄이기 위해 도입되었습니다.
디폴트 파티셔닝 전략 외
Round-Robin
org.apache.kafka.clients.producer.RoundRobinPartitioner
- key 유무와 상관없이 파티션을 순서대로 돌려가며 선택(균등 분산이 직관적으로 잘 됨)하여 분산을 최우선으로 합니다.
- 배치 효율을 희생할 가능성이 있어(파티션이 계속 바뀌면 배치가 작아짐) 처리량/지연에 불리할 수 있습니다.
UniformStickPartitioner
org.apache.kafka.clients.producer.UniformStickyPartitioner
- key 유무와 상관없이 sticky로만 파티션을 고르는 파티셔너입니다.
- Kafka clients 3.3.0부터 UniformStickyPartitioner는 Deprecated 입니다. 따라서 이 전략을 쓰지 않고, 기본 파티셔닝 로직을 쓰되 partitioner.ignore.keys=true로 동일 효과를 내라는 가이드가 있습니다.
- 그리고 Kafka 4.0에서 UniformStickyPartitioner(및 기존 DefaultPartitioner) 클래스가 제거되었습니다.
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 (디폴트 케이스 3)
로그/트래킹/모니터링 같은 keyless 이벤트는 보통 완벽한 균등 분산보다 전송 효율이 더 중요합니다. 이때 디폴트의 sticky 파티셔닝이 강점을 발휘합니다. 한 파티션에 붙어서 레코드를 모아 배치(RecordBatch)를 크게 만들기 쉬워지고 작은 배치가 자주 생기는 현상을 줄여 전송 오버헤드를 낮추는 방향으로 동작합니다. 결과적으로 같은 트래픽에서도 처리량(throughput)과 지연(latency) 측면에서 유리한 경우가 많습니다.
keyless라면 “골고루”보다 “잘 묶어서 보내기”가 성능에 더 큰 영향을 주는 경우가 많습니다.
균등 분산이 절대적이고 배치 효율 손해를 감수할 수 있으면 → Round-robin
특정 상황에서는 '배치 효율'보다 파티션 간 '부하 균등'이 최우선이 될 때가 있습니다. 작업량이 파티션 간 정말 고르게 분산되어야 하거나 핫스팟을 예방하는 것이 최우선이고 그 대가로 요청 증가 / 지연 상승(배치가 작아질 가능성)을 감당할 수 있다면 Round-robin 파티셔닝을 고려할 수 있습니다.
균등 분산은 보기엔 좋아 보이지만, keyless 환경에서는 배치가 작아질 수 있어 성능 비용이 생길 수 있습니다. 균등이 목적일 때만 선택하는 편이 좋습니다.
핫키/테넌트/샤딩 등 도메인 제약이 있으면 → Custom
실무에서 가장 까다로운 케이스는 “키는 있는데, 그 키가 너무 커서 한 파티션이 터지는” 핫키(hot key) 상황입니다. 또는 멀티테넌트/샤딩 정책처럼 도메인 제약이 명확한 경우도 있습니다. 이때는 partitioner.class로 커스텀 파티셔너를 붙이거나, 데이터 모델 자체를 조정하는 전략을 검토합니다. 대표적인 접근은 아래와 같습니다.
- 키 쪼개기(splitting): key + salt / time-bucket key 등으로 핫키를 분산
- 헤더 기반 라우팅: 테넌트/센터/지역 등을 헤더로 넣고 라우팅 규칙 적용
- 특정 키만 별도 정책: 특정 키만 별도 토픽/별도 파티션 정책(격리)으로 운영 안정성 확보
도메인 제약이 강하면 기본 파티셔닝을 잘 쓰는 것보다, 키/토픽/파티션 전략을 함께 설계하는 것을 고려해보는 것이 좋습니다.
마무리
Producer 파티셔닝은 단순히 분산이라는 1차원 문제가 아닙니다. 실무에서는 순서 보장(키 단위), 컨슈머 병렬 처리(파티션 분배), 배치 효율(요청 수 / 지연 / 처리량)에 대해 고려가 필요합니다. 그래서 디폴트 파티셔닝 로직이 3가지 케이스로 나뉘는 이유도 명확합니다.
- key가 있으면 의미 기반 라우팅(해시)
- key가 없으면 배치 효율(스티키)
- partition을 주면 강제 라우팅
즉, 파티셔닝 전략을 고를 때는 균등 분산만을 보지 말고, 우리 시스템이 무엇을 최우선 가치로 두는지(순서 vs 성능 vs 균등 vs 도메인 제약)를 먼저 정리하고 파티셔닝 전략을 결정하는 것이 좋습니다.
'Server > Kafka' 카테고리의 다른 글
| [Kafka] 핵심 개념 빠르게 알아보기 (0) | 2026.01.11 |
|---|