결제 완료가 먼저였는데 배송 시작 record가 먼저 처리되면 어떡하죠?주문 41번은
PAID 다음에 SHIPPING으로 바뀌어야 해요. 주문 45번은 PAID 다음에 CANCELLED가 됐고요. 두 주문이 동시에 들어오면 Kafka가 topic 전체를 하나의 긴 줄로 세워줄까요?
그렇지 않아요. Kafka에서 순서를 이야기할 때는 먼저 어느 partition 안의 순서인지 물어야 해요.
앞 글에서 orders.paid topic의 partition 하나에 첫 record를 남겼어요. 이번에는 partition을 두 개로 늘린 새 topic을 만들고, 두 주문의 key가 각각 다른 줄로 가는 모습을 확인해볼게요.
이 글의 동작 설명과 CLI 예제는 Apache Kafka 4.3.1을 기준으로 해요. Docker는
apache/kafka:4.3.1, Ubuntu 직접 설치는 OpenJDK 21과 kafka_2.13-4.3.1.tgz를 사용했어요. 아래의 정확한 partition 번호는 topic의 partition 수, 기본 partitioner, key serializer가 같을 때의 예제 결과예요.Topic 하나에 기록 줄을 두 개 둬볼게요
Topic은 record를 묶는 논리적인 이름이에요. 실제 append와 읽기의 기본 단위는 그 안의 partition이에요. Partition마다 offset은0, 1, 2...처럼 따로 증가해요. partition 0의 offset 1과 partition 1의 offset 1은 서로 다른 위치예요.
그래서 offset은 이 세 가지가 아니에요.
- Topic 전체에서 하나뿐인 record ID가 아니에요.
- 모든 partition을 합친 전역 순번이 아니에요.
- Business event의 식별자도 아니에요.
eventId 같은 business identifier가 필요하고요.
두 partition에 두 key를 보내봐요
앞 글의working-first-record 상태에서 broker를 실행하세요. 이번 실습은 두 VM 모두 orders.paid가 있고 order-sequence는 아직 없는 상태에서 시작해요.
- Docker
- Ubuntu 직접 설치
1
기존 broker와 topic을 먼저 확인해요
orders.paid의 partition 0이 보이면 앞 글의 broker에 연결된 거예요.2
Partition이 두 개인 topic을 만들어요
PartitionCount: 2와 partition 0, 1을 확인해요. Broker가 하나뿐이므로 replication factor는 1이에요.3
Key와 value를 구분해 네 record를 보내요
> prompt에서 아래 네 줄을 차례로 입력한 뒤 Ctrl+C를 눌러요.4
Partition과 offset을 함께 출력해요
order-41과 order-45가 record key예요. PAID, SHIPPING, CANCELLED는 value고요.
order-41의 두 record가 같은 partition에서 offset 0, 1로 이어지고, order-45의 두 record도 다른 한 partition에서 offset 0, 1로 이어졌다면 key 기반 배치를 확인한 거예요.order-sequence topic과 네 record를 지우지 마세요. 다음 글에서 shipping-service와 analytics-service group이 같은 네 record를 서로 다른 위치에서 읽어요. 다시 시작할 복구 지점이 필요하면 broker를 정상 종료한 뒤 두 VM에 topic-ordering-ready snapshot을 남기세요.Key는 같은 업무 흐름을 같은 줄로 모아요
기본 producer partitioning logic은 partition을 직접 지정하지 않았고 key가 있다면 key의 hash를 사용해 partition을 골라요. 같은 key를 같은 serializer와 같은 partition 수로 보내면 같은 partition으로 모을 수 있어요. 이 예제에서orderId를 key로 고른 이유는 같은 주문의 상태 변화 순서가 중요하기 때문이에요. 모든 주문을 한 줄로 모으려는 게 아니에요.
Key를 고를 때는 “무엇끼리 같은 순서를 공유해야 하나요?”를 물어보세요.
Key를 너무 넓게 잡으면 많은 record가 한 partition으로 몰려요. 너무 잘게 나누면 함께 처리해야 할 업무 흐름이 여러 partition으로 흩어질 수 있어요.
순서 보장은 partition 경계를 넘지 않아요
위 출력에서 partition 1의 record가 먼저 보였다고 해서order-45의 결제가 order-41보다 먼저 일어났다고 단정할 수 없어요. Consumer가 여러 partition을 fetch하고 결과를 출력하는 시점은 topic 전체의 event time 순서를 만들지 않아요.
순서에는 또 다른 경계도 있어요.
- Producer가 record를 어떤 순서로 보냈는지
- Broker가 같은 partition에 어떤 offset으로 저장했는지
- Consumer가 어떤 순서로
poll했는지 - Business code가 병렬 작업을 어떤 순서로 끝냈는지
- Database나 외부 API side effect가 어떤 순서로 반영됐는지
Key가 없으면 같은 흐름이 한 partition에 남는다고 기대할 수 없어요
Kafka 4.3의 기본 producer logic에서 key와 명시적인 partition이 모두 없으면, producer는 batch를 모으는 동안 한 partition을 사용하다가 조건에 따라 다른 partition으로 바꿀 수 있어요. 이것을 “한 줄씩 정확히 round-robin한다”라고 이해하면 안 돼요. Key가 없는 record도 partition 안에서는 offset 순서를 갖지만, 서로 관련된 record가 계속 같은 partition으로 간다는 업무 보장은 없어요.Partition 수를 늘리면 같은 key의 번호도 바뀔 수 있어요
기본 key partitioning은 key hash와 현재 partition 수를 함께 사용해요. Partition 수가 2에서 4로 늘어나면 같은 key가 앞으로 다른 partition 번호로 갈 수 있어요. 이미 저장된 record가 새 partition으로 이동하는 것은 아니에요. 증설 전 record는 예전 partition에 남고, 증설 뒤 record는 새 계산 결과의 partition에 저장될 수 있어요. 그러면 같은 key의 전체 history가 둘 이상의 partition에 걸칠 수 있어요. Partition을 줄이는 작업도 일반적인 topic 변경 명령으로 지원되지 않아요. 새 topic으로 옮기는 migration을 설계해야 할 수 있으므로 처음 수를 정할 때는 현재 처리량만 보지 말고 성장과 운영 비용을 함께 봐야 해요.Partition 수는 consumer 병렬성의 상한도 만들어요
Partition 두 개를 같은 consumer group이 읽는다면, 동시에 소유할 수 있는 consumer는 최대 두 개예요. Consumer를 세 개 띄워도 partition이 두 개라면 같은 group의 한 consumer는 쉬어요. 반대로 consumer 하나가 partition 두 개를 모두 맡을 수도 있어요. 이 관계 때문에 partition은 저장 구조이면서 병렬 처리 단위예요. 다음 글에서 consumer group이 partition을 어떻게 나눠 맡고, offset을 어디까지 읽었다는 위치로 사용하는지 이어갈게요.자, 정리해볼까요?
-
Topic은 하나 이상의 partition으로 나뉘고, offset은 partition마다
0, 1, 2...로 따로 증가해요. - 기본 producer는 key가 있으면 key hash를 사용해 partition을 골라요.
- 같은 key를 같은 partition에 모으면 그 partition의 offset 순서로 업무 흐름을 읽을 수 있어요.
- Kafka의 ordering 범위는 topic 전체가 아니라 같은 partition 안이에요.
- Partition 수를 바꾸면 key 배치가 달라질 수 있고, partition 수는 같은 consumer group의 병렬성 상한을 만들어요.
이전 글
로컬 broker에서 첫 record를 보내고 재시작 뒤에도 읽어봐요.
다음 글
Consumer group이 partition을 나눠 맡고 commit으로 다음 읽기 위치를 남기는 과정을 따라가요.