🤖

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

⚠️

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

이미지 로딩 중...

메시지 큐와 이벤트 스트리밍 개념 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2026. 4. 9. · 0 Views

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

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


목차

  1. 메시지 큐란 무엇인가
  2. Point-to-Point vs Publish-Subscribe
  3. 이벤트 기반 아키텍처란
  4. 이벤트 드리븐 마이크로서비스
  5. 스트리밍 플랫폼의 필요성
  6. 전통적 메시지 브로커의 한계

1. 메시지 큐란 무엇인가

어느 날 김개발 씨는 밀려있는 이메일 알림 처리를 보며 한숨을 쉬었습니다. "이걸 언제 다 처리하나요..." 그때 박시니어 씨가 지나가며 말했습니다.

"메시지 큐를 쓰면 그 고민이 사라질 거예요."

메시지 큐(Message Queue)는 시스템 간에 데이터를 비동기적으로 전달하는 중간 매개체입니다. 마치 우체국이 편지를 받아 순서대로 배달하는 것과 같습니다.

발신자는 편지를 넣고, 수신자는 편지를 꺼내 처리합니다. 서로 직접 연결될 필요가 없습니다.

다음 코드를 살펴봅시다.

// Java에서 메시지 큐의 기본 동작 원리 (의사코드)
MessageQueue queue = new MessageQueue("order-notifications");

// 생산자: 주문 완료 메시지를 큐에 전송
queue.send(new Message("order-1001", "주문이 완료되었습니다"));
queue.send(new Message("order-1002", "배송이 시작되었습니다"));

// 소비자: 큐에서 메시지를 순서대로 꺼내 처리
Message msg = queue.receive(); // order-1001
System.out.println(msg.getContent());
queue.acknowledge(msg); // 처리 완료를 큐에 알림

김개발 씨는 입사 3개월 차 백엔드 개발자입니다. 오늘도 JIRA 티켓을 처리하던 중, "주문 완료 시 이메일 발송이 가끔 누락된다"는 버그 리포트를 발견했습니다.

코드를 열어보니 주문 서비스에서 직접 이메일 발송 API를 호출하고 있었습니다. 박시니어 씨가 커피를 들고 다가와 모니터를 들여다봅니다.

"아, 여기서 주문 처리와 이메일 발송이 동기적으로 묶여 있네요. 이메일 서버가 느려지면 주문 응답도 느려지고, 서버가 죽으면 메시지도 사라지겠네." 김개발 씨가 고개를 갸웃했습니다.

"그럼 어떻게 해결하죠?" 그렇다면 메시지 큐란 정확히 무엇일까요? 쉽게 비유하자면, 메시지 큐는 마치 우체통과 같습니다.

편지를 쓰는 사람은 우체통에 편지를 넣고 자기 일을 계속합니다. 우체부는 우체통에서 편지를 꺼내 배달합니다.

편지를 쓰는 사람과 배달하는 사람이 동시에 일하지 않아도 됩니다. 편지가 우체통에 안전하게 쌓여 있기 때문입니다.

메시지 큐도 똑같습니다. 생산자(Producer)는 메시지를 큐에 넣고, 소비자(Consumer)는 큐에서 메시지를 꺼내 처리합니다.

두 시스템이 서로를 직접 알 필요가 없습니다. 메시지 큐가 없던 시절에는 어땠을까요?

개발자들은 서비스 간에 직접 API를 호출해야 했습니다. 주문 서비스가 이메일 서비스, 알림 서비스, 재고 서비스를 모두 직접 호출해야 했습니다.

한 서비스가 멈추면 연쇄적으로 다른 서비스도 영향을 받았습니다. 코드가 얽히고설키기 복잡해졌습니다.

더 큰 문제는 트래픽 급증이었습니다. 이벤트 오픈 날 주문이 100배로 늘어나면 이메일 서비스가 버티지 못하고 전체 시스템이 멈출 수 있었습니다.

바로 이런 문제를 해결하기 위해 메시지 큐가 등장했습니다. 메시지 큐를 사용하면 시스템 간 결합도를 낮출 수 있습니다.

주문 서비스는 그냥 메시지만 큐에 던지면 됩니다. 누가 처리할지는 알 필요 없습니다.

또한 비동기 처리가 가능해집니다. 사용자는 주문 즉시 응답을 받고, 이메일 발송은 백그라운드에서 천천히 처리됩니다.

무엇보다 장애 격리라는 큰 이점이 있습니다. 이메일 서버가 죽어도 주문 서비스는 멀쩡하게 동작합니다.

위의 코드를 한 줄씩 살펴보겠습니다. 먼저 MessageQueue 객체를 생성할 때 큐의 이름을 지정합니다.

이 이름으로 큐를 식별합니다. 다음으로 send() 메서드로 메시지를 큐에 전송합니다.

생산자 입장에서는 이게 전부입니다. 소비자 쪽에서는 receive()로 메시지를 꺼내고, 처리가 끝나면 acknowledge()로 완료를 알립니다.

이렇게 해야 큐에서 해당 메시지가 삭제됩니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 쇼핑몰에서 주문이 발생하면 동시에 여러 작업이 필요합니다. 이메일 발송, 재고 감소, 배송 시스템 연동, 포인트 적립 등입니다.

메시지 큐 없이 처리하면 하나라도 실패하면 전체를 롤백해야 합니다. 큐를 사용하면 각각 독립적으로 처리할 수 있어 훨씬 안정적입니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 메시지 처리 후 acknowledge를 깜빡하는 것입니다.

