어제 오늘 내일

[Spring Boot] "이벤트가 1초에 10만 개?" 대용량 처리를 위한 Kafka 연동 기초 (Producer/Consumer) 본문

IT/SpringBoot

[Spring Boot] "이벤트가 1초에 10만 개?" 대용량 처리를 위한 Kafka 연동 기초 (Producer/Consumer)

hi.anna 2026. 3. 28. 08:55

 

쇼핑몰에서 "주문 버튼"을 눌렀다고 가정해 봅시다.

  1. 주문 정보 DB 저장
  2. 재고 감소
  3. 결제 승인
  4. 배송 요청
  5. 사용자에게 알림톡 발송
  6. 데이터 분석팀에 로그 전송

이 모든 걸 한 번의 HTTP 요청(Controller) 안에서 순차적으로 처리하면 어떻게 될까요?
알림톡 서버가 1초만 늦게 응답해도, 사용자는 주문 완료 화면을 못 보고 뱅글뱅글 도는 로딩만 보게 됩니다.

오늘은 이 강한 결합(Tight Coupling)을 끊어내고, 초당 수십만 건의 데이터도 거뜬히 처리하는 메시지 큐의 제왕, Apache Kafka를 스프링 부트에 연동해 보겠습니다.


1. 왜 Kafka 인가요? (RabbitMQ vs Kafka)

"메시지 큐(Message Queue)라면 RabbitMQ도 있잖아요?"
맞습니다. 하지만 목적이 다릅니다.

  • RabbitMQ: 메시지를 안전하게 전달하고 삭제하는 것이 목표. (복잡한 라우팅 가능)
  • Kafka: 메시지를 로그(파일)로 저장하고, 엄청난 속도(Throughput)로 처리하는 것이 목표. (대용량 로그, 실시간 스트리밍에 특화)

카프카는 "생산자(Producer)"가 데이터를 던져놓으면, "소비자(Consumer)"가 자기가 처리할 수 있는 속도로 가져가는 구조입니다. 덕분에 서버가 터지지 않고 안정적으로 돌아갑니다.


2. 설치하기 (Docker Compose)

카프카는 설치가 좀 까다롭습니다. 주키퍼(Zookeeper)라는 코디네이터가 필요하기 때문인데요. Docker Compose를 쓰면 3분 컷입니다.

docker-compose.yml

version: '3'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka:
    image: confluentinc/cp-kafka:latest
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1

docker-compose up -d로 실행하면 준비 끝!


3. 스프링 부트 설정 (의존성 추가)

build.gradle

dependencies {
    implementation 'org.springframework.kafka:spring-kafka'
}

application.yml

spring:
  kafka:
    bootstrap-servers: localhost:9092 # 카프카 서버 주소
    consumer:
      group-id: my-group # 소비자 그룹 ID (중요!)
      auto-offset-reset: earliest # 처음부터 다 읽을래?(earliest) 아니면 지금부터 읽을래?(latest)
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

Tip: 카프카는 모든 데이터를 Byte 배열로 주고받습니다. 그래서 보낼 때(Serializer)와 받을 때(Deserializer) "이거 문자열이야!"라고 알려줘야 합니다.


4. 메시지 보내기 (Producer)

주문이 발생했을 때 "주문 생성됨"이라는 메시지를 카프카에 던져봅시다.

@Service
@RequiredArgsConstructor
@Slf4j
public class KafkaProducerService {

    private final KafkaTemplate<String, String> kafkaTemplate;

    public void sendMessage(String topic, String message) {
        // topic: 메시지 주제 (예: "order_create")
        // message: 보낼 데이터 (보통 JSON 문자열)
        kafkaTemplate.send(topic, message);
        log.info("Kafka Message Sent: topic={}, msg={}", topic, message);
    }
}

Controller에서 호출:

producer.sendMessage("order_create", "주문번호:1234, 사용자:홍길동");

이렇게 하면 컨트롤러는 카프카에 던지자마자 즉시 리턴합니다. (0.01초 소요) 알림톡이 늦게 가든 말든 사용자는 쾌적합니다.


5. 메시지 받기 (Consumer)

이제 누군가는 그 메시지를 받아서 뒷수습(알림 발송, 로그 저장)을 해야겠죠?

@Service
@Slf4j
public class KafkaConsumerService {

    // 1. topics: 구독할 주제
    // 2. groupId: 내 소속 그룹 (같은 그룹끼리는 메시지를 나눠서 처리함)
    @KafkaListener(topics = "order_create", groupId = "my-group")
    public void consume(String message) {
        log.info("Kafka Message Received: {}", message);

        // 여기서 무거운 작업을 수행합니다.
        // ex) 알림톡 발송 (3초 걸림)
        // ex) DB에 로그 저장
    }
}

서버를 켜두면 콘솔창에 Kafka Message Received: 주문번호:1234...가 찍히는 걸 볼 수 있습니다.


6. 핵심 개념: Consumer Group (확장성)

카프카의 꽃은 Consumer Group입니다.
주문이 폭주해서 1초에 1,000건이 들어오는데, Consumer 서버가 1대라서 처리가 느리다면?

그냥 똑같은 Consumer 서버를 2대 더 띄우세요. (groupId를 똑같이 해서)
카프카가 알아서 파티션(Partition)을 나눠서 병렬 처리를 시켜줍니다. 서버를 늘리는 만큼 처리 속도도 정비례해서 빨라집니다. 이게 바로 카프카가 대용량 처리에 강한 이유입니다.


마치며

오늘의 결론입니다.

  1. Kafka를 쓰면 "요청"과 "처리"를 분리(Decoupling)할 수 있다.
  2. Producer는 던지고 잊어버리고(Fire and Forget), Consumer는 자기 속도대로 처리한다.
  3. 서버를 늘리는 것(Scale-out)만으로 처리량을 무한대로 확장할 수 있다.

이제 여러분의 시스템은 트래픽이 몰려도 터지지 않고, 메시지를 차곡차곡 쌓아뒀다가 안전하게 처리할 수 있게 되었습니다.

다음 포스팅에서는 "RDB에서 LIKE %검색어% 쓰면 느려터져요..." 1억 건 데이터에서도 0.1초 만에 검색 결과를 찾아주는 Elasticsearch 검색 엔진 구축에 대해 알아보겠습니다.

도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊

반응형
Comments