
Kafka에서 키(key) 는 단순한 “식별자”가 아니라, 파티션 라우팅(부하 분산) 과 파티션 내 순서 보장을 동시에 결정하는 설계 레버입니다. 잘 잡으면 확장성이 좋아지고, 잘못 잡으면 핫 파티션/지연/리밸런스 폭탄/확장 불가로 이어집니다. Source
키가 있으면(serialize된 keyBytes가 있으면) 기본 파티셔너는 murmur2 해시 → 파티션 수로 mod 해서 파티션을 정합니다. 키가 동일하면 같은 파티션으로 가므로, 그 파티션 안에서는 순서가 유지됩니다(토픽 전체 순서 보장은 아님). Source
Kafka가 보장하는 ordering은 파티션 단위입니다. 그래서 “같은 키로 같은 파티션에 모으는 것”이 곧 “그 키 범위의 순서를 보장하는 트릭”입니다. Source
기본 파티셔너는 키가 존재할 때 32-bit murmur2 해시 + mod 로 파티션을 선택합니다. Source
이 말은 곧:
키가 없을 때는 파티션을 임의 선택/분산하는 모드가 동작합니다. 특히 Kafka 2.4+에서는 “sticky” 방식으로 배치 단위 효율을 올리는 최적화가 언급됩니다. Source
아래 트리는 “키를 무엇으로 할지”를 5분 안에 결정하기 위한 실전용입니다.
orderId, accountId …)queryId, tenantId, sessionId)orderId#bucket, tenantId:userId#bucketorderId, userId, accountIdKafka가 같은 키를 같은 파티션으로 보내 순서를 보장하는 메커니즘은 이 전략의 기반입니다. Source
tenantId:userId, storeId:orderIdorderId#0..N, userId#bucket“랜덤 파티셔닝은 부하를 가장 고르게 퍼뜨려 스케일이 쉽다”는 설명이 대표적입니다. Source
겉보기엔 분산이 잘 되지만, 사실상 키 기반 의미를 다 잃고 순서/집계/상태 처리에서 아무 이점이 없습니다. 차라리 키를 비우고(null key) 의도를 명확히 하는 편이 낫습니다.
키 공간이 작으면 특정 파티션에 트래픽이 몰립니다(핫 파티션). 특히 “KR/US/JP” 같은 값은 위험합니다.
대형 테넌트가 전체 트래픽을 먹으면 그 테넌트 파티션이 병목이 됩니다. 필요하면 tenantId:userId 같은 복합 키 또는 샤딩이 필요합니다.
기본 파티셔닝은 hash(key)%N 이라 N이 바뀌면 매핑이 크게 바뀝니다. Source
상태ful 처리(Kafka Streams, Flink state, 캐시 locality)가 있으면 재파티셔닝 비용이 커집니다.
핫 파티션은 “특정 파티션의 리더가 받는 부하” 문제라, 브로커를 늘려도 키가 바뀌지 않으면 병목 파티션이 그대로 남는 경우가 많습니다.
키 변경은 파티션 변경을 뜻하고, 곧 “순서/처리 주체(consumer instance)/상태”가 달라집니다. 롤아웃 전략이 필요합니다(새 토픽 + 이중발행, 리플레이 등).
예: 어떤 서비스는 "00123", 다른 서비스는 "123"을 같은 의미로 씀 → 서로 다른 파티션으로 갈 수 있음. “키는 계약(Contract)”입니다.
키는 라우팅 목적이므로 최소한으로 유지하는 편이 좋습니다(해시/네트워크/스토리지 오버헤드).
소비자가 어차피 다시 그룹핑해야 해서 비용이 올라가고, 순서 보장도 깨집니다.
New Relic 사례에서도 일부 key가 대부분 이벤트를 차지해 “unlucky partition” 핫스팟이 발생했다고 언급합니다. 이런 경우는 구조적 처방(샤딩/2단계 집계/토픽 분리)이 필요합니다. Source
키 전략은 결국 “파티션 부하 분산” 문제로 귀결되며, MSK에서는 브로커 자원/운영 제약과 직결됩니다.
MSK는 브로커 사이즈별로 권장 파티션 수(리더+팔로워 포함) 와 “업데이트를 지원하는 최대 파티션 수”를 제시합니다. 이 한도를 넘으면 클러스터 구성 업데이트 등 운영 작업이 제한될 수 있습니다. Source
MSK는 브로커 CPU(User+System) 60% 이하 유지를 강하게 권장합니다. 파티션 리더가 불균형하면 특정 브로커 CPU가 먼저 차고, 지연이 연쇄적으로 늘어납니다. Source
AWS의 사이징 관점에서 클러스터 처리량 한계는 스토리지/네트워크/복제/컨슈머 그룹 수 등에 의해 결정될 수 있고, 스케일 전략(브로커 수 vs 브로커 크기)은 병목 지점에 따라 달라집니다. Source
키 설계가 좋으면:
AWS 사이징 글에서도 “프로듀서가 부하를 고르게 퍼뜨리고, 브로커가 파티션을 균등하게 가진다” 같은 이상 조건을 전제로 논의를 시작합니다. 현실에서 가장 많이 깨지는 전제가 바로 “키 스큐”입니다. Source
ProducerRecord<String, String> record =
new ProducerRecord<>("orders", orderId, payloadJson);
producer.send(record);
같은 orderId는 같은 파티션으로 가며(기본 파티셔닝: murmur2+mod), 파티션 내 순서가 유지됩니다. Source
String key = tenantId + ":" + userId;
ProducerRecord<String, String> record =
new ProducerRecord<>("user-events", key, payloadJson);
producer.send(record);
int buckets = 16;
int bucket = Math.floorMod(orderId.hashCode(), buckets);
String key = orderId + "#" + bucket;
producer.send(new ProducerRecord<>("orders", key, payloadJson));
권장 사용 조건:
hash(key)%N Source