이렇게 하면 같은 메시지가 여러 번 처리될 수 있습니다. 또한 메시지 순서가 보장된다고 착각하는 경우도 많습니다.

일부 큐는 순서를 보장하지 않으므로, 순서가 중요한 비즈니스라면 이를 고려해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 그랬군요!

우체통 같은 역할을 하는 거군요." 메시지 큐를 제대로 이해하면 더 유연하고 장애에 강한 시스템을 설계할 수 있습니다. 이것이 우리가 Apache Kafka 완전 정복 코스에서 배울 내용의 첫걸음입니다.

실전 팁

💡 - 메시지 큐의 핵심은 "비동기 처리"와 "시스템 분리"입니다. 이 두 가지를 기억하세요

  • 실무에서는 RabbitMQ, ActiveMQ, 그리고 물론 Kafka 등 다양한 메시지 큐를 상황에 맞게 선택합니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 1/15편입니다. 다음 편에서는 Kafka의 핵심 특징을 알아봅니다

2. Point-to-Point vs Publish-Subscribe

김개발 씨는 메시지 큐를 도입하려다가 또다시 멈칫했습니다. "근데 선배님, 큐에 메시지를 넣으면 한 명만 받을 수 있는 건가요?

아니면 여러 명이 동시에 받을 수도 있나요?" 박시니어 씨가 미소를 지으며 화이트보드로 향했습니다.

Point-to-Point 모델은 하나의 메시지를 정확히 한 명의 소비자에게만 전달합니다. 반면 Publish-Subscribe(Pub/Sub) 모델은 하나의 메시지를 구독한 모든 소비자에게 동시에 전달합니다.

마치 카카오톡 1:1 채팅과 단체 채팅방의 차이와 같습니다.

다음 코드를 살펴봅시다.

// Point-to-Point 모델
Queue queue = new Queue("order-queue");
queue.send(new Message("주문 #1001 완료"));
// 소비자 A만 메시지를 받고, B는 못 받음

// Publish-Subscribe 모델
Topic topic = new Topic("order-events");
topic.publish(new Message("주문 #1001 완료"));
// 이벤트 알림 서비스, 재고 서비스, 배송 서비스 모두 수신

// Java Observer 패턴으로 Pub/Sub 이해하기
EventBus eventBus = new EventBus();
eventBus.subscribe("order.completed", (msg) -> sendEmail(msg));
eventBus.subscribe("order.completed", (msg) -> updateInventory(msg));
eventBus.publish("order.completed", orderData); // 모든 구독자에게 전달

메시지 큐의 기본 개념을 이해한 김개발 씨는 드디어 실전 적용을 준비하고 있었습니다. 하지만 설계 단계에서 또다시 막혔습니다.

"주문 완료 메시지를 이메일 서비스에도 보내야 하고, 재고 서비스에도 보내야 하는데, 큐에 하나 넣으면 누가 가져가는 거죠?" 박시니어 씨가 옆에 앉으며 말합니다. "좋은 질문이에요.

메시징에는 두 가지 전달 방식이 있거든요." 그렇다면 이 두 가지 방식은 무엇이 다를까요? 먼저 Point-to-Point 모델을 비유해 보겠습니다.

이는 마치 식당의 번호표 시스템과 같습니다. 안내 데스크에서 번호표를 뽑으면, 정확히 한 명의 손님이 그 번호를 부르고 음식을 받습니다.

같은 번호표를 두 손님이 동시에 받을 수는 없습니다. 메시지도 마찬가지입니다.

큐에 하나의 메시지를 넣으면, 여러 소비자 중 단 한 명만 꺼내갈 수 있습니다. Publish-Subscribe 모델은 전혀 다릅니다.

이는 마치 신문 배달과 같습니다. 신문사에서 기사를 발행하면, 그 신문을 구독한 모든 가정에 동시에 배달됩니다.

100가구가 구독하면 100부가 배달됩니다. Pub/Sub에서는 하나의 메시지가 해당 토픽(Topic)을 구독한 모든 소비자에게 전달됩니다.

Point-to-Point 방식만 있던 시절에는 어땠을까요? 주문 완료 후 이메일, 재고, 배송 세 가지 작업을 해야 한다면, 개발자들은 번거롭게 세 개의 큐를 만들고 같은 메시지를 세 번 전송**해야 했습니다.

코드가 중복되고, 새로운 처리가 추가될 때마다 생산자 코드를 수정해야 했습니다. 확장성이 매우 떨어졌습니다.

바로 이런 문제를 해결하기 위해 Pub/Sub 모델이 널리 쓰이게 되었습니다. Pub/Sub을 사용하면 생산자는 누가 구독하는지 알 필요가 없습니다.

그냥 토픽에 메시지를 발행하기만 하면 됩니다. 새로운 소비자가 추가되어도 생산자 코드는 변경되지 않습니다.

또한 한 번의 발행으로 여러 시스템에 동시 전달이 가능합니다. 이것이 실무에서 Pub/Sub이 더 많이 쓰이는 이유 중 하나입니다.

위의 코드를 한 줄씩 살펴보겠습니다. 첫 번째 블록은 Point-to-Point 모델입니다.

Queue 객체에 메시지를 보내면, 여러 소비자가 대기 중이더라도 한 명만 꺼내갑니다. 두 번째 블록은 Pub/Sub 모델입니다.

