파티셔닝과 오프셋 관리
파티션은 왜 존재하는가
Kafka 토픽 하나가 단일 브로커의 단일 파일이라면, 쓰기 처리량은 디스크 하나의 한계에 묶인다. 파티션은 토픽을 수평으로 쪼개 여러 브로커에 분산시키는 단위다. 파티션 수 × 브로커 수만큼 병렬 쓰기와 병렬 읽기가 가능해진다.
파티션 내부에서 레코드는 오프셋(offset) 이라는 단조 증가 정수로 식별된다. 오프셋은 파티션 범위 안에서만 유일하다. 토픽 전체에서 유일한 식별자는 (파티션 번호, 오프셋) 쌍이다.
독립적으로 증가 ← 는 현재 LEO
(Log End Offset)
파티셔닝 전략
메시지가 어느 파티션에 기록될지는 프로듀서의 파티셔너가 결정한다.
키 기반 파티셔닝
ProducerRecord에 키가 있으면 murmur2(key) % numPartitions로 파티션을 고정한다. 같은 키는 항상 같은 파티션으로 가므로, 파티션 내 순서가 보장된다. 주문 ID를 키로 쓰면 동일 주문의 이벤트(생성 → 결제 → 배송)가 순서대로 도착한다.
주의: 파티션 수를 늘리면 동일 키가 다른 파티션으로 라우팅될 수 있다. 순서가 중요한 토픽은 파티션 수를 초기에 넉넉하게 설계한다.
키 없는 라운드로빈 vs Sticky Partitioner
키가 null이면 기본 동작이 버전별로 다르다.
| 버전 | 기본 파티셔너 | 동작 |
|---|---|---|
| Kafka < 2.4 | DefaultPartitioner (round-robin) | 레코드마다 파티션을 순환 |
| Kafka ≥ 2.4 | StickyPartitioner | 배치가 채워지거나 linger.ms가 지날 때까지 같은 파티션에 누적 |
Sticky Partitioner는 브로커 한 곳에 배치를 모아 쓰므로 압축·쓰기 효율이 높다.
커스텀 파티셔너
org.apache.kafka.clients.producer.Partitioner 인터페이스를 구현하면 비즈니스 규칙으로 파티션을 지정할 수 있다. 예를 들어 VIP 사용자의 이벤트를 별도 파티션에 라우팅해 전용 컨슈머가 처리하게 할 수 있다.
파티션 수 결정 기준
파티션이 많을수록 처리량이 늘지만 메타데이터 오버헤드와 리밸런싱 비용도 커진다.
실무에서 쓰는 근거:
- 목표 처리량 / 파티션당 처리량 으로 시작. 단일 파티션은 보통 수십 MB/s를 처리할 수 있다.
- 컨슈머 인스턴스 수보다 파티션 수가 많아야 병렬 소비가 가능하다. 컨슈머 수 = 파티션 수가 최대 병렬도다.
- 추후 스케일아웃을 고려해 처음부터 여유 있게 잡는다(키 순서 보장 토픽은 변경이 어렵다).
- Kafka 공식 문서는 브로커당 파티션 수 4000개, 클러스터 전체 200,000개를 권고 상한으로 제시한다 (KRaft 기준).
오프셋의 세 가지 위치
컨슈머 입장에서 파티션에는 세 가지 의미 있는 오프셋 위치가 있다.
| 용어 | 설명 |
|---|---|
| LEO (Log End Offset) | 리더가 다음에 쓸 오프셋. 현재 마지막 메시지 + 1. |
| HW (High Watermark) | 모든 ISR에 복제 완료된 마지막 오프셋. 컨슈머는 HW 이하만 읽을 수 있다. |
| 커밋 오프셋 (Committed Offset) | 컨슈머 그룹이 __consumer_offsets에 기록한 처리 완료 지점. |
LEO와 HW의 차이 구간(HW ~ LEO)은 복제 중인 미확정 메시지다. 리더 장애 시 이 구간이 롤백될 수 있으므로 컨슈머에게는 노출하지 않는다.
__consumer_offsets: 오프셋 저장소
컨슈머 그룹이 commitSync() / commitAsync()를 호출하면 오프셋은 브로커 내부의 특수 토픽 __consumer_offsets에 기록된다. 기본 파티션 수는 50개이며, 그룹 ID를 해시해 어느 파티션(=어느 브로커)이 그룹 코디네이터 역할을 맡을지 결정한다.
레코드 키는 (group.id, topic, partition) 튜플이다. 이 토픽은 로그 컴팩션 으로 관리되므로, 같은 키의 오래된 레코드는 주기적으로 정리되고 최신 오프셋만 남는다. 덕분에 컨슈머를 재시작해도 커밋된 오프셋을 빠르게 복원할 수 있다.
auto.offset.reset: 커밋이 없을 때
처음 소비하거나 커밋 오프셋이 만료·삭제된 경우 auto.offset.reset 설정이 적용된다.
latest(기본값): 구독 시점 이후의 신규 메시지만 소비. 과거 데이터를 놓친다.earliest: 파티션의 가장 오래된 메시지부터 소비. 재처리나 신규 그룹에 유용.none: 커밋 오프셋이 없으면 예외 발생. 운영 환경에서 실수 방지용.
파티션 수 변경 시 주의사항
파티션을 늘리는 것은 가능하지만 줄이는 것은 지원되지 않는다. 키 기반 파티셔닝을 쓰는 토픽에서 파티션을 늘리면 hash(key) % numPartitions 결과가 바뀌어 기존 키의 메시지가 다른 파티션으로 갈 수 있다. 이를 허용하지 않는다면 마이그레이션 전략(새 토픽으로 복사, 커넥터 재설정 등)이 필요하다.
Open Questions
- KIP-848(Next-Gen Consumer Rebalance Protocol)은 Kafka 4.0에서 조기 접근 기능으로 포함됐으나, 일반 GA 여부는 배포판별로 확인 필요.
References
- https://kafka.apache.org/documentation/#design_offsetmanagement
- https://www.conduktor.io/glossary/kafka-topics-partitions-brokers-core-architecture
- https://oneuptime.com/blog/post/2026-01-27-kafka-topics-partitions-design/view
- https://oneuptime.com/blog/post/2026-01-30-kafka-consumer-assignment-strategies/view
- https://oneuptime.com/blog/post/2026-01-25-manage-consumer-offsets-kafka/view
- https://www.automq.com/blog/kafka-offsets-best-practices
- https://www.confluent.io/blog/guide-to-consumer-offsets/
- https://cwiki.apache.org/confluence/display/KAFKA/Offset+Management