🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Apache Kafka 소개 및 특징 - 슬라이드 1/7
A

AI Generated

2026. 4. 9. · 0 Views

Apache Kafka 소개 및 특징

Apache Kafka의 탄생 배경부터 핵심 설계 철학, 다른 메시지 브로커와의 비교, 높은 처리량의 원리, 생태계 구성 요소, 실제 사용 사례까지 종합적으로 다룹니다. 이벤트 스트리밍 플랫폼으로서 카프카가 왜 업계 표준이 되었는지 알아봅니다.


목차

  1. LinkedIn에서의 탄생 배경
  2. 카프카의 핵심 설계 철학
  3. RabbitMQ ActiveMQ와의 비교
  4. 높은 처리량과 낮은 지연시간 원리
  5. 카프카 생태계 구성 요소
  6. 카프카 사용 사례

1. LinkedIn에서의 탄생 배경

김개발 씨는 팀 미팅에서 새로운 메시징 시스템 도입 논의를 듣고 있었습니다. "우리 서비스 트래픽이 급증하는데, 기존 시스템으로는 감당이 안 됩니다." 박시니어 씨의 목소리에 긴장감이 감돌았습니다.

Apache Kafka는 2011년 LinkedIn에서 시작된 오픈소스 이벤트 스트리밍 플랫폼입니다. 마치 우체국이 수많은 편지를 분류하고 배달하는 것처럼, 대규모 데이터를 실시간으로 처리하고 전달합니다.

현재는 Netflix, Uber, Airbnb 등 전 세계 수천 개 기업에서 핵심 인프라로 사용하고 있습니다.

다음 코드를 살펴봅시다.

// Kafka의 탄생 배경을 이해하는 간단한 메시지 전송 예시
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 프로듀서 생성 - 카프카의 핵심 진입점
Producer<String, String> producer = new KafkaProducer<>(props);
// 토픽에 메시지 전송 - fire-and-forget 방식
producer.send(new ProducerRecord<>("user-activities", "key1",
    "{\"userId\": 1001, \"action\": \"login\"}"));
producer.close();

"Apache Kafka 완전 정복" 코스에 오신 것을 환영합니다. 지난 1화에서는 메시지 큐의 기본 개념과 이벤트 기반 아키텍처의 필요성을 살펴보았습니다.

이제 본격적으로 이 코스의 주인공, Apache Kafka를 만나볼 차례입니다. 김개발 씨의 팀에서도 비슷한 고민이 시작되었습니다.

서비스 사용자가 10만 명을 돌파하면서, 기존의 데이터 파이프라인이 버거워지기 시작한 것입니다. 로그도 처리해야 하고, 사용자 활동도 추적해야 하고, 실시간 추천도 제공해야 합니다.

모든 것을 동시에 해결할 방법이 필요했습니다. 바로 이 지점에서 Kafka의 이야기가 시작됩니다.

2010년경 LinkedIn은 직면한 비슷한 문제를 해결하고자 세 명의 엔지니어를 투입했습니다. 준 라오(Jay Kreps), 네흐 나크하르(Neha Narkhede), 그리고 **정하오 준(Jun Rao)**이 그 주인공입니다.

이들은 당시 LinkedIn에서 가장 심각한 문제가 무엇인지 정확히 진단했습니다. 수많은 시스템 간에 데이터를 이동시켜야 하는데, 기존 방식으로는 확장성내구성을 동시에 만족시킬 수 없었습니다.

MySQL에서 데이터를 읽어 다른 시스템으로 전달하는 배치 작업은 너무 느렸고, 기존 메시지 큐는 데이터양이 늘어나면 성능이 급격히 떨어졌습니다. 그래서 이들은 완전히 새로운 접근 방식을 시도했습니다.

"메시지를 단순히 전달하는 것이 아니라, 스트림으로 처리하자." 이 발상의 전환이 Kafka를 탄생시켰습니다. 이름은 프란츠 카프카(Franz Kafka)에서 따왔습니다.

빠르고 확장 가능한 시스템이라는 의미를 담은 이름이었죠. 2011년 오픈소스로 공개된 Kafka는 2012년 **Apache 최상위 프로젝트(Top-Level Project)**로 승격되었습니다.

이는 오픈소스 세계에서 인정받았다는 의미입니다. 이후 급속도로 성장하여, 현재는 이벤트 스트리밍 분야의 사실상 표준이 되었습니다.

코드를 살펴보면 Kafka 사용의 기본 구조를 알 수 있습니다. Properties 객체에 브로커 주소와 직렬화 방식을 설정하고, KafkaProducer를 생성하여 메시지를 전송합니다.

이 간결한 API가 Kafka의 장점 중 하나입니다. 복잡한 설정 없이도 몇 줄의 코드로 메시지를 생산할 수 있습니다.