Topic 객체에 메시지를 발행하면, 이 토픽을 구독하는 모든 소비자가 동시에 수신합니다. 세 번째 블록은 Java의 Observer 패턴으로 이 개념을 더 직관적으로 보여줍니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 은행 시스템을 생각해 봅시다.

계좌 이체라는 이벤트가 발생하면, 입출금 내역 서비스, SMS 알림 서비스, 거래 내역 분석 서비스 등 여러 시스템이 이 이벤트를 필요로 합니다. Pub/Sub을 사용하면 이체 서비스는 그냥 "이체 완료" 이벤트를 발행하기만 하면 됩니다.

새로운 서비스가 추가되어도 이체 서비스 코드는 건드리지 않아도 됩니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 모든 상황에 Pub/Sub을 쓰려는 것입니다. 만약 작업을 정확히 한 번만 처리해야 한다면 Point-to-Point가 적합합니다.

예를 들어 중복 처리되면 안 되는 결제 처리 같은 경우입니다. 또한 Pub/Sub에서는 구독자가 메시지 처리 속도를 따라가지 못하면 메시지가 쌓여 장애로 이어질 수 있으므로, 소비자의 처리 능력을 고려해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 듣고 김개발 씨는 화이트보드에 두 모델을 비교하는 그림을 그렸습니다.

"알겠습니다! 저희 시스템은 주문 이벤트를 여러 서비스에서 필요로 하니까 Pub/Sub이 맞겠네요!" 상황에 맞는 올바른 모델을 선택하는 것이 시스템 설계의 첫걸음입니다.

실전 팁

💡 - 작업이 한 번만 처리되어야 하면 Point-to-Point, 여러 시스템이 동시에 받아야 하면 Pub/Sub을 선택하세요

  • Kafka는 Pub/Sub 모델을 기반으로 하면서도 컨슈머 그룹을 통해 Point-to-Point처럼도 동작할 수 있습니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 1/15편입니다

3. 이벤트 기반 아키텍처란

박시니어 씨가 김개발 씨에게 새로운 프로젝트 아키텍처 다이어그램을 보여줬습니다. "이번엔 이벤트 기반 아키텍처로 가려고 해요." 김개발 씨는 생소한 용어에 고개를 갸웃했습니다.

"이벤트 기반이요? 그게 뭔가요?"

이벤트 기반 아키텍처(Event-Driven Architecture)는 시스템의 상태 변화를 "이벤트"라는 형태로 발행하고, 이를 구독하는 시스템들이 각자의 방식으로 반응하는 설계 방식입니다. 마치 뉴스 속보가 나오면 각 언론사가 자체적으로 기사를 작성하는 것과 같습니다.

중앙에서 모든 것을 제어하지 않고, 각 구성요소가 독립적으로 동작합니다.

다음 코드를 살펴봅시다.

// 이벤트 기반 아키텍처의 핵심 구조
public class OrderService {
    private EventPublisher publisher;

    public void placeOrder(Order order) {
        orderRepository.save(order);     // 1. 주문 저장
        publisher.publish("OrderPlaced",  // 2. 이벤트 발행 (누가 들을지 모름)
            new OrderPlacedEvent(order.getId(), order.getAmount()));
    }
}

// 이벤트를 수신하는 쪽 (이메일 서비스)
@EventListener(topic = "OrderPlaced")
public void handleOrderPlaced(OrderPlacedEvent event) {
    emailService.sendOrderConfirmation(event.getOrderId());
}

// 이벤트를 수신하는 쪽 (재고 서비스) - 독립적
@EventListener(topic = "OrderPlaced")
public void handleInventoryUpdate(OrderPlacedEvent event) {
    inventoryService.decreaseStock(event.getOrderId());
}

김개발 씨가 입사하자마자 참여한 첫 프로젝트는 전통적인 요청-응답(Request-Response) 방식의 모놀리식 애플리케이션이었습니다. 사용자가 주문을 하면 OrderController가 OrderService를 호출하고, OrderService가 EmailService와 InventoryService를 순차적으로 호출했습니다.

코드를 수정할 때마다 전체 시스템이 얽혀 있어 한 군데를 건드리면 다른 곳에서 버그가 터졌습니다. "선배님, 이 구조는 너무 복잡한 것 같아요..." 김개발 씨의 한숨에 박시니어 씨가 고개를 끄덕였습니다.

"맞아요. 그래서 이번 리뉴얼에서 이벤트 기반으로 바꾸려는 거예요." 그렇다면 이벤트 기반 아키텍처란 정확히 무엇일까요?

쉽게 비유하자면, 이벤트 기반 아키텍처는 마치 소방서의 출동 시스템과 같습니다. 화재 발생이라는 "이벤트"가 발생하면, 소방서, 구급대, 경찰이 각자의 역할에 맞춰 독립적으로 출동합니다.

화재 발생 장소가 소방서에게 "너희가 와야 해"라고 직접 요청하는 것이 아닙니다. 그냥 "불이 났다"는 사실을 알리고, 각 기관이 알아서 반응하는 것입니다.

이벤트 기반에서 이벤트(Event)란 "이미 일어난 사실"을 의미합니다. "주문 #1001이 완료되었습니다", "사용자가 로그인했습니다", "재고가 0이 되었습니다" 같은 것들입니다.

이 사실들을 메시지 큐를 통해 발행하면, 관심 있는 시스템들이 각자 필요한 작업을 수행합니다. 전통적인 요청-응답 방식에서는 어땠을까요?

모든 호출이 동기적으로 이루어졌습니다. 주문 서비스가 이메일 서비스를 직접 호출하면, 이메일 서비스가 응답할 때까지 주문 서비스는 기다려야 합니다.

