LabHub
배우기 러닝패스 코스

주문이 두 번 도착했고 한 번은 사라졌다 · Kafka 가 약속하는 것 · 이론

Kafka 가 약속하는 것 — 파티션 안의 순서와 정수 하나의 위치

LabHub 에서 이어서 보기

한 줄 요약

Kafka 의 토픽은 파티션으로 나뉜 추가 전용 로그이고, 같은 키의 이벤트는 같은
파티션에 순서대로 쌓이며, 컨슈머 그룹의 "어디까지 읽었나" 는 파티션마다 정수
하나(오프셋)다. 소비해도 지워지지 않는다 — 보존 설정이 지울 뿐이다. 이 네
문장이 뒤의 "두 번"과 "사라짐"을 읽는 열쇠다.

왜 이게 필요했나

큐를 써 본 사람은 Kafka 를 큐로 오해한다. 큐는 메시지를 꺼내면 사라지고, 브로커가
"누가 무엇을 받았나" 를 메시지마다 기억한다. [설계 문서](https://kafka.apache.org/43/design/design/)
의 'Consumer Position' 절은 그 방식의 비용을 적는다 — 넘겨주자마자 소비됨으로
표시하면 컨슈머가 처리 중 죽었을 때 메시지가 사라지고, 확인(ack)을 기다리면
처리했는데 확인을 못 보낸 경우 두 번 소비되며, 브로커는 메시지마다 상태를
여러 개 들고 있어야 한다.

Kafka 는 다른 길을 택했다. 토픽을 전순서(totally ordered) 파티션들로 나누고,
파티션 하나는 한 컨슈머 그룹 안에서 정확히 한 컨슈머만 읽게 하면, 컨슈머의
위치는 "다음에 읽을 오프셋" 정수 하나가 된다. 그 정수를 주기적으로 체크포인트
하는 것이 확인의 전부라 매우 싸고, 부산물로 되감기가 생긴다 — 코드에
버그가 있었다면 옛 오프셋으로 돌아가 다시 읽을 수 있다. 문서는 이것이 큐의
계약을 어기지만 많은 컨슈머에게 필수 기능이라고 적는다.

어떻게 동작하나

이벤트와 토픽. [소개 문서](https://kafka.apache.org/intro)대로 이벤트는
키·값·타임스탬프(와 선택적 헤더)를 가지며, 토픽은 그 이벤트가 쌓이는 자리다.
토픽은 여러 프로듀서와 여러 컨슈머를 동시에 받고, 이벤트는 소비된 뒤에도
지워지지 않는다. 얼마나 오래 남길지는 토픽별 보존 설정이 정한다(4모듈).

파티션과 키. 토픽은 여러 파티션("버킷")으로 나뉘어 여러 브로커에 놓인다.
새 이벤트는 그중 한 파티션의 끝에 붙는데, 같은 키(예: 주문 번호)의 이벤트는
같은 파티션으로 간다. [프로듀서 설정 문서](https://kafka.apache.org/43/configuration/producer-configs/)
partitioner.class 항목이 기본 동작을 적어 두었다 — 키가 있으면 키의
해시로 파티션을 고르고, 키가 없으면 batch.size 만큼 채워질 때까지 한
파티션에 붙는 sticky 방식이다. 그래서 키 없이 보낸 소량의 이벤트는 한
파티션에 몰리고, 키를 주면 주문마다 한 줄로 늘어선다.

Kafka 가 약속하는 순서는 파티션 안뿐이다. 주문 하나의 `created → paid →
shipped` 는 같은 파티션이라 그 순서로 읽히지만, 다른 주문끼리는 어느 파티션이
먼저 읽힐지 정해져 있지 않다. 순서가 필요한 단위가 곧 키여야 하는 이유다.

orders (3 파티션)              컨슈머 그룹 order-svc 의 위치 P0: order-2 created, order-3 created, order-2 paid   → 오프셋 3 P1: order-1 created, order-1 paid, order-1 shipped   → 오프셋 3 P2: order-4 created, order-5 created                 → 오프셋 2

컨슈머 그룹과 오프셋. 그룹은 group.id 로 묶인 컨슈머들이고, 그룹의 위치는
파티션마다 브로커에 커밋된 오프셋이다. 다른 그룹은 다른 위치를 갖는다 —
order-svc 가 끝까지 읽었어도 analytics 는 처음부터 다시 읽는다. 로그 끝
오프셋과 커밋된 오프셋의 차이가 지연(lag)이고, 운영에서 가장 먼저 보는
숫자다. kafka-consumer-groups.sh --describe 가 파티션마다 CURRENT-OFFSET,
LOG-END-OFFSET, LAG 을 보여 준다.

파티션 수는 늘릴 수만 있다. [운영 문서](https://kafka.apache.org/43/operations/basic-kafka-operations/)
의 'Modifying topics' 절은 파티션을 늘릴 때의 부작용을 셋 적는다. 데이터가
hash(key) % 파티션 수 로 나뉘므로 수가 바뀌면 같은 키가 다른 파티션으로 갈 수
있고 기존 데이터는 재배치되지 않는다(키 순서 보장이 깨질 수 있다),
auto.offset.reset=latest 인 기존 컨슈머는 새 파티션을 알아채기 전에 들어온
메시지를 놓칠 수 있다, 메타데이터 전파에 지연이 있다. 그리고 줄이는 것은
지원되지 않는다.

현장에서 만나는 모습

"주문 상태가 뒤집혀 보인다" 는 신고의 흔한 원인은 키다. 주문 서비스가 키 없이
보내고 있었거나, 주문 번호가 아니라 이벤트 종류를 키로 썼거나, 파티션 수를
늘린 뒤 같은 주문의 옛 이벤트와 새 이벤트가 다른 파티션에 갈라져 있는
경우다. 세 경우 모두 브로커는 정상이고 로그도 정상이다 — 약속하지 않은 것을
기대한 것이다.

지연 그래프가 갑자기 오르면 두 가지 중 하나다. 프로듀서가 많이 보내거나
컨슈머가 멈췄거나. 그룹을 describe 해서 CONSUMER-ID 열이 비어 있으면 후자다.
컨슈머가 죽어도 커밋된 오프셋은 브로커에 남아 있으므로, 다시 뜨면 그 자리부터
읽는다 — 그때 무엇이 두 번 오고 무엇이 사라지는지가 3모듈이다.

다음 실습에서 할 것

파티션 3개짜리 토픽에 키 있는 이벤트를 넣어 같은 키가 같은 파티션에 순서대로
쌓이는 것을 보고, 그룹의 오프셋과 지연을 읽고, 두 그룹이 독립임을 확인한 뒤,
파티션을 6개로 늘려 키 배치가 바뀌는 것을 직접 관찰한다.