2. Kafka 핵심 구성요소: 브로커, 토픽, 파티션, 오프셋

핵심 요약: 카프카는 여러 대의 브로커(서버)로 구성된 클러스터 위에서 동작합니다. 데이터는 토픽(주제)별로 분류되고, 각 토픽은 병렬 처리를 위해 여러 개의 파티션(조각)으로 나뉩니다. 파티션 내의 각 메시지는 오프셋(순번)이라는 고유한 번호를 가집니다.

1. 카프카 시스템의 전체 구조: 클러스터와 브로커

가장 큰 그림부터 보겠습니다. 카프카는 단일 서버로 동작하기보다는, 여러 대의 서버(컴퓨터)를 하나로 묶어 거대한 단일 시스템처럼 동작시키는 것을 전제로 설계되었습니다.

카프카 클러스터 (하나의 팀)
💻
브로커 1
(서버 1)
+
💻
브로커 2
(서버 2)
+
💻
브로커 3
(서버 3)

여러 대의 브로커(서버)를 클러스터로 묶어 하나의 시스템처럼 동작하게 합니다.
만약 브로커 1에 장애가 발생해도, 브로커 2, 3이 계속 서비스를 이어가 고가용성(High Availability)을 확보합니다. 보통 3대 이상의 홀수로 구성하는데, 이는 클러스터 내에서 의사결정을 할 때 과반수 투표와 같은 합의 알고리즘을 안정적으로 사용하기 위함입니다.

2. 클러스터의 두뇌: 주키퍼(Zookeeper)와 KRaft 모드

여러 브로커가 한 팀처럼 움직이려면, 누군가는 "어떤 브로커가 살아있는지", "각 토픽의 리더는 누구인지"와 같은 중요한 정보(메타데이터)를 총괄 관리하고 조정하는 '반장' 역할을 해야 합니다. 이 역할을 하는 것이 바로 '코디네이터'이며, 과거에는 '주키퍼'가, 현재는 'KRaft'가 그 역할을 수행합니다.

Before: 주키퍼 (Zookeeper) - "외부에서 온 반장"

과거 카프카는 클러스터의 메타데이터 관리와 리더 선출 등 핵심적인 조정 작업을 위해 '주키퍼'라는 별도의 분산 코디네이션 시스템에 의존했습니다. 주키퍼가 수행하는 주요 역할은 다음과 같습니다.

카프카 클러스터

Broker 1
Broker 2
Broker 3
의존

주키퍼 클러스터

ZK 1
ZK 2
ZK 3

단점 (Pain Point): 카프카를 운영하려면 주키퍼 클러스터도 따로 설치하고 관리해야 합니다. 관리 포인트가 두 배가 되는 셈이라 아키텍처가 복잡하고 운영 부담이 컸습니다.


After: KRaft (Kafka Raft) - "내부에서 뽑은 반장"

최신 카프카는 Raft 합의 프로토콜을 내장하여, 주키퍼 없이 자체적으로 클러스터를 관리할 수 있는 KRaft 모드를 지원합니다. 이 모드에서는 브로커 중 일부가 '컨트롤러' 역할을 맡아 주키퍼가 하던 모든 일을 직접 처리합니다.

카프카 클러스터 (KRaft 모드)

Controller
Broker
Broker

결론: KRaft 모드는 운영 복잡성을 줄이고 성능을 향상시키므로, 현재 새로 카프카를 도입한다면 KRaft 모드를 사용하는 것이 표준으로 자리 잡고 있습니다.

3. 애플리케이션 관점에서 본 카프카 클러스터

그렇다면 개발자, 즉 애플리케이션의 입장에서 이 복잡한 클러스터는 어떻게 보일까요? 다행히도 우리는 클러스터 내부의 모든 브로커를 일일이 알 필요 없이, '대표 연락처'에 해당하는 bootstrap.servers 목록만으로 전체 클러스터와 통신을 시작할 수 있습니다.

