> ## Documentation Index
> Fetch the complete documentation index at: https://blog.nvim.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Kafka는 왜 메시지를 줄 세워 기록할까요?

> 직접 호출과 전형적인 작업 큐만으로 풀기 어려운 시간 분리, 여러 독자의 독립적인 읽기, 과거 재생 문제에서 Kafka의 출발점을 찾아봐요.

> 배송팀이 이미 읽은 결제 기록을, 한 달 뒤에 합류한 분석팀도 처음부터 다시 읽을 수 있어요.

메시지는 보통 쪽지처럼 느껴져요.

보내는 사람이 받는 사람에게 건네고, 받는 사람이 처리하면 역할이 끝나는 모습이죠. 그래서 Kafka도 “서비스 사이에서 메시지를 전달하는 빠른 우체통” 정도로 생각하기 쉬워요.

근데요, **사실은 전달만 생각하면 Kafka의 중요한 절반을 놓치게 돼요.**

Kafka의 중심에는 누군가에게 쪽지를 건네는 장면보다, **일어난 일을 순서대로 기록하고 여러 독자가 각자의 위치에서 읽는 장면**이 있어요. 왜 이런 구조가 필요해졌는지, 온라인 쇼핑몰의 결제 장면부터 따라가 볼게요.

***

## 처음에는 직접 부르면 충분해 보여요

고객이 주문을 결제했어요. 이제 주문 서비스가 배송, 포인트, 알림 서비스에 차례로 요청을 보내면 될 것 같죠?

```mermaid theme={null}
flowchart LR
    O[주문 서비스<br />결제 완료]
    D[배송 서비스]
    P[포인트 서비스]
    N[알림 서비스]

    O -->|배송 준비 요청| D
    O -->|포인트 적립 요청| P
    O -->|결제 알림 요청| N
```

서비스가 몇 개 없고 결과를 즉시 받아야 한다면 이 구조가 가장 단순할 수 있어요. HTTP 같은 직접 호출 자체가 나쁜 것은 아니에요.

문제는 결제 뒤에 반응할 팀이 계속 늘어날 때 생겨요.

* 알림 서비스가 느리면 주문 응답도 함께 기다려야 할까요?
* 배송 서비스가 잠시 멈췄다면 결제를 실패로 되돌려야 할까요?
* 분석 서비스가 새로 생길 때 주문 서비스를 다시 수정해야 할까요?
* 어제 결제된 주문까지 새 분석 방식으로 다시 계산하려면 어디서 가져와야 할까요?

주문 서비스가 모든 후속 작업의 주소와 실패를 직접 책임지기 시작하면, 결제라는 본래 일보다 주변 서비스의 사정을 더 많이 알게 돼요.

***

## 중간에 작업표를 두면 시간은 분리돼요

이번에는 주문 서비스가 후속 서비스에 직접 전화하지 않고, 접수대에 “주문 42번 결제 완료”라는 작업표를 남긴다고 해볼게요. 배송 담당자는 자기 차례에 작업표를 가져가 처리해요.

이것이 전형적인 \*\*작업 큐(work queue)\*\*의 mental model이에요.

| 해결하고 싶은 문제      | 작업표를 두면 달라지는 점              |
| --------------- | --------------------------- |
| 배송 서비스가 잠시 느려요  | 주문 서비스는 작업표를 먼저 남길 수 있어요    |
| 순간적으로 주문이 몰려요   | 작업표를 쌓아두고 가능한 속도로 처리할 수 있어요 |
| 배송 담당자가 여러 명이에요 | 서로 다른 작업표를 나눠 처리할 수 있어요     |

보내는 쪽과 처리하는 쪽이 같은 순간에 움직이지 않아도 되니 **시간이 분리**돼요. 이것만 필요한 시스템이라면 단순한 작업 큐가 더 잘 맞을 수 있어요.

하지만 다음 요구가 붙으면 작업표 비유만으로는 부족해져요.

> “배송팀이 가져간 결제 완료 기록을 정산팀도 읽고, 다음 달에 생긴 분석팀도 지난달 것부터 다시 읽고 싶어요.”

하나의 작업을 누가 가져가 처리할지가 중심인 큐와, 일어난 사실을 남겨 여러 독자가 각자 읽게 하는 기록은 해결하려는 질문이 달라요.