서비스 간에 강한 결합(Tight Coupling)이 형성되었습니다. 이메일 서비스의 인터페이스가 바뀌면 주문 서비스 코드도 수정해야 했습니다.

배포도 하나의 서비스가 고장 나면 전체가 멈추는 경우가 많았습니다. 바로 이런 문제를 해결하기 위해 이벤트 기반 아키텍처가 등장했습니다.

이벤트 기반으로 설계하면 서비스 간 결합도가 대폭 낮아집니다. 주문 서비스는 이벤트만 발행하면 되고, 누가 수신하는지 알 필요가 없습니다.

또한 비즈니스 로직의 독립성이 확보됩니다. 이메일 발송 로직을 변경해도 주문 서비스에는 영향이 없습니다.

무엇보다 새로운 기능 추가가 쉬워집니다. "주문 시 포인트도 적립해야 해"라는 요구가 오면, 새로운 이벤트 리스너만 추가하면 됩니다.

위의 코드를 한 줄씩 살펴보겠습니다. OrderServiceplaceOrder 메서드를 보면, 주문을 저장한 후 이벤트를 발행합니다.

이 시점에서 이벤트가 누구에게 전달될지 알 수 없습니다. 그리고 그것이 의도한 바입니다.

이메일 서비스와 재고 서비스는 각각 @EventListener 어노테이션으로 이벤트를 구독합니다. 두 서비스는 서로를 모르고, 발행자도 모릅니다.

각자 자기 할 일만 합니다. 실제 현업에서는 어떻게 활용할까요?

대규모 전자상거래 플랫폼을 예로 들어보겠습니다. 사용자 결제라는 한 가지 이벤트에서 파생되는 작업이 수십 가지입니다.

영수증 발송, 포인트 적립, 추천 시스템 업데이트, 매출 분석, 사기 감지, 창고 알림 등입니다. 이 모든 것을 결제 서비스에서 직접 호출한다면 결제 서비스는 끔찍하게 복잡해질 것입니다.

이벤트 기반으로 설계하면 결제 서비스는 "결제 완료" 이벤트 하나만 발행하면 됩니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 이벤트의 의미를 너무 모호하게 정의하는 것입니다. "데이터가 변경되었습니다" 같은 이벤트는 누가 봐도 무엇이 변경되었는지 알 수 없습니다.

"주문_결제_완료", "재고_부족_경고"처럼 구체적으로 명명해야 합니다. 또한 이벤트 처리가 실패했을 때의 재시도 전략과 보상 트랜잭션을 반드시 고려해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 화이트보드 위에 그려진 이벤트 흐름도를 보며 김개발 씨는 감탄했습니다.

"각 서비스가 서로를 모르게 독립적으로 동작한다니, 정말 깔끔하네요!" 이벤트 기반 아키텍처는 시스템의 복잡도를 관리하는 강력한 방법입니다. 그리고 이 아키텍처의 핵심에는 바로 메시지 브로커가 있습니다.

실전 팁

💡 - 이벤트 이름은 과거형으로 작성하세요. "OrderCreated", "PaymentCompleted"처럼 이미 일어난 사실을 표현합니다

  • 이벤트에는 최소한의 정보만 담으세요. 필요한 데이터는 수신자가 자체적으로 조회하게 하는 것이 좋습니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 1/15편입니다

4. 이벤트 드리븐 마이크로서비스

회사에서 마이크로서비스 전환 프로젝트가 시작되었습니다. 김개발 씨는 기대감에 들떴지만, 박시니어 씨는 신중한 표정이었습니다.

"마이크로서비스만으로는 부족해요. 서비스 간 통신을 어떻게 할지가 진짜 문제거든요." 김개발 씨는 고개를 끄덕이며 메모를 꺼냈습니다.

이벤트 드리븐 마이크로서비스(Event-Driven Microservices)는 마이크로서비스 간에 동기식 REST API 호출 대신 이벤트 메시지를 통해 통신하는 방식입니다. 마치 회사에서 부서 간에 직접 방문해서 업무를 요청하는 대신, 사내 메신저로 요청을 보내고 각 부서가 자기 속도로 처리하는 것과 같습니다.

각 서비스가 독립적으로 배포되고 확장될 수 있습니다.

다음 코드를 살펴봅시다.

// 동기식 통신 (안티패턴)
public class OrderServiceSync {
    public void processOrder(Order order) {
        paymentService.processPayment(order);  // 결제 서비스 대기...
        inventoryService.reserveStock(order);   // 재고 서비스 대기...
        notificationService.sendNotification(order); // 알림 서비스 대기...
        // 하나라도 느려지면 전체 응답이 느려짐
    }
}

// 이벤트 드리븐 통신 (올바른 패턴)
public class OrderServiceEventDriven {
    private EventPublisher publisher;

    public OrderResult processOrder(Order order) {
        orderRepository.save(order);
        publisher.publish("OrderCreated",
            new OrderEvent(order.getId(), order.getItems()));
        return OrderResult.success(order.getId());
        // 즉시 응답! 나머지는 이벤트로 비동기 처리
    }
}

회사의 모놀리식 애플리케이션이 너무 커져서 드디어 마이크로서비스로 분리하기로 결정했습니다. 김개발 씨는 "마이크로서비스 = 독립 배포, 독립 확장"이라고 외우며 기뻐했습니다.