주문 서비스 (Producer)

bootstrap.servers="b1,b2,b3"
초기 연결
직접 통신

카프카 클러스터

Broker 1
Broker 2 (Leader)
Broker 3
  1. 초기 연결 (Bootstrap): 애플리케이션은 bootstrap.servers 목록에 있는 브로커 중 하나에 접속하여 클러스터 전체의 메타데이터(e.g., 'order' 토픽의 파티션 리더는 'Broker 2'라는 정보)를 요청합니다.
  2. 직접 통신: 메타데이터를 획득한 후, 프로듀서는 'Broker 2'에게 직접 메시지를 전송하고, 컨슈머도 'Broker 2'로부터 직접 메시지를 읽어옵니다. 중간 단계를 거치지 않아 매우 효율적입니다.

3. 데이터의 논리적 구조: 토픽, 파티션, 오프셋

카프카 클러스터라는 거대한 시스템 위에서, 우리가 다루는 데이터는 그냥 뒤죽박죽 저장되는 것이 아니라, 명확한 계층 구조를 가지고 체계적으로 관리됩니다. 이 구조를 이해하는 것이 카프카를 제대로 사용하는 첫걸음입니다.

토픽: '주문' (order)
카프카 클러스터
파티션 0
Offset 0: 주문A
Offset 1: 주문D
...
파티션 1
Offset 0: 주문B
Offset 1: 주문E
...
파티션 2
Offset 0: 주문C
Offset 1: 주문F
...
※ 위 그림에서 '주문' 토픽은 3개의 파티션으로 구성되어 있으며, 각 주문 메시지는 키(Key) 값 등에 따라 적절한 파티션에 분배되어 저장됩니다.

5. 비유: '대형 도서관'으로 이해하기

여러 독자(컨슈머)가 동시에 다른 권(파티션)을 읽을 수 있으므로, 전집을 훨씬 빨리 읽을 수 있습니다. 이것이 카프카의 병렬 처리 원리입니다.

5. 실무 관점: 메시지 한 개가 저장되기까지의 여정

프로듀서 코드에서 send() 메소드를 호출하는 단 한 줄의 코드 이면에서는, 데이터의 안정성과 효율성을 보장하기 위한 정교한 통신 과정이 일어납니다. 이 흐름을 이해하면 카프카의 동작 방식을 깊이 있게 파악할 수 있습니다.

프로듀서
1. 메타데이터 요청 ("'order' 토픽 리더 누구야?")
카프카 클러스터
프로듀서
2. 메타데이터 응답 ("파티션1 리더는 브로커2야")
카프카 클러스터
프로듀서
3. 메시지 전송 (브로커2에게 직접 전송)
브로커 2 (리더)
브로커 2 (리더)
4. 데이터 복제 요청
브로커 1, 3 (팔로워)
브로커 2 (리더)
5. 복제 완료 보고
브로커 1, 3 (팔로워)
프로듀서
6. 최종 성공 응답 (Ack)
브로커 2 (리더)

이러한 복제 과정 덕분에 리더 브로커에 장애가 발생해도 팔로워에 데이터가 남아있어 데이터 유실을 방지할 수 있습니다.

7. 실무 관점: 파티션 개수 정하기 (심층 분석)

면접 질문: "토픽의 파티션 개수는 어떻게 정하는 것이 좋을까요?"

💡 모범 답안 예시

"토픽의 파티션 개수는 예상 처리량(Throughput)과 컨슈머의 병렬 처리 수준을 핵심적으로 고려하여 결정해야 합니다.

먼저, 파티션은 한번 늘리면 메시지 키의 순서 보장 문제 때문에 줄일 수 없으므로, 처음에는 보수적으로 접근하는 것이 안전합니다. 예를 들어, 클러스터의 브로커 수와 비슷하게 설정하거나, 예상되는 최대 컨슈머 수에 맞춰 설정하고, 이후 모니터링을 통해 처리량이 부족하다고 판단될 때 점진적으로 늘려나가는 것이 좋습니다.