위 코드에서 user-activities라는 토픽에 JSON 형태의 메시지를 전송하고 있습니다. 이는 실무에서 매우 흔한 패턴입니다.

사용자의 로그인, 클릭, 구매 등 모든 활동을 이벤트로 기록하는 것이죠. Kafka는 이런 이벤트 로그를 처리하는 데 특히 뛰어난 성능을 발휘합니다.

주의할 점이 있습니다. Kafka를 단순한 메시지 큐로만 생각하면 안 됩니다.

물론 메시지를 전달하는 기능도 있지만, Kafka의 본질은 분산 이벤트 로그 시스템입니다. 이 구별이 중요한 이유는, 설계 방식과 사용 패턴이 완전히 다르기 때문입니다.

김개발 씨는 박시니어 씨의 설명을 들으며 고개를 끄덕였습니다. "단순한 큐가 아니라 이벤트 스트리밍 플랫폼이라니, 생각보다 훨씬 큰 개념이네요." 맞습니다.

Kafka를 제대로 이해하려면 이 근본적인 차이부터 인지하는 것이 중요합니다. 다음 장에서는 이런 철학이 Kafka의 설계에 어떻게 반영되었는지 살펴보겠습니다.

실전 팁

💡 - Kafka는 단순한 메시지 큐가 아니라 분산 이벤트 로그 시스템입니다

  • LinkedIn에서 탄생했으며, 2012년 Apache 최상위 프로젝트로 승격되었습니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 2/15편입니다

2. 카프카의 핵심 설계 철학

박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다. "Kafka를 이해하려면 이 세 가지 철학을 알아야 합니다." 김개발 씨는 필기준을 꺼내며 집중하기 시작했습니다.

카프카의 설계는 단순함(Simplicity), 확장성(Scalability), **내구성(Durability)**이라는 세 가지 철학에 기반합니다. 마치 레고 블록처럼 각 구성 요소를 독립적으로 확장할 수 있도록 설계되었습니다.

이 철학 덕분에 Kafka는 수백만 개의 메시지를 안정적으로 처리할 수 있습니다.

다음 코드를 살펴봅시다.

// Kafka의 핵심 개념: 토픽, 파티션, 오프셋
// 토픽 생성 시 파티션 수와 복제 팩터 지정
Properties adminProps = new Properties();
adminProps.put("bootstrap.servers", "localhost:9092");
AdminClient admin = AdminClient.create(adminProps);
// 3개의 파티션, 복제 팩터 2로 토픽 생성
NewTopic topic = new NewTopic("order-events", 3, (short) 2);
admin.createTopics(Collections.singleton(topic));
// 컨슈머가 특정 파티션에서 메시지를 읽을 때 오프셋 관리
props.put("group.id", "order-processor-group");
props.put("auto.offset.reset", "earliest");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("order-events"));

박시니어 씨가 화이트보드에 크게 세 가지를 적었습니다. 단순함, 확장성, 내구성.

이 세 단어가 Kafka의 전부라고 해도 과언이 아닙니다. 첫 번째 철학인 단순함부터 살펴봅시다.

여기서 말하는 단순함은 기능이 적다는 의미가 아닙니다. 오히려 핵심에 집중한다는 뜻입니다.

Kafka는 메시지를 받고, 저장하고, 전달하는 것에만 집중합니다. 메시지 변환, 라우팅 규칙 같은 복잡한 기능은 외부 시스템에 맡깁니다.

마치 택배 회사가 물건을 배달하는 데만 집중하고, 물건의 내용물에는 관여하지 않는 것과 같습니다. 두 번째 철학은 확장성입니다.

코드를 보면 NewTopic("order-events", 3, (short) 2)라는 부분이 있습니다. 앞의 숫자 3은 **파티션(Partition)**의 수입니다.

파티션이란 토픽을 여러 조각으로 나눈 것을 말합니다. 쉽게 비유하자면, 하나의 대형 파이프라인 대신 여러 개의 작은 파이프라인을 병렬로铺设하는 것과 같습니다.

데이터 처리량이 늘어나면 파티션을 추가하기만 하면 됩니다. 세 번째 철학은 내구성입니다.

코드에서 (short) 2는 **복제 팩터(Replication Factor)**를 의미합니다. 즉, 각 파티션의 데이터를 2개의 브로커에 복사하여 저장하겠다는 뜻입니다.

마치 중요한 문서를 원본과 사본 두 곳에 보관하는 것과 같습니다. 한쪽 서버가 고장 나도 다른 서버에서 데이터를 복구할 수 있습니다.

이 세 가지 철학은 Kafka의 핵심 데이터 구조인 토픽(Topic), 파티션(Partition), **오프셋(Offset)**으로 구현됩니다. 토픽은 메시지를 분류하는 카테고리입니다.

