배송팀이 이미 읽은 결제 기록을, 한 달 뒤에 합류한 분석팀도 처음부터 다시 읽을 수 있어요.메시지는 보통 쪽지처럼 느껴져요. 보내는 사람이 받는 사람에게 건네고, 받는 사람이 처리하면 역할이 끝나는 모습이죠. 그래서 Kafka도 “서비스 사이에서 메시지를 전달하는 빠른 우체통” 정도로 생각하기 쉬워요. 근데요, 사실은 전달만 생각하면 Kafka의 중요한 절반을 놓치게 돼요. Kafka의 중심에는 누군가에게 쪽지를 건네는 장면보다, 일어난 일을 순서대로 기록하고 여러 독자가 각자의 위치에서 읽는 장면이 있어요. 왜 이런 구조가 필요해졌는지, 온라인 쇼핑몰의 결제 장면부터 따라가 볼게요.
처음에는 직접 부르면 충분해 보여요
고객이 주문을 결제했어요. 이제 주문 서비스가 배송, 포인트, 알림 서비스에 차례로 요청을 보내면 될 것 같죠? 서비스가 몇 개 없고 결과를 즉시 받아야 한다면 이 구조가 가장 단순할 수 있어요. HTTP 같은 직접 호출 자체가 나쁜 것은 아니에요. 문제는 결제 뒤에 반응할 팀이 계속 늘어날 때 생겨요.- 알림 서비스가 느리면 주문 응답도 함께 기다려야 할까요?
- 배송 서비스가 잠시 멈췄다면 결제를 실패로 되돌려야 할까요?
- 분석 서비스가 새로 생길 때 주문 서비스를 다시 수정해야 할까요?
- 어제 결제된 주문까지 새 분석 방식으로 다시 계산하려면 어디서 가져와야 할까요?
중간에 작업표를 두면 시간은 분리돼요
이번에는 주문 서비스가 후속 서비스에 직접 전화하지 않고, 접수대에 “주문 42번 결제 완료”라는 작업표를 남긴다고 해볼게요. 배송 담당자는 자기 차례에 작업표를 가져가 처리해요. 이것이 전형적인 **작업 큐(work queue)**의 mental model이에요.
보내는 쪽과 처리하는 쪽이 같은 순간에 움직이지 않아도 되니 시간이 분리돼요. 이것만 필요한 시스템이라면 단순한 작업 큐가 더 잘 맞을 수 있어요.
하지만 다음 요구가 붙으면 작업표 비유만으로는 부족해져요.
“배송팀이 가져간 결제 완료 기록을 정산팀도 읽고, 다음 달에 생긴 분석팀도 지난달 것부터 다시 읽고 싶어요.”하나의 작업을 누가 가져가 처리할지가 중심인 큐와, 일어난 사실을 남겨 여러 독자가 각자 읽게 하는 기록은 해결하려는 질문이 달라요.
실제 메시지 브로커마다 보관, fan-out, 재전달 기능은 달라요. 여기서는 특정 제품 전체를 정의하는 것이 아니라, 처리할 작업 하나를 소비자 중 하나에게 건네는 전형적인 작업 큐 그림과 Kafka의 출발점을 비교하고 있어요.
Kafka는 작업표보다 장부에 가까워요
카페 벽에 하루 동안 일어난 일을 시간순으로 적는 장부가 있다고 상상해볼게요. 배송팀이 2번까지 읽었다고 장부의 앞장을 뜯어버리지는 않아요. 정산팀과 분석팀은 각자 어디까지 읽었는지를 기억하고, 자기 속도로 다음 기록을 읽어요. 이 장면을 Kafka 용어로 옮기면 이래요.
여기서 event와 record는 비슷하게 들리지만 초점이 조금 달라요. “주문이 결제됐다”는 과거의 업무 사실이 event이고, 그 사실을 key, value, timestamp 같은 형태로 Kafka에 적어둔 단위가 record예요.
기록은 실제로 어떻게 흘러갈까요?
주문 42번의 결제가 끝난 뒤를 한 단계씩 볼게요.- Producer인 결제 서비스가 결제 완료 record를 보냅니다.
- Kafka는 그 record를 topic 안의 한 partition 끝에 이어 붙여요.
- 배송 consumer group은 자기 위치부터 record를 읽고 배송을 준비해요.
- 분석 consumer group도 같은 기록을 자기 위치부터 독립적으로 읽어요.
- 각 group은 다음에 읽을 위치를 관리하므로 서로 속도가 달라도 돼요.
근데 왜 읽은 기록을 바로 지우지 않을까요?
기록이 남아 있으면 같은 사실을 여러 목적에 다시 사용할 수 있어요. 예를 들어 오전 10시에주문 42 결제 완료 record가 기록됐다고 해볼게요.
새 분석팀이 생겼다고 결제 서비스가 과거의 이벤트를 다시 만들어 보낼 필요가 없어요. 보관 중인 record가 있다면 분석팀의 읽기 위치를 앞쪽으로 옮겨 다시 읽을 수 있어요. 이런 동작을 흔히 replay라고 불러요.
하지만 “기록이 남는다”를 “영원히 남는다”로 이해하면 안 돼요.
Kafka는 topic의 retention 정책에 따라 record를 보관해요. 정해진 시간이나 크기 조건을 넘어 오래된 record가 제거될 수 있으므로, replay는 아직 보관 중인 범위에서만 가능해요.
Kafka가 풀어주는 세 가지 분리
지금까지의 장면을 세 가지로 묶을 수 있어요.보내는 시간과 읽는 시간을 분리해요
Producer가 record를 남기는 순간과 consumer가 처리하는 순간이 꼭 같지 않아도 돼요. Consumer가 잠시 느려도 보관 범위 안의 기록을 나중에 따라 읽을 수 있어요.쓰는 서비스와 읽는 서비스를 분리해요
결제 서비스가 배송팀, 정산팀, 분석팀의 주소를 하나씩 직접 알 필요가 줄어들어요. 새로운 consumer group은 기존 producer의 호출 목록에 들어가지 않고 같은 topic을 구독할 수 있어요.지금의 처리와 과거의 기록을 분리해요
한 번 읽었다고 record가 즉시 사라지지 않으므로, 다른 목적의 consumer가 독립적으로 읽거나 보관 범위 안에서 과거 위치로 돌아가 다시 처리할 수 있어요. 이 세 가지가 Kafka를 단순한 “빠른 전달 도구”보다 분산된 event log를 중심으로 한 시스템으로 바라봐야 하는 이유예요.그렇다면 모든 곳에 Kafka를 쓰면 될까요?
여기서 또 한 번 뒤집어볼게요. 사실은 아니에요. Kafka를 운영하면 broker, partition 수, 복제, 보관 정책, consumer lag, schema, 중복 처리 같은 새로운 책임이 생겨요. 다음과 같은 상황이라면 더 단순한 선택이 나을 수 있어요.- 사용자가 지금 결과를 받아야 하는 요청은 직접 HTTP 호출이 자연스러울 수 있어요.
- 하나의 작업을 worker 한 명에게만 전달하게 된다면, 단순한 작업 큐가 더 적합할 수 있어요.
- 여러 독자의 독립적인 읽기나 replay가 필요 없고 데이터가 이미 database table에 잘 담긴다면, 그 table과 batch job으로 충분할 수 있어요.
- 같은 event를 여러 팀이 서로 다른 속도로 읽어야 하나요?
- Consumer가 잠시 멈춰도 나중에 밀린 기록을 따라가야 하나요?
- 새로운 consumer가 과거 event부터 처리해야 하나요?
- 큰 양의 연속된 event를 partition 단위로 나눠 처리해야 하나요?
자, 정리해볼까요?
- 직접 호출은 단순하지만 후속 서비스가 늘어나면 보내는 쪽이 대상과 실패를 많이 알게 될 수 있어요.
- 전형적인 작업 큐는 보내는 시간과 처리 시간을 분리하지만, Kafka는 여기에 여러 독자의 독립적인 읽기와 기록 재사용이라는 관점을 더해요.
- Kafka에서 event는 topic의 partition에 record로 이어지고, 각 consumer group은 자기 읽기 위치를 따라가요.
- Record는 읽었다는 이유만으로 바로 사라지지 않지만, retention 정책이 허용하는 범위에서만 보관되고 replay할 수 있어요.
- Kafka의 순서 보장 범위는 topic 전체가 아니라 같은 partition 안이에요.
- Kafka가 항상 정답은 아니며, 단순한 직접 호출이나 작업 큐가 문제에 더 잘 맞을 수도 있어요.
다음 글에서는
이제 Kafka가 왜 기록을 중심에 두는지는 감이 왔어요. 그런데 이런 질문이 남죠?“그 기록을 직접 한 번 보내보고 읽어보면, 지금 본 그림이 실제로 어떻게 보일까요?”다음 글에서는 local Kafka를 실행하고 topic에 첫 record를 보내는 흐름으로 이어갈게요. 글이 공개되면 이곳에 링크를 연결할게요.