또한, 하나의 파티션은 한 컨슈머 그룹 내에서 단 하나의 컨슈머만 소비할 수 있으므로, 파티션의 개수가 곧 해당 토픽을 처리할 수 있는 최대 병렬 처리의 한계가 됩니다. 따라서 미래에 컨슈머를 스케일 아웃할 가능성까지 고려하여 초기 파티션 수를 설계해야 합니다."

위와 같이 대답하려면 아래의 몇 가지 핵심 원리를 이해해야 합니다.

A. "브로커당 1~2개"의 의미

이는 하나의 토픽에 대한 파티션을 의미합니다. 예를 들어, 3대의 브로커로 구성된 클러스터에 'order' 토픽을 생성할 때 파티션을 3개로 만들면, 카프카는 각 브로커에 파티션을 하나씩 분산시켜(e.g., P0→B1, P1→B2, P2→B3) 부하를 균등하게 만듭니다. 이것이 '브로커당 파티션 1개' 상태입니다. "작게 시작하라"는 것은 처음부터 불필요하게 많은 파티션을 만들지 말라는 의미입니다.

B. 왜 파티션은 늘릴 수만 있고, 줄일 수는 없을까?

결론부터 말하면 메시지 키(Key)의 순서 보장 때문입니다. 카프카는 동일한 키를 가진 메시지가 항상 동일한 파티션에 저장되도록 보장합니다. 이때 사용하는 공식이 hash(key) % numPartitions 입니다.

이렇게 되면 동일한 키의 메시지들이 여러 파티션에 흩어져 순서가 완전히 꼬이게 됩니다. 이 데이터 정합성 문제를 막기 위해 카프카는 파티션 수를 줄이는 것을 원천적으로 금지합니다.

C. 컨슈머 개수와의 관계

하나의 파티션은 컨슈머 그룹 내에서 오직 하나의 컨슈머만 점유할 수 있습니다. 만약 파티션이 3개인데 컨슈머를 5개 띄우면, 3개의 컨슈머만 파티션에 할당되고 나머지 2개는 아무 일도 하지 않고 놀게 됩니다. 따라서 최대 병렬 처리량은 파티션 개수에 의해 제한됩니다.

8. 미리보는 심화 개념: DLT와 데이터 일관성

'Dead Letter'나 'Read Repair'와 같은 개념은 심화 과정에서 자세히 다룰 내용입니다. 하지만 현재 단계에서 카프카가 이러한 문제들을 어떻게 해결하는지 미리 알아두면 전체적인 그림을 이해하는 데 큰 도움이 됩니다.

Dead Letter Topic (DLT) - "처리 못하는 메시지는 어떡하죠?"

컨슈머가 특정 메시지를 계속해서 처리하지 못하고 재시도만 반복하는 상황이 발생하면, 전체 시스템의 지연을 유발할 수 있습니다. 이러한 '독이 든 메시지(Poison Pill)'를 처리하기 위한 패턴이 바로 Dead Letter Topic (DLT)입니다.

※ 이 내용은 [4. 컨슈머 심화], [6. 운영과 트러블슈팅]에서 실제로 구현하는 방법을 자세히 다룰 예정입니다.

Read Repair vs. Kafka의 데이터 일관성 모델 - "복제된 데이터가 서로 다르면 어떡하죠?"

여러 서버에 데이터를 복제할 때 발생할 수 있는 데이터 불일치 문제를 해결하기 위해 'Read Repair'와 같은 방식이 사용되지만, 카프카는 더 효율적인 접근 방식을 사용합니다.

결론적으로, 컨슈머는 항상 일관성이 보장된 리더로부터 데이터를 읽기 때문에, 읽기 시점에 데이터를 비교하고 수정할 필요가 없습니다.

※ 이 내용은 [5. 브로커와 복제]에서 ISR과 리더 선출을 학습하면 완벽하게 이해할 수 있습니다.