하지만 첫 번째 장벽은 서비스 간 통신이었습니다. 주문 서비스에서 결제 서비스를 호출하려면 HTTP 요청을 보내야 했고, 결제 서비스가 응답할 때까지 주문 서비스는 스레드를 점유한 채 기다려야 했습니다.

"이게 마이크로서비스가 맞나요?" 김개발 씨의 질문에 박시니어 씨가 대답했습니다. "서비스를 나눴지만 통신 방식은 예전과 똑같아서 문제예요.

이벤트 드리븐으로 바꿔야 합니다." 그렇다면 이벤트 드리븐 마이크로서비스란 무엇일까요? 쉽게 비유하자면, 이는 마치 레스토랑의 주방 시스템과 같습니다.

웨이터가 손님의 주문을 받으면 주방에 주문서를 전달합니다. 웨이터는 조리가 끝날 때까지 기다리지 않고, 다음 손님을 응대합니다.

주방에서는 각 코너(구이, 튀김, 샐러드)가 독립적으로 자기 몫을 준비합니다. 모든 준비가 끝나면 웨이터에게 완료 신호가 갑니다.

이처럼 각 구성요소가 자신의 속도로 독립적으로 일합니다. 이벤트 드리븐이라는 말의 핵심은 서비스의 행동이 외부 요청이 아니라 이벤트에 의해 주도된다는 것입니다.

주문 서비스는 "결제 서비스에게 결제해 달라고 요청"하지 않습니다. 그냥 "주문이 생성되었다"는 이벤트를 발행하고, 결제 서비스가 스스로 반응합니다.

동기식 통신만 사용하던 시절에는 어떤 문제가 있었을까요? 가장 큰 문제는 연쇄 장애(Cascading Failure)였습니다.

결제 서비스가 응답하지 않으면 주문 서비스도 멈추고, 주문 서비스가 멈추면 프론트엔드도 장애 페이지를 보여줬습니다. 또한 서비스 간에 순환 의존이 발생하기도 했습니다.

A가 B를 호출하고, B가 C를 호출하고, C가 다시 A를 호출하는 식이었습니다. 배포 시에도 서비스 간 타이밍을 맞춰야 해서 독립 배포의 이점이 사라졌습니다.

바로 이런 문제를 해결하기 위해 이벤트 드리븐 방식이 도입되었습니다. 이벤트 드리븐 마이크로서비스에서는 서비스 간에 직접 호출이 없습니다.

모든 통신이 이벤트를 통해 이루어집니다. 결제 서비스가 죽어도 주문 서비스는 멀쩡합니다.

또한 각 서비스가 자신의 속도로 처리할 수 있습니다. 트래픽이 많은 서비스는 느리게 처리해도 되고, 여유 있는 서비스는 빠르게 처리합니다.

무엇보다 완전한 독립 배포가 가능해집니다. 이벤트의 형식만 맞추면 서비스를 언제든 자유롭게 배포할 수 있습니다.

위의 코드를 두 부분으로 나누어 비교해 보겠습니다. 첫 번째 OrderServiceSync는 전형적인 안티패턴입니다.

세 개의 서비스를 순차적으로 호출하며, 각 호출마다 네트워크 지연이 발생합니다. 세 번째 호출에서 장애가 나면 앞선 두 호출도 무효가 될 수 있습니다.

반면 OrderServiceEventDriven은 주문을 저장하고 이벤트만 발행한 뒤 즉시 응답을 반환합니다. 결제, 재고, 알림 처리는 각 서비스가 백그라운드에서 독립적으로 수행합니다.

실제 현업에서는 어떻게 활용할까요? 넷플릭스, 아마존, 우버 같은 기업이 대표적으로 이벤트 드리븐 마이크로서비스를 사용합니다.

넷플릭스의 경우, 사용자가 영상을 재생하면 수십 개의 마이크로서비스가 동시에 반응합니다. 시청 기록 저장, 추천 엔진 업데이트, CDN 캐시 갱신, 광고 타겟팅 등을 모두 이벤트로 처리합니다.

하나의 서비스가 장애가 나도 사용자는 영상을 끊김 없이 시청할 수 있습니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 모든 통신을 이벤트로 바꾸려는 것입니다. 사용자 정보 조회처럼 즉각적인 응답이 필요한 경우에는 동기식 통신이 더 적합합니다.

또한 이벤트 기반 시스템에서는 최종 일관성(Eventual Consistency)을 받아들여야 합니다. 이벤트를 발행한 직후에는 모든 서비스가 즉시 반영되지 않을 수 있습니다.

이 점을 비즈니스 요구사항과 잘 조율해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨와 함께 아키텍처 다이어그램을 다시 그리던 김개발 씨는 눈을 반짝였습니다. "이제 이해했어요!

마이크로서비스의 진정한 힘은 분리에 있는 게 아니라, 이벤트를 통한 느슨한 연결에 있었군요!" 이벤트 드리븐 마이크로서비스는 분산 시스템의 복잡도를 효과적으로 관리하는 핵심 패턴입니다. 그리고 이 패턴을 실제로 구현할 때 등장하는 것이 바로 스트리밍 플랫폼입니다.

실전 팁

💡 - 서비스 간 통신 설계 시, "이 요청은 즉시 응답이 필요한가?"를 먼저 질문하세요. 아니라면 이벤트 기반이 적합합니다

  • 이벤트 스키마를 처음부터 잘 정의하세요. 나중에 변경하는 것은 수많은 소비자에게 영향을 미칩니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 1/15편입니다

5. 스트리밍 플랫폼의 필요성