파티션은 토픽 내의 순차적인 로그 조각입니다. 오프셋은 파티션 내에서 각 메시지의 고유 번호입니다.

컨슈머 코드를 보면 group.idauto.offset.reset 설정이 눈에 띕니다. group.id컨슈머 그룹을 식별하는 이름입니다.

같은 그룹의 컨슈머들은 파티션을 분할하여 소비합니다. auto.offset.resetearliest로 설정하면, 새로운 컨슈머가 토픽에 처음 참여할 때 가장 오래된 메시지부터 읽기 시작합니다.

이런 설계 덕분에 Kafka는 놀라운 유연성을 제공합니다. 하나의 토픽에 여러 컨슈머 그룹이 독립적으로 구독할 수 있습니다.

마치 하나의 신문사에서 여러 구독자가 각자의 속도로 신문을 읽는 것과 같습니다. 구독자 A는 빠르게 읽고, 구독자 B는 천천히 읽어도 서로에게 영향을 주지 않습니다.

초보 개발자들이 흔히 하는 실수는 파티션 수를 너무 적게 설정하는 것입니다. "데이터가 많지 않으니까 1개면 되겠지"라고 생각하지만, 나중에 트래픽이 증가하면 파티션 추가는 가능하지만 파티션 축소는 까다롭습니다.

초기 설계 단계에서 적절한 파티션 수를 고려하는 것이 중요합니다. 또 한 가지, 복제 팩터를 1로 설정하는 경우도 주의해야 합니다.

개발 환경에서는 괜찮지만, 운영 환경에서는 반드시 2 이상으로 설정해야 합니다. 그렇지 않으면 단일 장애점(SPOF)이 되어 한 대의 서버가 고장 나면 데이터가 유실될 수 있습니다.

김개발 씨는 필기하며 감탄했습니다. "복제 팩터 2, 파티션 3...

이 숫자 하나하나가 시스템의 안정성과 성능을 결정하는군요." 정확한 통찰이었습니다. Kafka의 힘은 이런 세심한 설계 결정에서 나옵니다.

실전 팁

💡 - 토픽 생성 시 파티션 수와 복제 팩터를 신중하게 결정하세요

  • 운영 환경에서는 복제 팩터를 반드시 2 이상으로 설정하세요
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 2/15편입니다

3. RabbitMQ ActiveMQ와의 비교

회의실에서 김개발 씨는 기술 선택 발표를 준비하고 있었습니다. "왜 RabbitMQ 대신 Kafka를 선택해야 할까요?" 팀장님의 질문에 명확한 근거가 필요했습니다.

Kafka는 RabbitMQActiveMQ 같은 전통적 메시지 브로커와 근본적으로 다른 설계 목표를 가집니다. RabbitMQ가 스마트 브로커(Smart Broker) 방식이라면, Kafka는 스마트 컨슈머(Smart Consumer) 방식을 채택했습니다.

이 차이가 처리량, 확장성, 그리고 사용 사례를 결정합니다.

다음 코드를 살펴봅시다.

// RabbitMQ vs Kafka: 메시지 소비 패턴 비교
// RabbitMQ - 브로커가 메시지 배달 상태를 관리 (push 모델)
channel.basicConsume("task-queue", false, (consumerTag, delivery) -> {
    String message = new String(delivery.getBody(), "UTF-8");
    processMessage(message);  // 메시지 처리
    channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
    // ACK를 보내야 브로커가 메시지를 삭제함
}, consumerTag -> {});
// Kafka - 컨슈머가 읽을 위치를 직접 관리 (pull 모델)
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("task-queue"));
while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
    for (ConsumerRecord<String, String> record : records) {
        processMessage(record.value());
    }
    // 컨슈머가 원할 때 데이터를 가져옴 (pull)
}

김개발 씨의 질문은 실무에서 매우 흔하게 등장합니다. "RabbitMQ도 잘 돌아가는데, 굳이 Kafka로 바꿔야 할 이유가 있을까요?" 이 질문에 답하려면 두 시스템의 근본적인 차이를 이해해야 합니다.

가장 큰 차이는 메시지 전달 방식에 있습니다. RabbitMQ는 Push 모델을 사용합니다.

브로커가 컨슈머에게 메시지를 밀어 넣습니다. 코드에서 channel.basicConsume()를 호출하면, 브로커가 능동적으로 메시지를 보냅니다.

컨슈머가 처리 속도를 따라잡지 못하면, 브로커가 메시지 배달 상태를 추적하고 관리해야 합니다. 반면 Kafka는 Pull 모델을 사용합니다.

컨슈머가 직접 브로커에서 데이터를 가져갑니다. 코드의 consumer.poll() 메서드가 핵심입니다.

