요즘 개발자 포토폴리오에 빠짐없이 적혀있는 Kafka, Redis 그 중에서도 Kafka는 매우 뜨거운 주제라고 볼 수 있다.
근데 Kafka를 처음 써보는 나로서는 어떠한 동작 과정으로 처리하는지 이해가 어려워 실제 구현보다 이론에 조금 치중한 한주를 보냈고 그 고민을 이 곳에 풀어보는 시간을 가지려고 한다.
Kafka는 분산 로그 저장소(Distributed Log Store)다.
Kafka Components에는 Broker, Cluster, Topic, Partition등이 있다.
복잡한 용어 대신 나만의 용어로 쉽게 풀어보자면
“프로듀서는 특정 터널(Topic)에 메시지를 push하고, 톨게이트 매표소(Broker)는 이를 차선(Partition) 단위의 로그로 저장하며,
컨슈머는 자신에게 할당된 차선(Partition)에서 offset 기준으로 선택적으로 pull 한다.”
나는 카프카를 하면서 용어가 헷갈릴 때마다 계속 이 문장을 옆에 띄워서 기억을 상기시켰다.
카프카는 다음과 같은 과정으로 동작한다고 보면 된다.
{ 프로듀서 } → {클러스터 [브로커 A, 브로커 B, 브로커 C]} ← { 컨슈머그룹 A : 컨슈머 A-1, A-2, A-3 }
내가 진행 중인 프로젝트에 대입하여 생각해보면
프로듀서는 이벤트를 발생시키는 주체다.
내 프로젝트에서는 OrderService가 대표적인 프로듀서 역할을 한다.
예를 들어 사용자가 주문을 완료하면,
이 이벤트의 의미는 “주문 번호 100번이 생성되었다”이다.
OrderService는 이 사실을 다른 도메인에게 직접 알려주지 않고,
Kafka를 통해 이벤트로 발행한다.
이때 프로듀서는
- topic = order-created-v1
- partitionKey = orderId
- acks = all
과 같은 설정으로 메시지를 Kafka 클러스터에 produce 한다.
Kafka 클러스터 (Broker A, B, C)
Kafka 클러스터는 여러 개의 브로커로 구성된다.
각 브로커는 토픽의 파티션 로그를 분산 저장하는 역할을 한다.
order-created-v1 토픽이 있고,
이 토픽이 6개의 파티션(P1 ~ P6)을 가진다고 가정하면,
이러한 로그들이 브로커들에 분산되어 저장된다.
acks=all 설정을 사용했기 때문에,
- Leader 브로커에 기록되고
- ISR(In-Sync Replica)에 속한 Follower 브로커들까지
- 모두 로그에 기록된 것이 보장된 후
Leader 브로커가 대표로 단 한 번의 ACK를 프로듀서에게 반환한다.
이 시점에서 프로듀서는
“이 이벤트는 안전하게 저장되었다” 라고 판단하고 다음 로직으로 넘어간다.
컨슈머 그룹 (Consumer Group)
이제 이 이벤트를 소비하는 쪽이 등장한다.
Kafka에서 컨슈머는 항상 컨슈머 그룹 단위로 동작한다.
내 프로젝트에서는 LOOPERS라는 컨슈머 그룹을 사용하고 있다.
이 그룹에 속한 컨슈머들은 order-created-v1 토픽의 파티션을 서로 나누어 할당받는다.
예를 들어,
- Consumer A-1 → P1, P2
- Consumer A-2 → P3, P4
- Consumer A-3 → P5, P6
이런 식으로 파티션 단위로 병렬 처리가 이루어진다.
Offset과 처리 보장
각 컨슈머는 자신에게 할당된 파티션에서
offset 기준으로 메시지를 순서대로 읽는다.
메시지 처리가 끝나면 해당 offset을 커밋하고,
이 정보는 Kafka 브로커에 저장된다.
그래서 컨슈머가 중간에 죽더라도, 다시 메시지를 읽어 처리할 수 있다.
Kafka 문서를 읽다 보니 acks=all 옵션과 관련해 High Watermark(HW) 개념이 조금 더 명확해졌다.
HW란 ISR(In-Sync Replicas)에 속한 모든 replica들이 공통으로 복제 완료한 최대 offset을 의미한다.
-> 쉽게 설명하자면 follower들이 Leader의 값을 복제할 때 공통적으로 도달한 최대값이라고 이해하면 된다.
여기서 중요한 점은 Producer가 ACK를 받는 조건이다.
나는 처음 학습을 할 때까지만 해도 ACK는 follwer들위 offset이 Leader의 최신 offset과 같아질 때라고 생각했었다.
그러나 그 생각은 AI가 제공한 반례에 의해 틀렸다는 것을 알게 되었다!
Producer A → offset 5
Producer B → offset 6
Leader log: 0 1 2 3 4 5 6
Follower A: 0 1 2 3 4 5
Follower B: 0 1 2 3 4 5
HW = 5이 경우,
• offset 5는 ISR의 모든 replica에 복제되었기 때문에
→ Producer A는 ACK를 받는다
• 하지만 offset 6은 아직 follower에 복제되지 않았으므로
→ Producer B는 HW가 6 이상으로 올라갈 때까지 ACK를 기다리게 된다
한 마디로 acks=all에서의 ACK 조건은
HW ≥ 해당 record의 offset이며, HW가 반드시 Leader의 최신 offset과 같아야 ACK가 반환되는 것은 아니다!
이렇게 예시를 통해서 이해하니, 왜 연산기호를 ==가 아닌 ≥를 썼는지도 자연스럽게 이해됐다.
참고로 단일 프로듀서를 사용한 경우엔 상단에 기술한 것처럼 Leader의 offset이 HW랑 동일할 때라고 이해하면 될 것 같다.
이렇게 이해하니 다중 프로듀서가 동시에 같은 브로커 클러스터에 메시지를 보내는 상황에서도, 각 Producer는 자신이 보낸 record 단위로 독립적인 ACK 보장을 받는다는 점이 자연스럽게 연결되었다.
'개발 > 루퍼스(Loopers)' 카테고리의 다른 글
| 10주간의 이커머스 개발을 통해 설계부터 대규모 데이터처리까지 경험하기 (1) | 2026.01.02 |
|---|---|
| Redis ZSET로 랭킹 시스템 구현하기 (0) | 2025.12.26 |
| ApplicationEvent 를 활용해 Facade 경량화 하기 (0) | 2025.12.12 |
| 인덱스를 이용하여 50만건의 데이터 빠르게 조회하기 + k6를 이용한 성능 테스트 (0) | 2025.11.28 |
| JPA CountBy 조회 vs 엔티티 필드 저장, DDD에서는 무엇을 선택해야 할까? (0) | 2025.11.21 |