🚀 카프카(Kafka), 왜 필요할까요?
과거의 IT 시스템은 주로 하나의 거대한 애플리케이션인 모놀리식 아키텍처로 구성되었습니다. 하지만 서비스가 커지고 트래픽이 폭발적으로 증가하면서, 시스템을 여러 개의 작은 서비스로 나누는 마이크로서비스 아키텍처(MSA)가 주류로 자리 잡게 되었습니다.
문제는 서비스들이 쪼개지면서 서비스 간에 데이터를 주고받아야 할 일이 기하급수적으로 늘어났다는 것입니다.
결제 서비스가 완료되면 포인트 서비스, 알림 서비스, 배송 서비스 등 수많은 시스템에 데이터를 전달해야 합니다. 초기에는 이를 각 서비스가 직접 연결되어 데이터를 주고받는 Point-to-Point (End-to-End) 방식으로 해결했습니다.
하지만 시스템이 추가될 때마다 연결은 복잡해졌고, 하나의 시스템에 장애가 발생하면 연결된 다른 시스템까지 연쇄적으로 장애가 전파되는 끔찍한 상황이 발생했습니다. 이를 흔히 스파게티 네트워크라고 부릅니다.
이러한 문제를 해결하기 위해 링크드인(LinkedIn)의 개발자들은 새로운 비동기 메시징 시스템을 자체적으로 개발하게 되었습니다. 모든 시스템의 데이터 흐름을 중앙에서 안전하고 빠르게 처리해 줄 강력한 플랫폼, 그것이 바로 카프카의 탄생입니다.
카프카를 도입함으로써 각 서비스는 더 이상 다른 서비스가 살아있는지, 어디에 있는지 신경 쓸 필요 없이 오직 카프카로 데이터를 보내고 카프카에서 데이터를 가져오기만 하면 되는 구조를 갖추게 되었습니다.
💡 카프카를 처음 도입할 때 하는 실수
카프카의 강력함에 매료되어 많은 개발팀이 카프카를 도입하지만, 기존 시스템과 다른 패러다임 때문에 초반에 시행착오를 겪곤 합니다. 여러분이 같은 실수를 반복하지 않도록, 실무에서 빈번하게 발생하는 세 가지 오해를 짚고 넘어가겠습니다.
1. 기존의 메시지 큐(Message Queue)와 똑같이 동작할 것이라는 오해
전통적인 메시지 브로커인 래빗엠큐(RabbitMQ)나 액티브엠큐(ActiveMQ)에 익숙한 개발자들은, 데이터를 읽어가면 큐에서 데이터가 바로 삭제된다고 생각합니다. 하지만 카프카는 컨슈머가 데이터를 읽어가도 즉시 데이터를 삭제하지 않습니다.
지정된 보관 기간(Retention) 동안 데이터를 파일 시스템에 안전하게 보관합니다. 이 사실을 모르고 디스크 용량 설정을 작게 하거나, 데이터가 지워지지 않아 중복 처리될까 봐 불필요한 방어 로직을 짜는 경우가 많습니다.
2. 모든 데이터의 완벽한 순서 보장을 기대하는 것
카프카는 엄청난 처리량을 위해 토픽을 여러 개의 파티션으로 나누어 분산 처리합니다. 여기서 많은 분이 놓치는 핵심은 카프카의 데이터 순서는 오직 동일한 파티션 내에서만 보장된다는 것입니다.
토픽 전체의 단위로 순서가 보장된다고 착각하여 설계하면, 나중에 데이터가 뒤죽박죽으로 처리되는 동시성 이슈를 겪게 됩니다. 순서가 중요한 데이터는 반드시 같은 파티션에 할당되도록 메시지 키(Key)를 적절히 설정해야 합니다.
3. 컨슈머의 처리 속도를 고려하지 않은 무한정 발행
프로듀서는 데이터를 쏟아내는데, 컨슈머의 데이터 처리 로직이 너무 느리다면 어떻게 될까요?
카프카 내부에는 처리되지 못한 데이터가 쌓이게 되는 지연(Lag) 현상이 발생합니다. 무작정 카프카를 도입하기 전에, 컨슈머가 데이터를 소비하는 속도와 프로듀서가 데이터를 생산하는 속도의 균형을 맞추는 설계가 반드시 선행되어야 합니다.
필요하다면 컨슈머를 여러 대로 늘려 병렬 처리를 할 수 있도록 파티션 개수도 넉넉하게 설계해야 합니다.
🧠 핵심 : 카프카의 탄생과 핵심 구조 파악하기
이제 본격적으로 카프카를 지탱하는 4가지 핵심인 Pub/Sub 모델, 토픽, 프로듀서, 컨슈머에 대해 상세히 알아보겠습니다.
📡 1. Pub/Sub (발행/구독) 모델: 완벽한 결합도 분리
카프카는 기본적으로 Pub/Sub (Publish/Subscribe) 모델을 기반으로 작동합니다. 이를 가장 쉽게 이해하려면 유튜브 채널이나 라디오 방송국을 떠올려 보세요.
유튜브 크리에이터(발행자)는 구독자가 현재 영상을 볼 수 있는 상태인지, 네트워크가 연결되어 있는지 신경 쓰지 않습니다. 영상을 만들어 유튜브 플랫폼에 업로드(Publish)할 뿐입니다. 그러면 그 채널을 구독(Subscribe)하고 있는 수많은 시청자들은 각자 편한 시간에 유튜브에 접속하여 영상을 시청합니다.
카프카도 이와 완벽하게 동일합니다.
데이터를 생산하는 프로듀서와 데이터를 소비하는 컨슈머는 서로의 존재를 모릅니다. 둘 사이에는 오직 카프카라는 플랫폼만 존재할 뿐입니다.
프로듀서는 카프카에 데이터를 던져두고 자신의 역할을 끝내며, 컨슈머는 카프카에 접속해 자신이 필요한 데이터를 가져갑니다. 이렇게 발행자와 구독자가 직접 연결되지 않고 중간 매개체를 통해 비동기적으로 데이터를 주고받는 구조는 시스템 간의 결합도를 낮춰줍니다.
한쪽 시스템이 점검 중이거나 장애가 나더라도, 다른 시스템은 카프카를 통해 정상적으로 자신의 비즈니스 로직을 수행할 수 있는 무정지 시스템을 구축할 수 있게 해 줍니다.
📂 2. Topic (토픽): 데이터가 들어가는 논리적인 파이프/폴더
카프카에서 데이터가 최종적으로 도착하고 저장되는 곳을 토픽(Topic)이라고 부릅니다. 데이터베이스의 테이블(Table)이나 파일 시스템의 폴더(Folder)와 비슷한 개념이라고 생각하시면 이해가 쉽습니다.
수많은 데이터가 목적지 없이 섞여 있으면 안 되기 때문에, 결제 데이터는 결제 토픽에, 회원 가입 데이터는 회원 가입 토픽에 나누어 저장합니다. 카프카의 토픽은 아주 특별한 특징을 가지고 있습니다. 바로 파티션(Partition)이라는 개념을 통해 하나의 토픽을 여러 갈래로 쪼갤 수 있다는 것입니다.
고속도로의 톨게이트를 상상해보면 차가 몰려오는데 톨게이트가 하나뿐이라면 교통 체증이 발생할 것입니다. 토픽도 마찬가지로 수백만 건의 데이터가 쏟아질 때 하나의 파티션만 있다면 병목 현상이 생깁니다.
그래서 토픽을 여러 개의 파티션으로 나누어 데이터를 분산 저장하고, 여러 컨슈머가 동시에 데이터를 읽어갈 수 있도록 합니다.
또한, 파티션 내부에 저장된 데이터에는 각각 고유한 번호표가 부여되는데 이를 오프셋(Offset)이라고 부릅니다. 컨슈머는 이 오프셋 번호를 기억해 두었다가, 자신이 어디까지 데이터를 읽었는지 추적하는 용도로 사용합니다.
📤 3. Producer (프로듀서): 데이터를 생산하고 보내는 주체
프로듀서(Producer)는 애플리케이션에서 발생한 의미 있는 데이터(이벤트)를 카프카의 토픽으로 전송하는 역할을 담당합니다.
웹사이트에서 사용자가 클릭을 하거나, 상품을 장바구니에 담거나, 결제를 완료하는 등 모든 행위가 프로듀서가 전송할 수 있는 데이터가 됩니다. 프로듀서는 데이터를 보낼 때 단순히 텍스트만 보내는 것이 아니라, Key-Value(키-값) 형태로 데이터를 전송할 수 있습니다.
만약 메시지 키(Key)를 지정하지 않으면 데이터는 토픽의 여러 파티션에 라운드 로빈(순차적) 방식으로 분배됩니다. 하지만 앞서 시행착오에서 언급했듯 순서가 중요한 데이터라면 특정 키를 지정하여 항상 동일한 파티션으로 데이터가 들어가도록 유도해야 합니다.
실무에서 가장 많이 쓰이는 스프링 부트(Spring Boot) 환경에서 프로듀서가 데이터를 보내는 간단한 코드를 살펴보겠습니다.
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
@Service
public class OrderProducerService {
// 카프카로 메시지를 보내기 위해 스프링이 제공하는 템플릿 클래스입니다.
private final KafkaTemplate<String, String> kafkaTemplate;
public OrderProducerService(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
// 주문이 발생했을 때 호출되는 메서드
public void sendOrderCreateEvent(String orderId, String orderData) {
String topicName = "order-create-topic";
// 카프카의 특정 토픽으로 메시지 키(orderId)와 값(orderData)을 전송합니다.
// 키를 지정했으므로, 동일한 주문 ID는 항상 동일한 파티션에 순서대로 저장됩니다.
kafkaTemplate.send(topicName, orderId, orderData);
System.out.println("주문 데이터 전송 완료: " + orderId);
}
}
위 코드를 보면 프로듀서는 자신이 보낸 데이터가 이후에 어떻게 처리되는지, 어떤 컨슈머가 받아가는지 전혀 알지 못합니다. 그저 kafkaTemplate.send 메서드를 통해 지정된 토픽으로 데이터를 쏘아 올릴 뿐입니다. 이것이 바로 완벽한 역할 분리입니다.
📥 4. Consumer (컨슈머): 데이터를 가져와서 처리하는 주체
프로듀서가 데이터를 보냈다면 누군가는 그 데이터를 받아야겠죠. 카프카 토픽에 저장된 데이터를 읽어와서 실제 비즈니스 로직을 수행하는 주체가 바로 컨슈머(Consumer)입니다.
컨슈머의 가장 큰 특징은 폴링(Polling) 방식으로 동작한다는 점입니다.
카프카 브로커가 컨슈머에게 데이터를 밀어넣어 주는(Push) 것이 아니라, 컨슈머가 주도적으로 "새로운 데이터 있어?" 하고 카프카에게 물어보고 데이터를 당겨오는(Pull) 구조입니다. 덕분에 컨슈머는 자신의 서버 상태나 처리 능력에 맞게 데이터 소비 속도를 조절할 수 있습니다.
마찬가지로 스프링 부트 환경에서 컨슈머가 어떻게 데이터를 읽어오는지 코드 예제를 통해 확인해 보겠습니다.
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
@Service
public class NotificationConsumerService {
// KafkaListener 어노테이션을 통해 특정 토픽의 메시지를 지속적으로 수신합니다.
// groupId는 컨슈머 그룹을 지정하여 파티션 분배 로직을 활성화합니다.
@KafkaListener(topics = "order-create-topic", groupId = "notification-group")
public void consumeOrderEvent(String orderData) {
// 토픽에 새로운 데이터가 들어오면 이 메서드가 자동으로 실행됩니다.
System.out.println("주문 알림 발송을 위한 데이터 수신: " + orderData);
// 수신된 데이터를 바탕으로 고객에게 카카오톡이나 이메일 알림을 발송하는 로직을 구현합니다.
sendEmailNotification(orderData);
}
private void sendEmailNotification(String data) {
// 실제 이메일 발송 로직
}
}
스프링 프레임워크에서는 KafkaListener 어노테이션 하나만으로 복잡한 폴링 로직과 오프셋 관리 로직을 숨기고, 데이터를 수신할 수 있습니다.
여기서 한 가지 더 기억해야 할 중요 개념은 컨슈머 그룹(Consumer Group)입니다. 위 코드의 groupId가 그 역할을 합니다.
토픽의 파티션이 3개이고 컨슈머 그룹 내에 컨슈머 서버가 3대 있다면, 카프카는 각 파티션을 1대의 컨슈머에 1:1로 매핑해 줍니다. 이를 통해 대용량 데이터도 여러 대의 서버가 나누어서 병렬로 빠르게 처리할 수 있게 됩니다.
🎯 마무리: 오늘 내용의 '3줄 요약'
길고 자세한 설명이었지만, 오늘 배운 카프카의 본질을 딱 3가지로 압축해 보겠습니다.
- Pub/Sub 아키텍처: 카프카는 시스템 간의 직접적인 연결을 끊어내고, 중앙에서 메시지를 중계하여 마이크로서비스 간의 결합도를 낮추는 최고의 솔루션입니다.
- Topic의 분산 저장: 토픽은 데이터가 저장되는 논리적 공간이며, 파티션으로 쪼개져 분산 저장됨으로써 엄청난 속도의 병렬 처리를 가능하게 합니다.
- Producer와 Consumer의 독립성: 데이터를 생산하는 프로듀서와 데이터를 소비하는 컨슈머는 서로의 존재를 모른 채, 오직 카프카만을 바라보며 각자의 역할과 속도에 맞춰 비동기적으로 동작합니다.
'Kafka' 카테고리의 다른 글
| Kafka 데이터의 분산 처리 파티션과 브로커 (1) | 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 |
댓글