컨슈머가 준비되었을 때만 데이터를 요청하므로, 브로커는 상태 관리 부담이 줄어듭니다. 이를 스마트 컨슈머 패턴이라고 부릅니다.

비유하자면, RabbitMQ는 우체부가 집집마다 편지를 배달하는 방식입니다. 우체부는 누가 편지를 받았는지, 누가 아직 못 받았는지 일일이 기록해야 합니다.

반면 Kafka는 우체국에 편지를 보관해두고, 수령인이 직접 찾아가게 하는 방식입니다. 우체국 입장에서는 훨씬 단순해집니다.

두 번째 차이는 메시지 보관 방식입니다. RabbitMQ에서 컨슈머가 메시지를 확인(ACK)하면, 해당 메시지는 브로커에서 삭제됩니다.

한 번 소비한 메시지는 다시 읽을 수 없습니다. 반면 Kafka는 메시지를 디스크에 로그 형태로 보관합니다.

컨슈머가 메시지를 읽어도 삭제되지 않으며, 설정된 보관 기간(예: 7일) 동안 언제든 다시 읽을 수 있습니다. 이 차이가 실무에서 어떤 영향을 미칠까요?

예를 들어, 주문 처리 시스템에서 결제 서비스에 버그가 발생했다고 가정해봅시다. RabbitMQ를 사용 중이었다면, 이미 소비한 메시지는 복구하기 어렵습니다.

하지만 Kafka를 사용 중이었다면, 버그를 수정한 후 컨슈머의 오프셋을 이전 위치로 되돌려서 메시지를 재처리할 수 있습니다. 세 번째 차이는 처리량입니다.

Kafka는 순차적 디스크 쓰기를 사용하고, 운영체제의 **페이지 캐시(Page Cache)**를 적극 활용합니다. 덕분에 네트워크를 통해 전송하는 것보다 디스크에서 읽는 것이 더 빠를 때가 있습니다.

RabbitMQ는 메시지마다 개별적으로 처리하므로, 메시지 크기가 크거나 처리량이 많을 때 성능이 상대적으로 떨어집니다. 하지만 RabbitMQ가 무조건 열등한 것은 아닙니다.

복잡한 라우팅이 필요하거나, 개별 메시지의 배달 보장이 중요한 경우에는 RabbitMQ가 더 적합할 수 있습니다. 예를 들어, 특정 조건에 따라 메시지를 다른 큐로 분배해야 하는 작업이나, 각 메시지의 처리 결과를 정확히 추적해야 하는 작업에는 RabbitMQ의 기능이 유용합니다.

핵심은 사용 사례에 맞는 도구를 선택하는 것입니다. 실시간 로그 수집, 이벤트 스트리밍, 대규모 데이터 파이프라인에는 Kafka가 적합하고, 복잡한 작업 분배, RPC 패턴, 정교한 메시지 라우팅에는 RabbitMQ가 적합합니다.

김개발 씨는 이 비교표를 발표 자료에 정리하며 확신을 얻었습니다. "우리 서비스는 대규모 이벤트를 실시간으로 처리해야 하므로, Kafka의 pull 모델과 로그 보관 방식이 더 적합합니다."

실전 팁

💡 - RabbitMQ는 복잡한 라우팅에, Kafka는 대규모 스트리밍에 적합합니다

  • Kafka는 메시지를 삭제하지 않고 보관하므로 재처리가 가능합니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 2/15편입니다

4. 높은 처리량과 낮은 지연시간 원리

"초당 100만 건의 메시지를 처리한다고요?" 김개발 씨는 믿기 어렵다는 표정이었습니다. 박시니어 씨가 미소를 지으며 말했습니다.

"Kafka의 비밀은 아주 단순한 원리에 숨어 있어요."

Kafka의 높은 처리량은 **순차적 디스크 I/O(Sequential Disk I/O)**와 제로 카피(Zero Copy) 기술, 그리고 **배치 처리(Batch Processing)**에서 비롯됩니다. 마치 고속도로에서 톨게이트를 거치지 않고 직진하는 것처럼, 불필요한 복사 단계를 생략하여 성능을 극대화합니다.

다음 코드를 살펴봅시다.

// Kafka의 배치 처리와 압축 설정
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 배치 처리: 16KB 또는 10ms마다 전송 (처리량 극대화)
props.put("linger.ms", "10");
props.put("batch.size", "16384");
// 압축 설정: 네트워크 대역폭 절약
props.put("compression.type", "lz4");
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 10000; i++) {
    producer.send(new ProducerRecord<>("metrics", "sensor-" + i,
        "{\"temp\": 23.5, \"humidity\": 65}"));
}
producer.flush();

"빠르다"는 것은 모든 개발자가 추구하는 목표입니다. 하지만 Kafka가 얼마나 빠른지, 그리고 빠른지를 이해하는 것은 별개의 문제입니다.

