지난 시간에 카프카의 기본 구조인 Pub/Sub 모델과 프로듀서, 컨슈머에 대해 알아보았는데요. 오늘은 카프카가 어떻게 초당 수백만 건의 데이터를 끄떡없이 처리할 수 있는지, 그 원천인 데이터의 분산 처리에 대해 깊이 파헤쳐 보겠습니다.
단일 서버의 한계를 넘어 거대한 클러스터를 구성하는 브로커, 병렬 처리의 핵심인 파티션, 그리고 카프카의 두뇌 역할을 해온 주키퍼와 새로운 패러다임인 KRaft까지, 백엔드 개발자라면 반드시 알아야 할 핵심 아키텍처를 정리해 드리겠습니다.
🚀 왜 우리는 분산 처리와 카프카 클러스터에 열광하는가?
웹 서비스가 성장하면서 하루에 발생하는 로그, 결제 이벤트, 사용자 클릭 데이터는 상상을 초월할 정도로 방대해집니다. 초창기에는 서버 한 대의 성능을 영혼까지 끌어올리는 스케일 업(Scale-up) 방식으로 버틸 수 있었지만, 어느 순간 물리적인 한계에 부딪혀 비싼 CPU와 메모리를 꽂아도 트래픽을 감당할 수 없는 순간이 오죠.
카프카는 애초에 이런 문제를 해결하기 위해 스케일 아웃(Scale-out), 즉 여러 대의 서버를 연결하여 하나의 거대한 시스템처럼 동작하게 만드는 분산 아키텍처를 염두에 두고 설계되었습니다. 단일 장비의 장애가 전체 서비스의 중단으로 이어지는 문제를 완벽하게 극복하고, 데이터의 양이 늘어나면 단순히 서버(브로커)를 추가하는 것만으로 처리 용량을 무한히 확장할 수 있는 환경을 제공합니다.
결국 우리가 카프카의 분산 구조를 배워야 하는 이유는, 어떤 폭발적인 트래픽 앞에서도 데이터를 안전하게 보관하고 지연 없이 처리할 수 있는 강력하고 견고한 백엔드 시스템을 구축하기 위함입니다.
🤦♂️ 분산 환경을 이해하지 못해 발생하는 대참사
분산 시스템은 강력하지만, 그만큼 복잡합니다. 카프카를 실무에 도입하면서 브로커와 파티션의 특성을 정확히 이해하지 못해 겪게 되는 시행착오 세 가지를 소개합니다.
1. 파티션 개수를 나중에 줄일 수 있다고 착각하는 것
가장 많이 하는 실수 중 하나입니다. 트래픽이 얼마나 될지 몰라 일단 파티션 개수를 100개로 넉넉하게 설정해 놓고 운영을 시작합니다.
그런데 생각보다 트래픽이 적어서 파티션을 다시 10개로 줄이려고 시도하는 순간, 멘붕에 빠지게 됩니다. 카프카에서 토픽의 파티션 개수는 늘릴 수는 있지만 절대 줄일 수는 없습니다.
파티션을 줄이려면 토픽을 아예 삭제하고 새로 만들어야 하므로, 초반에는 보수적으로 파티션 개수를 적게 설정하고 트래픽 증가 추이를 보면서 점진적으로 늘려나가는 것이 올바른 전략입니다.
2. 오프셋 자동 커밋(Auto Commit)을 맹신하여 발생하는 데이터 유실
컨슈머가 데이터를 읽어갈 때, 카프카는 내부적으로 어디까지 읽었는지 기록하는 오프셋을 관리합니다.
많은 개발자가 편의성을 위해 기본 설정인 자동 커밋을 그대로 사용하지만 자동 커밋은 일정 주기마다 무조건 오프셋을 기록하기 때문에, 컨슈머가 데이터를 읽어와서 DB에 저장하기도 전에 에러가 발생해서 뻗어버려도 카프카는 "아, 얘가 여기까지 잘 읽었구나" 하고 오프셋을 올려버립니다.
결국 그 데이터는 영영 처리되지 못하고 유실되는 대참사가 발생합니다. 중요한 비즈니스 로직이라면 반드시 자동 커밋을 끄고, 데이터 처리가 완전히 끝난 후 수동으로 오프셋을 커밋하는 방식(Manual Commit)을 구현해야 합니다.
3. 레플리케이션(Replication) 팩터를 무시한 설정
브로커를 3대 띄워놓고 클러스터를 구성했다고 안심하는 경우가 있습니다.
하지만 토픽을 생성할 때 복제 계수(Replication Factor)를 1로 설정해버리면, 데이터는 오직 한 대의 브로커에만 저장됩니다. 만약 그 브로커 서버의 하드디스크가 고장 난다면 복제본이 없으므로 데이터는 영원히 날아갑니다.
고가용성을 위해서는 반드시 복제 계수를 2 이상(보통 브로커 3대 환경에서 3으로 설정)으로 주어, 한 브로커가 죽더라도 다른 브로커의 복제본을 통해 서비스가 중단 없이 이어지도록 설계해야 합니다.
🧠 핵심 : 카프카 클러스터와 분산 처리의 구조 해부하기
본격적으로 카프카의 분산 처리를 담당하는 핵심 요소들을 하나씩 살펴보겠습니다.
🏢 1. Broker (브로커)와 Cluster (클러스터): 거대한 물류 센터
브로커(Broker)는 카프카 애플리케이션이 설치되어 실행되고 있는 하나의 서버(노드)를 의미합니다. 프로듀서가 보낸 데이터를 파일 시스템에 안전하게 기록하고, 컨슈머가 데이터를 요청하면 저장된 데이터를 꺼내서 전달해 주는 핵심 일꾼입니다.
하지만 실무 환경에서는 브로커 한 대만으로 운영하는 경우는 절대 없습니다. 최소 3대 이상의 브로커를 하나로 묶어서 운영하는데, 이렇게 여러 대의 브로커가 모여 하나의 시스템처럼 동작하는 묶음을 클러스터(Cluster)라고 부릅니다.
클러스터로 묶인 브로커들은 데이터를 분산해서 나누어 가지고, 앞서 시행착오에서 언급한 복제(Replication) 기능을 통해 서로의 데이터를 백업해 줍니다.
예를 들어 A 브로커에 원본 데이터(Leader)가 저장되면, B와 C 브로커에는 그 데이터의 복사본(Follower)이 실시간으로 저장됩니다. 따라서 A 브로커에 불이 나서 서버가 완전히 타버리더라도, 카프카 클러스터는 즉시 B나 C 브로커가 가진 복사본을 리더로 승격시켜 단 1초의 서비스 장애도 없이 데이터를 안전하게 지켜냅니다.
🛣️ 2. Partition (파티션)과 Offset (오프셋): 무한한 확장성과 데이터의 이정표
이전 포스팅에서 토픽은 데이터를 구분하는 폴더 같은 개념이라고 말씀드렸습니다. 이 토픽 내부를 들여다보면, 데이터는 다시 파티션(Partition)이라는 단위로 쪼개져서 저장됩니다.
왜 하나의 토픽에 그냥 저장하지 않고 굳이 파티션으로 나눌까요? 바로 병렬 처리 때문입니다. 고속도로의 톨게이트를 생각해 보면 차가 10만 대가 몰려오는데 요금소가 딱 1개(파티션 1개)라면 차들은 끝없이 줄을 서야 합니다. 하지만 요금소를 10개(파티션 10개)로 늘리면 10대의 차가 동시에 요금을 내고 통과할 수 있습니다.
카프카도 마찬가지입니다. 파티션이 1개라면 프로듀서가 아무리 빨리 데이터를 보내도, 데이터를 처리하는 컨슈머는 1개밖에 붙을 수 없습니다. 하지만 파티션을 10개로 늘리면, 컨슈머 서버 10대를 동시에 띄워서 10배 빠른 속도로 데이터를 처리할 수 있습니다. 이것이 카프카의 압도적인 처리량의 비밀입니다.
그리고 각 파티션 내부에는 데이터가 들어온 순서대로 0, 1, 2, 3... 번호표가 차례대로 붙는데, 이 고유한 번호를 오프셋(Offset)이라고 합니다.
컨슈머는 데이터를 읽을 때마다 카프카에게 "나 지금 3번 파티션의 45번 오프셋까지 처리 완료했어"라고 보고합니다. 만약 컨슈머 서버가 재부팅되더라도, 카프카에 기록된 이 오프셋 정보를 보고 "아, 46번부터 다시 읽으면 되겠구나" 하고 정확하게 다음 데이터부터 처리를 재개할 수 있습니다.
수동 오프셋 커밋(Manual Commit)을 구현하는 자바 스프링 부트(Spring Boot) 컨슈머 코드를 살펴보겠습니다.
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Service;
@Service
public class PaymentConsumerService {
// 컨슈머 리스너 설정. 결제 토픽에서 데이터를 가져옵니다.
// application.yml에 ack-mode: manual_immediate 설정이 필요합니다.
@KafkaListener(topics = "payment-topic", groupId = "payment-processing-group")
public void consumePaymentEvent(ConsumerRecord<String, String> record, Acknowledgment acknowledgment) {
try {
System.out.println("수신된 파티션: " + record.partition() + ", 오프셋: " + record.offset());
String paymentData = record.value();
// 1. 비즈니스 로직 수행 (예: DB에 결제 내역 저장)
processPaymentUpdate(paymentData);
// 2. 비즈니스 로직이 완벽하게 성공했을 때만 수동으로 오프셋을 커밋합니다.
// 이 코드가 실행되어야만 카프카 브로커에 "여기까지 완벽히 처리함" 이라고 기록됩니다.
acknowledgment.acknowledge();
System.out.println("결제 데이터 처리 성공 및 커밋 완료");
} catch (Exception e) {
// 에러가 발생하면 acknowledge()가 호출되지 않으므로 오프셋이 올라가지 않습니다.
// 컨슈머가 재시작되거나 롤백 처리 후 동일한 데이터를 다시 읽어올 수 있습니다.
System.err.println("결제 데이터 처리 중 에러 발생, 커밋 생략");
}
}
private void processPaymentUpdate(String data) throws Exception {
// 실제 데이터베이스 저장 로직이 들어갑니다.
// 여기서 예외가 발생하면 catch 블록으로 빠집니다.
}
}
위 코드를 통해 우리는 에러가 나더라도 데이터가 유실되지 않도록 완벽하게 오프셋을 통제할 수 있습니다.
👑 3. Zookeeper(주키퍼)의 시대에서 KRaft(크래프트)의 시대로
지금까지 설명한 브로커 클러스터가 제대로 돌아가려면 누군가는 전체 브로커의 상태를 감시하고, 어떤 브로커가 살아있는지, 파티션의 리더는 누구인지 메타데이터를 꼼꼼하게 관리해야 합니다. 수년 동안 이 역할은 카프카 외부에 존재하는 별도의 애플리케이션인 주키퍼(Zookeeper)가 맡아왔습니다.
즉, 카프카 클러스터를 띄우려면 반드시 주키퍼 클러스터도 같이 띄워야만 했습니다. 서버 관리자 입장에서는 관리해야 할 시스템이 두 배로 늘어나는 셈이었죠. 게다가 파티션 개수가 수백만 개로 늘어나면 주키퍼와 카프카 브로커 사이의 통신 병목 현상이 발생하여 성능이 저하되는 치명적인 문제도 있었습니다.
이러한 주키퍼의 의존성을 완전히 끊어내기 위해 탄생한 것이 바로 KRaft (Kafka Raft Metadata mode) 입니다.
KRaft 모드에서는 더 이상 외부에 주키퍼를 설치할 필요가 없습니다. 카프카 브로커들 내부에서 자체적으로 투표를 통해 관리자(컨트롤러) 역할을 할 브로커를 선출하고, 자기들끼리 메타데이터를 합의하고 공유하는 Raft 합의 알고리즘을 사용하게 됩니다.
KRaft의 도입으로 카프카는 외부 시스템에 의존하지 않는 완벽한 독립적인 아키텍처를 완성했습니다.
수백만 개의 파티션을 생성해도 메타데이터 처리 속도가 획기적으로 개선되었으며, 클러스터 구축과 운영 관리가 비교할 수 없을 정도로 단순해졌습니다.
카프카 3.3 버전부터 KRaft가 운영 환경에 사용 가능해졌고, 4.0 버전부터는 주키퍼가 완전히 삭제될 예정이므로, 앞으로 새롭게 구축하는 시스템은 반드시 주키퍼가 아닌 KRaft 기반으로 설계해야 합니다.
🎯 오늘 내용 3줄 요약
복잡한 분산 시스템의 원리를 살펴보았는데요, 핵심만 딱 3줄로 정리해 보겠습니다.
- 고가용성의 핵심, 브로커 클러스터: 여러 대의 브로커가 클러스터를 이루고 데이터를 서로 복제함으로써, 서버 장애가 발생해도 서비스 중단 없이 데이터를 안전하게 보호합니다.
- 압도적 성능의 비밀, 파티션과 오프셋: 토픽을 파티션으로 잘게 쪼개어 다수의 컨슈머가 동시에 병렬로 데이터를 처리하며, 오프셋을 통해 데이터의 처리 상태를 정확하게 추적합니다.
- 독립적인 아키텍처의 완성, KRaft: 무겁고 복잡했던 주키퍼를 떼어내고, 카프카 스스로 메타데이터를 관리하는 KRaft 시대로 넘어가면서 성능과 관리 편의성이 상승했습니다.
'Kafka' 카테고리의 다른 글
| 카프카(Kafka) 핵심구조 Pub/Sub, 컨슈머, 프로듀서, 토픽 (0) | 2026.03.04 |
|---|---|
| [Kafka] 오프셋 커밋 동기 비동기, 리밸런스 리스너 (2) | 2022.01.24 |
| [Kafka] 리밸런싱 오프셋 커밋 subscribe poll 개념 설명 (0) | 2022.01.21 |
| Kafka Producer Client 주요 옵션 설명과 메시지 키/특정 파티션/동기-비동기 전송 (0) | 2021.12.02 |
| [Kafka] 카프카의 특징과 내부구조 (브로커, 프로듀서, 컨슈머, 파티션) (0) | 2021.10.27 |
댓글