<Note title="모든 queue 제품이 똑같다는 뜻은 아니에요">
  실제 메시지 브로커마다 보관, fan-out, 재전달 기능은 달라요. 여기서는 특정 제품 전체를 정의하는 것이 아니라, **처리할 작업 하나를 소비자 중 하나에게 건네는 전형적인 작업 큐 그림**과 Kafka의 출발점을 비교하고 있어요.
</Note>

***

## Kafka는 작업표보다 장부에 가까워요

카페 벽에 하루 동안 일어난 일을 시간순으로 적는 장부가 있다고 상상해볼게요.

```mermaid theme={null}
flowchart TB
    subgraph L["결제 기록 장부"]
        direction TB
        R0["0<br />주문 41 결제 완료"]
        R1["1<br />주문 42 결제 완료"]
        R2["2<br />주문 43 결제 완료"]
        R3["3<br />주문 44 결제 완료"]
        R0 --> R1 --> R2 --> R3
    end

    D[배송팀<br />다음은 3번] -.-> R3
    S[정산팀<br />다음은 2번] -.-> R2
    A[분석팀<br />다음은 1번] -.-> R1
```

배송팀이 2번까지 읽었다고 장부의 앞장을 뜯어버리지는 않아요. 정산팀과 분석팀은 각자 어디까지 읽었는지를 기억하고, 자기 속도로 다음 기록을 읽어요.

이 장면을 Kafka 용어로 옮기면 이래요.

| 장부 장면                         | Kafka에서 부르는 말  |
| ----------------------------- | -------------- |
| 결제 완료처럼 실제로 일어난 사실            | Event          |
| 그 사실을 Kafka에 저장한 한 줄          | Record         |
| 결제 기록을 모아둔 장부 이름              | Topic          |
| 장부를 나눠 적는 각각의 줄               | Partition      |
| 한 partition 안에서 record가 놓인 위치 | Offset         |
| 장부에 record를 쓰는 서비스            | Producer       |
| 기록을 읽고 처리하는 서비스               | Consumer       |
| 같은 목적과 읽기 위치를 공유하는 독자 팀       | Consumer group |

여기서 **event**와 **record**는 비슷하게 들리지만 초점이 조금 달라요. “주문이 결제됐다”는 과거의 업무 사실이 event이고, 그 사실을 key, value, timestamp 같은 형태로 Kafka에 적어둔 단위가 record예요.

***

## 기록은 실제로 어떻게 흘러갈까요?

주문 42번의 결제가 끝난 뒤를 한 단계씩 볼게요.

```mermaid theme={null}
flowchart TB
    P[결제 서비스<br />producer]
    K[Kafka 결제 topic<br />partition 끝에 record 추가]

    subgraph D["배송 consumer group"]
        direction TB
        D1[자기 위치부터 읽기]
        D2[배송 준비]
        D3[읽기 위치 진행]
        D1 --> D2 --> D3
    end

    subgraph A["분석 consumer group"]
        direction TB
        A1[자기 위치부터 읽기]
        A2[매출 지표 계산]
        A3[읽기 위치 진행]
        A1 --> A2 --> A3
    end

    P -->|주문 42 결제 완료 record| K
    K --> D1
    K --> A1
```

1. **Producer**인 결제 서비스가 결제 완료 record를 보냅니다.
2. Kafka는 그 record를 topic 안의 한 **partition** 끝에 이어 붙여요.
3. 배송 consumer group은 자기 위치부터 record를 읽고 배송을 준비해요.
4. 분석 consumer group도 같은 기록을 자기 위치부터 독립적으로 읽어요.
5. 각 group은 다음에 읽을 위치를 관리하므로 서로 속도가 달라도 돼요.

Kafka의 topic은 여러 partition으로 나뉠 수 있어요. 덕분에 기록과 읽기를 여러 갈래로 나눌 수 있지만, 순서를 말할 때는 범위를 조심해야 해요.

<Warning title="Kafka가 topic 전체의 순서를 자동으로 보장하는 것은 아니에요">
  Kafka가 기본적으로 보장하는 읽기 순서는 **같은 topic의 같은 partition 안**이에요. Partition이 다르면 어느 record가 전체에서 먼저였는지를 하나의 순서로 단정할 수 없어요.
</Warning>

Partition을 왜 나누는지, record가 어느 partition으로 가는지, offset이 정확히 무엇인지는 뒤의 구조 글에서 더 자세히 열어볼게요. 지금은 **topic 안에 이어지는 기록 줄이 있고, consumer group마다 읽는 위치가 따로 있다**는 그림이면 충분해요.