오늘은 이 비밀을 파헤쳐 보겠습니다. 가장 먼저 이해해야 할 개념은 순차적 디스크 I/O입니다.

일반적으로 디스크 접근은 메모리 접근보다 느린 것으로 알려져 있습니다. 하지만 이는 **랜덤 액세스(Random Access)**의 경우입니다.

헤드를 이리저리 움직여야 하니 느릴 수밖에 없죠. 반면 **순차적 액세스(Sequential Access)**는 디스크에서도 놀라운 속도를 냅니다.

헤드가 한 방향으로만 움직이면 되니까요. Kafka는 메시지를 로그 파일에 순차적으로 추가합니다.

새로운 메시지가 도착하면 항상 파일의 끝에 씁니다. 수정도, 삭제도, 중간에 끼워 넣기도 하지 않습니다.

이 단순한 규칙이 디스크 I/O를 극대화하는 열쇠입니다. 실제로 순차적 디스크 읽기는 임의 접근보다 수천 배에서 수만 배까지 빠릅니다.

두 번째 비밀은 제로 카피(Zero Copy) 기술입니다. 일반적으로 데이터를 전송할 때는 여러 번의 복사가 발생합니다.

디스크에서 커널 버퍼로, 커널 버퍼에서 사용자 공간으로, 다시 소켓 버퍼로. 각 복사마다 CPU 자원과 메모리 대역폭이 소모됩니다.

Kafka는 sendfile() 시스템 콜을 사용하여 이 복사 단계를 최소화합니다. 데이터를 디스크에서 직접 네트워크 카드로 전송하므로, 중간에 사용자 공간을 거치지 않아도 됩니다.

비유하자면, 물건을 창고에서 꺼내 사무실로 가져왔다가 다시 트럭에 싣는 대신, 창고에서 직접 트럭에 싣는 것과 같습니다. 중간 경유지를 거치지 않으니 시간과 비용이 절약되는 것이죠.

세 번째 비밀은 코드에서도 확인할 수 있는 **배치 처리(Batch Processing)**입니다. linger.ms10으로, batch.size16384(16KB)로 설정했습니다.

이는 "메시지가 16KB만큼 쌓이거나 10밀리초가 지나면 한 번에 전송하라"는 의미입니다. 개별 메시지를 하나씩 네트워크로 보내는 대신, 여러 메시지를 묶어서 전송하면 네트워크 오버헤드가 크게 줄어듭니다.

네 번째로, 코드의 compression.type 설정에서 볼 수 있는 압축도 중요한 역할을 합니다. lz4 압축은 속도가 매우 빠르면서도 압축률이 준수합니다.

배치로 묶은 메시지를 압축하여 전송하면 네트워크 대역폭을 효과적으로 절약할 수 있습니다. 특히 JSON 같은 텍스트 기반 데이터는 압축 효율이 매우 높습니다.

다섯 번째, 운영체제의 페이지 캐시(Page Cache) 활용도 빼놓을 수 없습니다. Kafka는 자체 캐시를 거의 사용하지 않고, 운영체제가 제공하는 페이지 캐시에 의존합니다.

자바의 가비지 컬렉션(GC)으로부터 자유로우면서도, 운영체제 수준의 최적화를 그대로 활용할 수 있습니다. 이것이 Kafka가 다른 자바 기반 시스템보다 메모리 관리가 수월한 이유 중 하나입니다.

이 모든 기술이 결합하면 어떤 결과가 나올까요? 단일 Kafka 클러스터에서 초당 수백만 건의 메시지를 처리하는 것이 가능합니다.

지연시간은 보통 2~5밀리초 수준으로, 대부분의 실시간 애플리케이션 요구사항을 충족합니다. 주의할 점은, linger.ms를 너무 크게 설정하면 지연시간이 증가한다는 것입니다.

10밀리초는 처리량과 지연시간 사이의 좋은 균형점입니다. 또한 batch.size를 너무 크게 설정하면 메모리 사용량이 증가하므로, 시스템 자원에 맞게 조절해야 합니다.

김개발 씨는 노트에 적으며 감탄했습니다. "단순히 빠른 게 아니라, 하나하나의 설계 결정이 성능에 기여하고 있군요." 시스템 성능은 마법이 아닙니다.

올바른 설계 결정의 결과물입니다.

실전 팁

💡 - linger.ms와 batch.size를 조절하여 처리량과 지연시간의 균형을 맞추세요

  • lz4 압축은 속도와 압축률의 균형이 가장 좋은 선택입니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 2/15편입니다

5. 카프카 생태계 구성 요소

박시니어 씨가 카프카 아키텍처 다이어그램을 펼쳤습니다. "Kafka라고 하면 브로커만 생각하지만, 사실 이것만으로는 부족해요." 다이어그램에는 여러 구성 요소가 그려져 있었습니다.