시스템이 점점 커지면서 김개발 씨는 새로운 고민에 빠졌습니다. "이벤트가 너무 많이 발생하는데, 이걸 다 저장하고 처리하려면 어떻게 해야 하죠?" 박시니어 씨가 답했습니다.

"이제 단순한 메시지 큐로는 부족해요. 스트리밍 플랫폼이 필요해요."

스트리밍 플랫폼(Streaming Platform)은 대량의 이벤트 데이터를 실시간으로 수집, 저장, 처리할 수 있는 통합 시스템입니다. 단순한 메시지 큐와 달리 데이터를 일정 기간 보존하고, 여러 소비자가 독립적으로 과거 데이터부터 읽을 수 있습니다.

마치 TV 생중계뿐만 아니라 다시보기 기능까지 제공하는 스트리밍 서비스와 같습니다.

다음 코드를 살펴봅시다.

// 단순 메시지 큐 vs 스트리밍 플랫폼 차이
// 단순 큐: 메시지를 읽으면 사라짐 (소멸형)
SimpleQueue queue = new SimpleQueue("clicks");
queue.send("user1 clicked product-A");
Message msg = queue.receive(); // 읽고 나면 사라짐, 다른 서비스는 못 읽음

// 스트리밍 플랫폼: 메시지가 보존됨 (로그 기반)
KafkaStream stream = new KafkaStream("user-clicks");
stream.produce("user1 clicked product-A");
stream.produce("user2 clicked product-B");

// 소비자 1: 실시간 알림 (최신부터)
stream.consume("notification-group", fromLatest());

// 소비자 2: 분석 (처음부터 전부)
stream.consume("analytics-group", fromBeginning());

// 소비자 3: 새 서비스 (어제 데이터부터)
stream.consume("new-service-group", fromTimestamp(yesterday));

이벤트 기반 아키텍처를 도입한 지 3개월, 시스템은 잘 돌아가고 있었습니다. 하지만 점점 새로운 요구사항이 늘어났습니다.

데이터팀에서 "지난달 주문 이벤트 전체를 분석하고 싶다"고 했고, 새로 합류한 ML팀에서 "실시간 추천을 위해 사용자 행동 이벤트 스트림이 필요하다"고 했습니다. 김개발 씨는 당황했습니다.

현재 사용 중인 메시지 큐는 메시지를 읽으면 즉시 삭제되기 때문에, 과거 데이터를 다시 읽을 방법이 없었습니다. "선배님, 과거 이벤트 데이터는 어떻게 보존하나요?" 박시니어 씨가 새로운 용어를 꺼냈습니다.

"지금 필요한 건 단순한 메시지 큐가 아니라 스트리밍 플랫폼이에요." 그렇다면 스트리밍 플랫폼이란 정확히 무엇일까요? 쉽게 비유하자면, 스트리밍 플랫폼은 마치 비디오 스트리밍 서비스와 같습니다.

생중계를 실시간으로 볼 수도 있고, 지난 에피소드를 처음부터 다시 볼 수도 있습니다. 친구와 같이 본 영화를 나중에 혼자 다시 볼 수도 있습니다.

중요한 점은 콘텐츠가 보존된다는 것입니다. 언제든 원하는 시점부터 볼 수 있습니다.

스트리밍 플랫폼에서 이벤트 데이터는 커밋 로그(Commit Log) 형태로 저장됩니다. 새로운 이벤트가 뒤에 계속追加되고, 과거 이벤트는 그대로 보존됩니다.

소비자는 자신의 위치(오프셋, Offset)를 기억하며 원하는 시점부터 읽을 수 있습니다. 단순 메시지 큐만 사용하던 시절에는 어떤 문제가 있었을까요?

가장 큰 문제는 데이터의 소멸이었습니다. RabbitMQ 같은 전통적인 메시지 큐는 소비자가 메시지를 확인(acknowledge)하면 즉시 삭제합니다.

과거 데이터가 필요해지면 별도의 데이터베이스에 저장해야 했습니다. 또한 새로운 소비자가 과거 데이터부터 처리할 방법이 없었습니다.

중간에 새로운 분석 서비스를 추가하면, 그 시점 이후의 데이터만 받을 수 있었습니다. 여러 팀이 같은 데이터를 각자의 목적으로 활용하는 것이 거의 불가능했습니다.

바로 이런 문제를 해결하기 위해 스트리밍 플랫폼이 등장했습니다. 스트리밍 플랫폼을 사용하면 이벤트 데이터가 일정 기간 보존됩니다.

7일, 30일, 또는 무제한으로 설정할 수 있습니다. 또한 각 소비자가 독립적으로 자기 위치를 관리합니다.

실시간 처리 서비스는 최신 데이터부터, 분석 서비스는 처음부터, 새로운 서비스는 특정 시점부터 읽을 수 있습니다. 무엇보다 데이터 파이프라인의 중앙 허브 역할을 합니다.

생산자는 한 번만 이벤트를 보내고, 수많은 소비자가 각자의 목적에 맞게 활용할 수 있습니다. 위의 코드를 비교해 보겠습니다.

첫 번째 SimpleQueue는 전통적인 메시지 큐입니다. receive()로 메시지를 읽으면 큐에서 삭제됩니다.

두 번째 KafkaStream은 스트리밍 플랫폼의 동작 방식입니다. 세 가지 소비자가 각각 다른 시작점에서 같은 스트림을 읽고 있습니다.