***

## 근데 왜 읽은 기록을 바로 지우지 않을까요?

기록이 남아 있으면 같은 사실을 여러 목적에 다시 사용할 수 있어요.

예를 들어 오전 10시에 `주문 42 결제 완료` record가 기록됐다고 해볼게요.

| 시점        | 읽는 팀  | 같은 기록으로 하는 일       |
| --------- | ----- | ------------------ |
| 오전 10시    | 배송팀   | 배송 준비를 시작해요        |
| 오전 10시 5분 | 알림팀   | 결제 완료 알림을 보내요      |
| 밤 12시     | 정산팀   | 하루 매출을 정산해요        |
| 다음 달      | 새 분석팀 | 지난 기록부터 새 지표를 계산해요 |

새 분석팀이 생겼다고 결제 서비스가 과거의 이벤트를 다시 만들어 보낼 필요가 없어요. 보관 중인 record가 있다면 분석팀의 읽기 위치를 앞쪽으로 옮겨 다시 읽을 수 있어요. 이런 동작을 흔히 **replay**라고 불러요.

하지만 “기록이 남는다”를 “영원히 남는다”로 이해하면 안 돼요.

Kafka는 topic의 **retention 정책**에 따라 record를 보관해요. 정해진 시간이나 크기 조건을 넘어 오래된 record가 제거될 수 있으므로, replay는 아직 보관 중인 범위에서만 가능해요.

<Tip title="Kafka의 첫 그림">
  **Producer는 기록을 남기고, Kafka는 partition에 이어 붙이고, consumer group은 자기 offset을 따라 읽어요.**
</Tip>

***

## 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으로 충분할 수 있어요.

반대로 이런 질문이 반복된다면 Kafka의 기록 모델을 검토할 이유가 생겨요.

* 같은 event를 여러 팀이 서로 다른 속도로 읽어야 하나요?
* Consumer가 잠시 멈춰도 나중에 밀린 기록을 따라가야 하나요?
* 새로운 consumer가 과거 event부터 처리해야 하나요?
* 큰 양의 연속된 event를 partition 단위로 나눠 처리해야 하나요?

도구 이름부터 고르기보다, **우리에게 작업 전달이 필요한지, 다시 읽을 수 있는 event history가 필요한지** 먼저 물어보는 편이 좋아요.

***

## 자, 정리해볼까요?

<Info title="오늘 우리가 배운 것">
  * 직접 호출은 단순하지만 후속 서비스가 늘어나면 보내는 쪽이 대상과 실패를 많이 알게 될 수 있어요.
  * 전형적인 작업 큐는 보내는 시간과 처리 시간을 분리하지만, Kafka는 여기에 **여러 독자의 독립적인 읽기와 기록 재사용**이라는 관점을 더해요.
  * Kafka에서 event는 topic의 partition에 record로 이어지고, 각 consumer group은 자기 읽기 위치를 따라가요.
  * Record는 읽었다는 이유만으로 바로 사라지지 않지만, retention 정책이 허용하는 범위에서만 보관되고 replay할 수 있어요.
  * Kafka의 순서 보장 범위는 topic 전체가 아니라 **같은 partition 안**이에요.
  * Kafka가 항상 정답은 아니며, 단순한 직접 호출이나 작업 큐가 문제에 더 잘 맞을 수도 있어요.
</Info>

***

## 다음 글에서는

이제 Kafka가 왜 기록을 중심에 두는지는 감이 왔어요. 그런데 이런 질문이 남죠?

> “그 기록을 직접 한 번 보내보고 읽어보면, 지금 본 그림이 실제로 어떻게 보일까요?”

다음 글에서는 local Kafka를 실행하고 topic에 첫 record를 보내는 흐름으로 이어갈게요. 글이 공개되면 이곳에 링크를 연결할게요.

***

## 더 읽어보기

* [Apache Kafka 소개와 핵심 용어](https://kafka.apache.org/documentation/#intro_concepts_and_terms)
* [Apache Kafka의 consumer position 설계](https://kafka.apache.org/documentation/#design_consumerposition)


## Related topics

- [Apache Kafka 소개와 핵심 용어](https://kafka.apache.org/documentation/#intro_concepts_and_terms)
- [Apache Kafka의 consumer position 설계](https://kafka.apache.org/documentation/#design_consumerposition)