카프카 생태계는 Kafka Broker, ZooKeeper(또는 KRaft), Kafka Connect, Kafka Streams, Schema Registry 등 여러 구성 요소로 이루어집니다. 마치 오케스트라의 각 악기가 제 역할을 하면서도 조화를 이루는 것처럼, 각 구성 요소가 협력하여 완전한 이벤트 스트리밍 플랫폼을 구성합니다.

다음 코드를 살펴봅시다.

// Kafka Streams: 실시간 스트림 처리 예시
Properties streamsProps = new Properties();
streamsProps.put(StreamsConfig.APPLICATION_ID_CONFIG, "order-analytics");
streamsProps.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
streamsProps.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
streamsProps.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
// KStream 빌더로 스트림 파이프라인 구성
StreamsBuilder builder = new StreamsBuilder();
// 주문 토픽에서 금액이 50000원 이상인 주문만 필터링
KStream<String, String> highValueOrders = builder.stream("orders")
    .filter((key, value) -> {
        JsonObject order = JsonParser.parseString(value).getAsJsonObject();
        return order.get("amount").getAsInt() >= 50000;
    });
// 필터링된 결과를 새로운 토픽으로 전송
highValueOrders.to("high-value-orders");
KafkaStreams streams = new KafkaStreams(builder.build(), streamsProps);
streams.start();

카프카를 배우다 보면, 단순히 메시지를 주고받는 것 이상의 영역을 만나게 됩니다. 오늘은 카프카 생태계의 전체 지도를 그려보겠습니다.

가장 기본이 되는 것은 Kafka Broker입니다. 브로커는 Kafka 클러스터를 구성하는 각 서버를 말합니다.

일반적으로 3대 이상의 브로커로 클러스터를 구성하며, 각 브로커는 하나 이상의 파티션을 관리합니다. 클러스터 내에서 하나의 브로커가 컨트롤러(Controller) 역할을 맡아 파티션 리더 선출, 브로커 장애 감지 등의 관리 작업을 수행합니다.

과거에는 Apache ZooKeeper가 이런 관리 작업을 담당했습니다. ZooKeeper는 분산 시스템의 **조정 서비스(Coordination Service)**로, 브로커의 메타데이터를 관리하고 리더 선출을 지원했습니다.

하지만 Kafka 2.8부터는 KRaft(Kafka Raft) 모드가 도입되어, ZooKeeper 없이도 클러스터를 운영할 수 있게 되었습니다. KRaft는 Kafka 내부에 메타데이터 관리를 통합한 모드로, 운영 복잡도를 크게 줄여줍니다.

새로운 프로젝트에서는 KRaft 모드를 기본으로 사용하는 것을 권장합니다. Kafka Connect는 Kafka와 외부 시스템 간의 데이터 연결을 담당합니다.

예를 들어, MySQL 데이터베이스의 변경 사항을 Kafka 토픽으로 실시간 전송하거나, Elasticsearch에 데이터를 색인하는 작업을 수행할 수 있습니다. **소스 커넥터(Source Connector)**는 외부 시스템에서 Kafka로 데이터를 가져오고, **싱크 커넥터(Sink Connector)**는 Kafka에서 외부 시스템으로 데이터를 보냅니다.

코드를 작성할 필요 없이 설정만으로 데이터 파이프라인을 구축할 수 있습니다. 코드에서 보여주는 Kafka Streams는 클라이언트 라이브러리 형태의 실시간 스트림 처리 엔진입니다.

별도의 처리 클러스터(Spark Streaming, Flink 등) 없이도, 애플리케이션 코드 내에서 스트림 처리 로직을 구현할 수 있습니다. 코드의 builder.stream("orders")가 토픽의 데이터를 스트림으로 읽어오고, .filter()로 조건에 맞는 데이터만 걸러낸 뒤, .to()로 결과를 다른 토픽으로 보냅니다.

Schema Registry는 메시지의 데이터 구조(스키마)를 중앙에서 관리하는 컴포넌트입니다. 프로듀서가 메시지를 보낼 때 스키마를 등록하고, 컨슈머가 메시지를 읽을 때 스키마를 검증합니다.

이를 통해 호환성 없는 스키마 변경이 배포되는 것을 방지할 수 있습니다. 마치 건축 도면을 중앙 관리소에 보관하여, 모든 시공자가 동일한 도면을 기준으로 작업하는 것과 같습니다.

마지막으로 Kafka REST Proxy도 알아두면 좋습니다. HTTP/REST API를 통해 Kafka에 접근할 수 있게 해주는 프록시 서비스입니다.

자바가 아닌 언어로 작성된 시스템이나, 브라우저 기반 애플리케이션에서 Kafka를 사용해야 할 때 유용합니다. 이처럼 카프카 생태계는 단순한 메시지 브로커를 넘어, 완전한 이벤트 스트리밍 플랫폼을 제공합니다.