실시간 알림 서비스는 최신부터, 분석 서비스는 처음부터, 새 서비스는 어제부터 읽습니다. 모두 독립적이며 서로에게 영향을 주지 않습니다.

실제 현업에서는 어떻게 활용할까요? 우버는 스트리밍 플랫폼을 통해 실시간으로 수백만 건의 차량 위치, 배차 요청, 결제 이벤트를 처리합니다.

이 데이터를 동시에 실시간 배차 시스템, 수요 예측 모델, 사기 감지 시스템, 운영 대시보드가 활용합니다. 각 시스템이 독립적으로 같은 스트림을 소비하면서 각자의 역할을 수행합니다.

하나의 데이터 소스로 수많은 사용 사례를 충족하는 것이 스트리밍 플랫폼의 진정한 가치입니다. 하지만 주의할 점도 있습니다.

초보 개발자들이 흔히 하는 실수 중 하나는 데이터 보존 기간을 무한정으로 설정하는 것입니다. 보존 기간이 길어질수록 저장 공간이 늘어나고 비용이 증가합니다.

비즈니스 요구사항에 맞는 적절한 보존 기간을 설정해야 합니다. 또한 이벤트 스키마 관리가 중요합니다.

수많은 소비자가 같은 이벤트를 읽기 때문에, 스키마 변경 시 하위 호환성을 반드시 고려해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

스트리밍 플랫폼의 개념을 이해한 김개발 씨는 눈을 반짝였습니다. "이제 이해했어요!

메시지를 보냈다가 사라지는 게 아니라, 모두가 원할 때 읽을 수 있는 거대한 이벤트 저장소인 거군요!" 스트리밍 플랫폼은 현대 데이터 인프라의 핵심입니다. 그리고 이 스트리밍 플랫폼의 대표 주자가 바로 우리가 배울 Apache Kafka입니다.

실전 팁

💡 - 데이터 보존 기간은 비즈니스 요구사항에 맞게 설정하세요. 실시간 처리만 필요하면 짧게, 분석이 필요하면 길게 설정합니다

  • 컨슈머 그룹을 통해 같은 스트림을 여러 목적으로 독립적으로 소비할 수 있습니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 1/15편입니다

6. 전통적 메시지 브로커의 한계

김개발 씨는 기존 시스템에 사용 중인 RabbitMQ 설정 파일을 들여다보고 있었습니다. "근데 선배님, 이미 메시지 브로커가 있는데 왜 Kafka를 도입하려는 거죠?" 박시니어 씨가 심각한 표정으로 대답했습니다.

"현재 브로커로는 감당할 수 없는 한계에 도달했어요."

전통적인 메시지 브로커(Message Broker)는 RabbitMQ, ActiveMQ 같은 시스템으로, 소규모 메시징에는 훌륭하지만 대규모 데이터 스트리밍에는 근본적인 한계가 있습니다. 메시지 처리 후 즉시 삭제, 낮은 처리량, 제한적인 확장성 등의 문제가 있습니다.

마치 시내버스가 소규모 통근에는 좋지만, 수만 명의 인파를 옮기기에는 역부족인 것과 같습니다.

다음 코드를 살펴봅시다.

// RabbitMQ 전통적 브로커의 한계 시나리오
// 문제 1: 메시지 소멸 - 읽으면 사라짐
channel.basicConsume("orders", (tag, message) -> {
    processOrder(message); // 처리 후 메시지 삭제됨
    // 1시간 뒤 분석팀: "과거 주문 데이터가 필요해요" -> 이미 없음!
});

// 문제 2: 처리량 한계
// 초당 1만 건 -> OK, 초당 100만 건 -> 장애
// 파티셔닝 없이 단일 큐로 처리

// 문제 3: 순서 보장의 어려움
// 여러 소비자가 동시에 큐에서 메시지를 가져가면 순서가 섞일 수 있음

// Kafka의 해결책 (비교)
Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092,kafka2:9092");
props.put("acks", "all");               // 모든 복제본에 저장 보장
props.put("retention.ms", "604800000"); // 7일간 데이터 보존
props.put("compression.type", "lz4");   // 압축으로 저장 공간 절약
Producer<String, String> producer = new KafkaProducer<>(props);
// 초당 수백만 건 처리 가능, 데이터 보존, 순서 보장

김개발 씨의 회사에서 RabbitMQ를 도입한 지 2년이 되었습니다. 초기에는 잘 동작했습니다.

하지만 사용자가 10배로 늘어나면서 문제가 시작되었습니다. 이벤트 발생량이 초당 수만 건을 넘었고, 큐에 메시지가 쌓여 처리 지연이 발생했습니다.

가장 치명적인 것은 데이터팀의 요청이었습니다. "지난주 사용자 행동 데이터를 분석하고 싶은데, 데이터가 없네요?" 김개발 씨가 RabbitMQ 대시보드를 확인해 보니, 모든 메시지가 소비된 후 삭제되어 있었습니다.

"이걸 어떡하지..." 박시니어 씨가 오며 말했습니다. "전통적 브로커의 한계예요.

이제 Kafka를 도입할 때가 됐습니다." 그렇다면 전통적 메시지 브로커의 한계는 무엇일까요? 쉽게 비유하자면, 전통적 메시지 브로커는 마치 수기 메모장과 같습니다.

비서가 메모를 받아 처리하면 메모지는 버립니다. 나중에 그 메시지가 필요해져도 찾을 수 없습니다.

또한 한 명의 비서가 동시에 처리할 수 있는 양에 한계가 있습니다. 더 많은 메모가 몰리면 비서는 감당할 수 없습니다.

