Kafka 기초 (3) - Topics and Partitions: Key와 순서 보장
kafka-topics.sh describe 출력을 뜯어보고, key를 붙인 이벤트가 같은 partition으로 모여 순서가 보장되는 것을 console 도구로 직접 확인합니다. partition 수를 늘릴 때의 함정도 다룹니다.
Kafka 기초 시리즈의 3편입니다. 2편에서 만든 kafka container와 events topic을 이어서 씁니다. 전체 목차는 0편에 있습니다.
Topic 들여다보기
2편에서 partition 3개로 만든 events topic의 구성을 확인합니다.
1
2
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 --describe --topic events
1
2
3
4
Topic: events TopicId: lZgAB2cPTn6... PartitionCount: 3 ReplicationFactor: 1 Configs: ...
Topic: events Partition: 0 Leader: 1 Replicas: 1 Isr: 1
Topic: events Partition: 1 Leader: 1 Replicas: 1 Isr: 1
Topic: events Partition: 2 Leader: 1 Replicas: 1 Isr: 1
- PartitionCount: 3: 이 topic은 독립된 log 3개로 이루어져 있습니다
- Leader: 1: 각 partition의 읽기와 쓰기를 담당하는 broker의 번호입니다. broker가 하나뿐이라 전부 1입니다
- ReplicationFactor: 1, Replicas: 1, Isr: 1: 복제본이 원본 하나뿐이라는 뜻입니다. broker가 죽으면 데이터도 접근 불가가 되므로 운영에서는 3으로 잡습니다 (6편)
partition 하나하나가 1편에서 말한 추가 전용 log입니다. 이벤트는 셋 중 하나의 끝에 붙고, 그 partition 안에서의 위치 번호가 offset입니다.
이벤트는 어느 partition으로 가나
배정 규칙은 두 가지뿐입니다.
- key가 없으면: broker가 적당히 분산합니다. 어느 partition에 갈지 예측할 수 없고, 예측할 필요도 없는 이벤트에 씁니다
- key가 있으면: key의 hash로 partition이 정해집니다. 같은 key는 언제나 같은 partition으로 갑니다
순서가 왜 여기 걸려 있는지가 핵심입니다. Kafka는 partition 안에서만 순서를 보장하므로, “같은 사용자의 이벤트는 발생 순서대로 처리돼야 한다”는 요구는 곧 “같은 사용자의 이벤트를 같은 partition에 넣어야 한다”가 되고, 그 수단이 key입니다.
실습: key로 partition 고정 확인
직접 확인해 봅니다. 터미널 1에서 key를 붙여 보내는 producer를 띄웁니다. parse.key=true를 주면 입력 줄을 구분자 기준으로 key와 value로 나눠 보냅니다.
1
2
3
4
# 터미널 1: key:value 형식으로 입력하는 producer
docker exec -it kafka /opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server localhost:9092 --topic events \
--property parse.key=true --property key.separator=:
터미널 2에서 partition 번호와 key를 함께 출력하는 consumer를 띄웁니다.
1
2
3
4
# 터미널 2: 이벤트가 어느 partition에서 왔는지 표시하는 consumer
docker exec -it kafka /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic events --from-beginning \
--property print.partition=true --property print.key=true
producer에 사용자 두 명의 이벤트를 섞어 입력합니다.
1
2
3
4
5
6
# 터미널 1 입력 # 터미널 2 출력
>user-1:로그인 Partition:2 user-1 로그인
>user-2:로그인 Partition:0 user-2 로그인
>user-1:장바구니 Partition:2 user-1 장바구니
>user-2:구매 Partition:0 user-2 구매
>user-1:구매 Partition:2 user-1 구매
partition 번호는 실행마다 다를 수 있지만, 규칙은 매번 같습니다. user-1은 전부 같은 partition, user-2도 전부 같은 partition입니다. user-1의 로그인 → 장바구니 → 구매는 한 partition 안에 이 순서로 쌓였으므로, 어떤 consumer가 읽어도 이 순서로 읽힙니다. 반면 user-1과 user-2 사이의 순서는 partition이 다르므로 보장되지 않습니다 — 그리고 서로 다른 사용자 사이의 순서는 대부분 필요하지도 않습니다.
순서가 필요한 단위를 key로 잡는다가 실무 규칙입니다. 주문 이벤트면 주문 ID, 사용자 행동이면 사용자 ID, CDC면 테이블의 PK입니다. CDC 파이프라인 글에서 Debezium이 기본으로 테이블 PK를 key로 삼는 것이 바로 이 원리의 적용입니다.
Partition 수의 함정
partition 수는 topic을 만들 때 정하고, 이후에는 늘릴 수만 있습니다.
1
2
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 --alter --topic events --partitions 6
줄이는 방법은 없습니다. 그리고 늘리는 것도 공짜가 아닙니다. partition 수가 바뀌면 key의 hash가 가리키는 partition이 달라져서, 같은 key의 이벤트가 변경 전후로 다른 partition에 나뉘어 쌓입니다. 기존 이벤트가 옮겨지는 것이 아니므로, 그 경계 구간에서는 key 단위 순서 보장이 깨집니다.
그래서 partition 수는 처음부터 여유 있게 잡는 것이 관행입니다. partition 수가 consumer 병렬성의 상한이라는 다음 편의 규칙까지 합치면, partition 설계가 topic 설계의 사실상 전부입니다.
kafka-topics.sh 정리
| 옵션 | 역할 |
|---|---|
--create --topic 이름 --partitions N | topic 생성 |
--list | topic 목록 |
--describe --topic 이름 | partition 구성, Leader, 복제 상태 확인 |
--alter --topic 이름 --partitions N | partition 수 늘리기 (줄이기 불가) |
--delete --topic 이름 | topic 삭제 |
다음 편에서 읽는 쪽으로 넘어갑니다. consumer group이 partition을 나눠 맡는 것과, 어디까지 읽었는지를 추적하는 offset을 실습으로 확인합니다.