각 구성 요소를 필요에 따라 조합하면, 데이터 수집부터 처리, 저장, 분석까지 하나의 플랫폼에서 해결할 수 있습니다. 초보 개발자들은 생태계가 너무 방대해서 어디서부터 시작해야 할지 막막할 수 있습니다.

이럴 때는 Kafka Broker와 기본 프로듀서/컨슈머 API부터 시작하는 것이 좋습니다. 기본기를 확실히 다진 후에 Connect, Streams, Schema Registry를 순서대로 살펴보세요.

김개발 씨는 다이어그램을 보며 눈을 반짝였습니다. "그러니까 Kafka 하나로 데이터 파이프라인 전체를 구축할 수 있다는 거군요!" 박시니어 씨가 고개를 끄덕였습니다.

"맞아요. 그래서 Kafka를 이벤트 스트리밍 플랫폼이라고 부르는 거예요."

실전 팁

💡 - 새로운 프로젝트에서는 KRaft 모드를 사용하여 ZooKeeper 없이 클러스터를 구성하세요

  • Kafka Streams로 별도의 처리 시스템 없이 실시간 스트림 처리를 구현할 수 있습니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 2/15편입니다

6. 카프카 사용 사례

김개발 씨가 박시니어 씨에게 물었습니다. "Kafka를 실제로 어디에 쓰이나요?" 박시니어 씨가 실제 기업들의 사례를 들어 설명하기 시작했습니다.

Kafka는 실시간 데이터 파이프라인, 이벤트 소싱(Event Sourcing), 로그 수집 및 모니터링, 실시간 추천 시스템, 마이크로서비스 간 통신 등 다양한 분야에서 활용됩니다. 마치 물이 수도관을 통해 다양한 곳으로 공급되는 것처럼, 이벤트 데이터를 필요한 곳으로 실시간에 전달합니다.

다음 코드를 살펴봅시다.

// 실시간 주문 처리 파이프라인 예시
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
// 주문 이벤트를 토픽에 발행
String orderEvent = """
    {"orderId": "ORD-2024-001", "userId": "user-1001",
     "items": [{"productId": "P001", "qty": 2, "price": 25000}],
     "totalAmount": 50000, "timestamp": "%s"}
    """.formatted(System.currentTimeMillis());
// 하나의 이벤트를 여러 시스템에서 동시에 소비 가능
producer.send(new ProducerRecord<>("orders", "ORD-2024-001", orderEvent));
// 재고, 배송, 알림, 분석 시스템이 각각 독립적으로 구독
producer.close();

이론만으로는 아직 와닿지 않을 수 있습니다. Kafka가 실제 현업에서 어떻게 쓰이는지 구체적인 사례를 살펴보겠습니다.

가장 대표적인 사용 사례는 실시간 데이터 파이프라인입니다. 코드에서 볼 수 있는 것처럼, 하나의 주문 이벤트를 orders 토픽에 발행하면 재고 관리 시스템, 배송 시스템, 알림 시스템, 데이터 분석 시스템이 각각 독립적으로 이 이벤트를 소비할 수 있습니다.

각 시스템이 다른 컨슈머 그룹으로 구독하면, 하나의 이벤트가 여러 시스템에 동시에 전달됩니다. 마치 하나의 TV 방송을 여러 대의 TV에서 동시에 시청하는 것과 같습니다.

**이벤트 소싱(Event Sourcing)**도 Kafka의 중요한 활용 분야입니다. 이벤트 소싱이란 애플리케이션의 상태를 최종 결과가 아니라, 상태를 변경한 이벤트들의 로그로 저장하는 패턴입니다.

은행 계좌를 예로 들면, 잔고 100,000원이라는 최종 상태를 저장하는 대신, "입금 50,000원", "출금 20,000원" 같은 이벤트를 순서대로 기록합니다. Kafka는 이런 이벤트 로그를 저장하기에 완벽한 시스템입니다.

모든 이벤트가 순서대로 보관되고, 언제든 재생할 수 있으니까요. 로그 수집 및 모니터링 분야에서도 Kafka는 널리 사용됩니다.

Netflix는 Kafka를 사용하여 수백 개의 마이크로서비스에서 발생하는 로그를 실시간으로 수집하고, Apache Spark와 연동하여 로그 분석을 수행합니다. 수천 대의 서버에서 발생하는 로그를 단일 시스템에 모으는 것은 쉽지 않은 일이지만, Kafka의 분산 아키텍처와 높은 처리량 덕분에 이것이 가능합니다.

