[ 9주차 과제 ] 대용량 트래픽&데이터 처리

2025. 5. 24. 15:20향해99 8기

★★★ 정확히 이해가 안되어서 실제 내 코드를 빗대어서 정리

1. 주문-결제 핵심 로직 완료

  • PointPaymentHandler.payWithPoints(order, user) 내부에서
    1. 포인트 차감
    2. 주문 상태 → PAID
    3. (트랜잭션 커밋)

2. 이벤트 발행 (Producer)

  • 기존 : 트랜잭션 커밋 직후(afterCommit)  “커밋 직후 비동기 이벤트 발행”  
    파사드에 트랜잭션을 걸지 않았으므로, 핵심 로직 트랜잭션 커밋 시점을 기준으로 이벤트를 발행
    paymentEventPublisher.publish(new PointPaymentCompletedEvent(orderId, userId))
    @TransactionalEventListener(phase = AFTER_COMMIT) 로 “커밋 후”를 보장하고
    @Async 로 “비동기” 처리하도록 설정하여
    결제 로직과 완전히 분리된 상태에서 외부 플랫폼에 메시지를 전송
  • 이 호출이 Kafka Producer 역할을 해서
    • 토픽: order-paid
    • : orderId (파티션 결정용)
    • 페이로드: PointPaymentCompletedEvent(orderId, userId) 객체

 

★★★★★ 의문점

order-paid 토픽

키 = orderId → 각 주문 이벤트가 고유 파티션에 들어감

 

파티션 키 = orderId (or couponTemplateId)
같은 키의 메시지는 같은 파티션으로 가서 순차 처리 보장

(파티션)주문 1번 -  

(파티션)주문 2번 - 

동시성 이슈는 거의 없음  주문-결제에는 카프카를 사용할 필요가 없지 않나???

 

coupon-request 토픽

사용해야 한다면 선착순 쿠폰 예를 들면 

(파티션)쿠폰 1 - 선착순 100명

(파티션)쿠폰 2 - 선착순 200명 

 

키 = couponTemplateId → 같은 쿠폰 요청만 같은 파티션에 몰아 순차 발급

재고 초과 없이 선착순 처리 가능

 

동시성 제어 관점만 보면 주문-결제 이벤트에 KafKa가 필수는 아니다 

 

★★★ 그래도 쓰는 이유

1. 동시성 제어가 주목적은 아니지만, 비동기·분리·내구성·확장성 때문에 Kafka를 쓸 수 있음

2. 선착순 쿠폰: 동시성 제어(순차 발급) 가 주목적이므로, Kafka 파티션 큐가 아주 잘 맞음

 

9주차 과제(카프카)는 선착순 쿠폰으로 구현해보기

 

★★★★★ 선착순 쿠폰 - 카프카 도입

1. 그림 설명

  • 토픽: coupon-publish-request
  • 파티션: N개 (예: 3개)
  • 키 = couponId 로 메시지 발행
    • 같은 couponId 요청은 동일 파티션 → 순차 처리 보장
    • 다른 couponId는 서로 다른 파티션 → 병렬 처리 가능

2. 카프카 설정

- Kafka 브로커 띄우기
- Spring Boot 설정
- 필요 빈(Bean) 설정

- 테스트 전용 Kafka(TestContainers)

 

@EnableKafka 한 줄로

  • @KafkaListener 를 감지할 수 있게 해 주고,
  • spring-kafka 의존성과 spring.kafka.* 설정을 기반으로
    • ProducerFactory<String, ?>
    • KafkaTemplate<String, ?>
    • ConsumerFactory<String, ?>
    • ConcurrentKafkaListenerContainerFactory<String, ?>

카프카 돌아가는지 확인

docker-compose ps

docker logs -f server-java-kafka-1

 

3. 선착순 쿠폰 수정

1. 기존에 파사드에서 락획득, 중복-재고 검사, 발급 처리를 하였는데 전부 삭제하고 

2. 발급 요청 이벤트만 발행

3. 결론적으로 어떻게 구현하고 싶냐면
Kafka를 잘 써서 락 없이도 동시성 문제를 해결할 수 있고,
DB 커밋 성공 이후에만 다음 이벤트를 발행

 

★★★★★ 4. 선착순 쿠폰 구현 순서 (Kafka 2회 전송)

1. 요청 발행 (CouponRequestEvent)

  • 위치: CouponFacadeImpl.issueCoupon()
kafkaTemplate.send(
  "coupon-publish-request",   // 토픽
  couponId.toString(),        // 메시지 키
  new CouponRequestEvent(userId, couponId)  // 페이로드
);
  • 목적: 요청을 분산·순차 처리 큐에 올리기

2. 파티션 순차 처리

  • Kafka 브로커가 couponId 키로 같은 쿠폰 요청을 한 파티션에 모아 순차 처리

3. 비즈니스 로직 & 완료 이벤트 준비

  • 위치: @KafkaListener가 붙은 Consumer
  • 동작:
    1. couponService.issueCoupon(userId, couponId) 호출
    2. 내부에서 중복·재고·유효기간 검증 + DB 발급 처리
    3. 커밋 직후 afterCommit() 에서 오프셋 커밋(ack.acknowledge()) 준비

4. 완료 통보 (CouponIssuedEvent)

  • 위치: Consumer afterCommit() 콜백
kafkaTemplate.send(
  "coupon-issued",            // 토픽
  couponId.toString(),        // 메시지 키
  new CouponIssuedEvent(userId, couponId)  // 페이로드
);
ack.acknowledge();            // 오프셋 커밋
  • 목적: 발급 완료를 downstream(알림·통계·데이터 플랫폼 등)에 통보

첫 전송 (coupon-publish-request): 요청을 분산·순차 처리

두 전송 (coupon-issued): 성공 여부를 비즈니스 후속 시스템에 알림

 

★ 발제 정리

① 메시지 흐름

  • Producer: 메시지<key, message> 를 “토픽”에 발행
  • KAFKA(Broker): 받은 메시지 저장·관리 (클러스터로 고가용성·확장성)
  • Consumer: 저장된 메시지 읽어 처리
  • Offset 관리: Consumer가 처리 위치를 직접 추적·커밋 (중복/누락 방지)

② 토픽 & 파티션

  • Topic: 메시지 분류 단위
  • Partition: 토픽을 쪼갠 조각
    • 같은 파티션 안 메시지는 순차 보장
    • 파티션 수만큼 병렬 처리 가능 → 처리량↑
  • Partitioner:
    • 키가 있으면 hash(key) % 파티션수 → 키별 순서 유지
    • 키 없으면 Round-Robin 분배

 

 

③ 컨슈머 그룹 & 리밸런싱

  • Consumer Group:
    • 하나의 토픽을 그룹 단위로 구독
    • 그룹 내 파티션을 멤버에게 1:1 분배해 병렬 소비
  • Rebalancing:
    • 컨슈머 추가/제거, 파티션 변경 시 파티션 소유권 재조정
    • 중단 없이 소비자 수·파티션 수 최적화

 

④ 파티션↔컨슈머 매핑 & 복제

  • 매핑 시나리오:
    1. 멤버 < 파티션 → 일부 파티션 미소비
    2. 멤버 = 파티션 → 최적
    3. 멤버 > 파티션 → 일부 멤버 유휴
  • Replication:
    • 각 파티션에 Leader/Follower 복제본 생성
    • Leader 장애 시 Follower 승격 → 데이터 가용성 확보