이에 비해 스트리밍 플랫폼은 디지털 아카이브와 같습니다. 모든 기록이 보존되고, 필요할 때 언제든 검색하고 읽을 수 있습니다.

전통적 브로커의 첫 번째 한계는 메시지의 소멸입니다. RabbitMQ에서 소비자가 메시지를 확인하면 해당 메시지는 큐에서 삭제됩니다.

과거 데이터를 다시 읽을 방법이 없습니다. 두 번째 한계는 처리량(Throughput)입니다.

설계 자체가 수만 건/초 수준에 최적화되어 있어, 수백만 건/초가 필요한 대규모 환경에서는 한계가 뚜렷합니다. 세 번째는 확장성입니다.

큐를 여러 서버에 분산하는 것이 구조적으로 어렵습니다. 이러한 한계는 실무에서 어떤 문제를 일으킬까요?

먼저 데이터 재처리가 불가능합니다. 버그가 발견되어 지난달 데이터를 다시 처리해야 한다면, 해당 데이터는 이미 삭제되었으므로 복구할 방법이 없습니다.

또한 다중 소비자 시나리오에서 어려움이 있습니다. 같은 데이터를 실시간 처리, 배치 분석, ML 모델 학습에 동시에 사용하려면 각각 별도의 큐와 별도의 메시지 전송 로직이 필요합니다.

시스템이 복잡해지고 비용이 증가합니다. 바로 이런 문제를 해결하기 위해 Apache Kafka가 등장했습니다.

Kafka는 커밋 로그(Commit Log) 기반 아키텍처를 채택하여 데이터를 일정 기간 보존합니다. 초당 수백만 건의 메시지를 처리할 수 있는 높은 처리량을 자랑합니다.

파티셔닝(Partitioning)을 통해 수평 확장이 용이합니다. 여러 소비자가 독립적으로 같은 데이터를 읽을 수 있습니다.

이러한 특징들로 인해 Kafka는 단순한 메시지 큐를 넘어 이벤트 스트리밍 플랫폼으로 자리 잡았습니다. 위의 코드에서 RabbitMQ와 Kafka의 차이를 비교해 보겠습니다.

RabbitMQ 코드에서는 basicConsume로 메시지를 소비하면 메시지가 즉시 삭제됩니다. 과거 데이터를 읽을 방법이 없습니다.

반면 Kafka 설정 코드를 보면 retention.ms로 7일간 데이터를 보존하도록 설정되어 있습니다. acksall로 설정하여 모든 복제본에 저장을 보장합니다.

compression.type으로 압축하여 저장 공간을 절약합니다. 이러한 설정 하나하나가 대규모 환경에서의 안정성을 담보합니다.

실제 현업에서는 어떻게 활용할까요? 링크드인(LinkedIn)은 Kafka를 직접 개발한 회사입니다.

2011년 당시 링크드인의 활동 추적 시스템은 전통적 메시지 브로커로는 감당할 수 없었습니다. 초당 수백만 건의 사용자 활동 이벤트를 실시간으로 처리하면서도, 검색 색인, 뉴스피드, 추천 시스템 등 여러 소비자가 같은 데이터를 독립적으로 활용해야 했습니다.

이 요구사항을 충족하기 위해 Kafka가 탄생했습니다. 현재는 Airbnb, Netflix, Uber, Goldman Sachs 등 전 세계 수천 개의 기업이 Kafka를 프로덕션에서 사용합니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 Kafka를 만능 해결책으로 생각하는 것입니다.

Kafka는 전통적 브로커를 완전히 대체하는 것이 아닙니다. 복잡한 라우팅이 필요한 경우에는 RabbitMQ가 더 적합할 수 있습니다.

또한 Kafka는 운영 복잡도가 높습니다. 클러스터 관리, 모니터링, 보안 설정 등 신경 쓸 부분이 많으므로, 도입 전에 팀의 역량을 충분히 고려해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 비교 설명을 듣고 김개발 씨는 고개를 끄덕였습니다.

"이제 왜 Kafka가 필요한지 이해했어요. 단순히 메시지를 전달하는 것을 넘어, 데이터를 보존하고 대규모로 처리하는 플랫폼이 필요했던 거군요!" 전통적 메시지 브로커의 한계를 이해하는 것은 Kafka를 제대로 활용하는 첫걸음입니다.

다음 카드뉴스에서는 드디어 Apache Kafka의 핵심 소개와 특징을 본격적으로 다룹니다. Kafka가 어떻게 이 모든 한계를 극복했는지, 그 설계 철학과 아키텍처를 자세히 살펴보겠습니다.

실전 팁

💡 - RabbitMQ와 Kafka는 경쟁 관계가 아닙니다. 각각의 장점이 있으므로 용도에 맞게 선택하세요

  • Kafka 도입 전에 "우리가 정말 대규모 스트리밍이 필요한가?"를 먼저 고민하세요. 과도한 엔지니어링은 독이 됩니다
  • 이 카드뉴스는 "Apache Kafka 완전 정복" 코스의 1/15편입니다. 다음 편에서는 Apache Kafka의 소개와 핵심 특징을 알아봅니다

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

#Kafka#MessageQueue#EventStreaming#PubSub#Microservices#Kafka,Messaging

댓글 (0)

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

함께 보면 좋은 카드 뉴스

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

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

Premium

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

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

Premium

Apache Kafka 소개 및 특징

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

Premium

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

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

Premium

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

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

Premium
이전
1/5다음