실시간 추천 시스템에서도 Kafka가 핵심 역할을 합니다. 사용자의 클릭, 조회, 구매 같은 활동 이벤트를 Kafka에 실시간으로 전송하고, Kafka Streams나 Apache Flink 같은 스트리밍 엔진으로 이를 처리하여 추천 모델에 피드백합니다.

사용자가 상품을 클릭한 지 수 초 이내에 맞춤형 추천이 업데이트되는 경험은 Kafka 없이는 구현하기 어렵습니다. 마이크로서비스 간 비동기 통신도 중요한 사례입니다.

마이크로서비스 아키텍처에서 서비스 간 직접 통신은 **결합도(Coupling)**를 높이고, 한 서비스의 장애가 다른 서비스로 전파될 위험이 있습니다. Kafka를 중간에 두면 서비스 간의 직접 의존성을 제거할 수 있습니다.

주문 서비스는 배송 서비스가 존재하는지조차 알 필요가 없습니다. 그저 orders 토픽에 이벤트를 발행하기만 하면 됩니다.

Uber는 Kafka를 사용하여 실시간 요금 계산, 운전자 매칭, 위치 추적 등 핵심 기능을 지원합니다. 매일 수십억 건의 이벤트가 Uber의 Kafka 클러스터를 통과합니다.

Airbnb는 검색 로그 수집과 가격 동기화에 Kafka를 활용합니다. LinkedIn 자체도 Kafka의 발상지이자 가장 대규모 사용자 중 하나입니다.

수조 건의 메시지를 매일 처리한다고 합니다. 하지만 모든 시스템에 Kafka를 도입해야 하는 것은 아닙니다.

초보 개발자들이 흔히 하는 실수 중 하나는 **과도한 카프카화(Over-Kafka-fication)**입니다. 단순한 작업 큐 하나 처리하는데 Kafka를 도입하는 것은 과한 투자입니다.

시스템이 실제로 높은 처리량, 이벤트 재생, 다중 컨슈머 같은 Kafka의 핵심 장점이 필요한지 먼저 평가해야 합니다. "그러면 우리 프로젝트에는 Kafka가 적합할까요?" 김개발 씨의 질문에 박시니어 씨는 웃으며 대답했습니다.

"여러 서비스에서 동일한 이벤트를 소비해야 하고, 데이터 양도 계속 늘어나고 있으니까, Kafka는 좋은 선택이 될 거예요. 하지만 항상 다른 대안과 비교해보는 습관을 들이세요." 다음 카드뉴스에서는 Kafka를 직접 설치하고 환경을 설정하는 실습을 진행합니다.

이론만으로는 부족하니, 직접 손으로 만져보며 Kafka를 경험해 보겠습니다.

실전 팁

💡 - Kafka는 높은 처리량과 다중 컨슈머가 필요한 상황에서 가장 가치를 발휘합니다

  • 이벤트 소싱 패턴과 Kafka의 로그 기반 설계는 자연스럽게 결합됩니다
  • 다음 카드뉴스에서는 "Kafka 설치 및 환경 설정"을 다룹니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 2/15편입니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Kafka#MessageQueue#EventStreaming#DistributedSystem#ApacheKafka

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스

토픽과 파티션의 이해 완벽 가이드

Kafka의 핵심 데이터 구조인 토픽과 파티션의 개념부터 오프셋, 메시지 순서 보장, 삭제 정책까지 실무에 필요한 모든 내용을 다룹니다. 카프카를 제대로 이해하기 위해 반드시 알아야 할 기초를 탄탄하게 다집니다.

Premium

Kafka 설치 및 환경 설정 완벽 가이드

Apache Kafka의 설치부터 환경 설정까지 단계별로 안내합니다. JDK 설치, ZooKeeper 구성, Kafka 브로커 설정, Docker Compose 활용, KRaft 모드까지 실무에 필요한 모든 환경 구축 방법을 다룹니다.

Premium

메시지 큐와 이벤트 스트리밍 개념 완벽 가이드

메시지 큐의 기본 개념부터 이벤트 기반 아키텍처, 스트리밍 플랫폼의 필요성까지 술술 읽히는 이북 스타일로 설명합니다. Apache Kafka를 배우기 전 반드시 알아야 할 핵심 개념들을 다룹니다.

Premium

MCP 어노테이션 기반 개발 완벽 가이드

Spring AI와 MCP(Model Context Protocol)를 활용한 어노테이션 기반 도구 개발 방법을 알아봅니다. 선언적 프로그래밍으로 AI 에이전트용 도구를 쉽게 만드는 방법을 초급자도 이해할 수 있게 설명합니다.

Premium

MCP 클라이언트 구현 완벽 가이드

Model Context Protocol 클라이언트를 Java/Spring 환경에서 구현하는 방법을 다룹니다. 서버 디스커버리부터 멀티 서버 관리까지 실무에서 바로 사용할 수 있는 패턴을 학습합니다.

Premium