Programming/프로그래밍 내용 정리

[ 보고서 ] Kafka 도입을 통한 선착순 쿠폰 발급 기능 개선

domean 2025. 5. 29. 15:25

🟡 보고서 목적

 

이 보고서는 기존 Redis 단독 구조의 선착순 쿠폰 발급 시스템이 지닌 한계를 분석하고,

Kafka 도입을 통한 아키텍처 개선 방안을 제시하는 데 목적이 있습니다.

 

선착순 쿠폰 이벤트는

대용량 트래픽 대응, 발급 정합성(중복/재고 초과 방지), 안정적인 장애 대응 및 복구 능력을 요구합니다.

 

기존 Redis 중심 구조는 빠른 응답성과 강력한 동시성 제어가 강점이었습니다.

하지만 스케줄러 기반 비동기 처리로 인해 발급 시점이 불확실하고

결과 누락 시 보상이나 추적이 어려운 구조적 한계를 가지고 있습니다.

 

이를 해결하기 위해 Kafka 기반의 비동기 처리 구조를 도입하여 분산성, 확장성,

메시지 기반의 안정적인 발급 플로우를 확보하는 방안을 제안합니다.

 

 


🧾 시나리오

1. 이벤트 목적

  • 특정 시점에 선착순 쿠폰을 한정 수량만 발급하는 마케팅 이벤트입니다.
  • 이벤트 시작 직후 수많은 사용자가 동시에 몰릴 수 있는 구조입니다.
  • 중복 발급, 마감 이후 발급, 재고 초과 발급과 같은 오류는 방지되어야 하며
  • 발급이 즉시 이루어지지 않더라도 큰 문제가 되지는 않습니다. (최대한 빠른 발급은 필요)
  • 정확하고 일관된 발급 처리가 더 중요합니다.

2. 기술적 요구사항

항목 요구 조건
대용량 트래픽 대응 이벤트 시작 직후 수천~수만 건의 동시 요청을 처리할 수 있어야 함
정합성 보장 한 사용자당 한 장만 발급되도록 하고, 재고 초과 또는 마감 이후의 발급은 발생하지 않아야 함
중복 처리 방지 네트워크 재시도, 중복 클릭 등 다양한 중복 요청에도 일관된 결과가 보장되어야 함
처리 지연 허용 사용자 경험을 크게 해치지 않는 범위 내에서 일정 수준의 지연은 허용될 수 있음
모니터링 및 복구 가능성 발급 처리 중 문제가 발생하더라도 원인 추적 및 재처리가 가능해야 함

 

 

 

 


결론

 

처리 단계 Kafka 도입 여부 설명
1. 사용자 요청 수신 Redis 유지 Redis로 중복 및 재고 확인 후 즉시 응답
2. 발급 요청 대기열 저장
Redis 유지 Redis ZSet으로 선착순 큐 등록
3. 발급 요청 전송 ✅ Kafka Producer Redis ZPOP 대신 Kafka 토픽 발행
4. 발급 처리 로직 ✅ Kafka Consumer Kafka 메시지 수신 후 중복/재고 확인 및 발급 수행
5. 발급 성공 기록 및 이벤트 발행 ✅ Kafka Consumer DB 저장 후 발급 완료 이벤트 발행
6. 실패/예외 처리 ✅ Kafka DLQ DLQ로 이관 후 재시도 또는 분석 대응
7. 발급 완료 후
Redis 상태 갱신
✅ Kafka Consumer 이벤트 리스너 발급 완료 이벤트 기반으로 Redis 갱신 처리

 

 


 

[ 🔵 AS-IS ] Redis 를 활용한 선착순 쿠폰 발급

 


기존 시스템은 Redis를 중심으로 설계된 2단계 처리 구조입니다.
아래 이미지를 통해 전체 흐름을 표현하였습니다.

 

 

 

 

▶ 로직1: 사용자 요청 처리 (즉시 응답 중심)

  • 사용자가 쿠폰 발급을 요청하면
  • Redis에서 중복 발급 여부를 확인 (SISMEMBER)
  • 문제가 없으면 발급 요청을 대기열에 저장 (ZADD)
  • 이후 사용자에게는 요청 성공 응답을 즉시 반환
  • (ex) "쿠폰 발급 요청이 접수되었어요. 쿠폰 발급 완료 시 앱 푸시로 알려드릴게요!"

 

▶ 로직2: 실제 발급 처리 (스케줄러 기반)

  • 백그라운드 스케줄러가 일정 간격으로 대기열에서 요청을 꺼내고 (ZPOPMIN)
  • 중복 발급 여부를 확인한 뒤 (SISMEMBER)
  • 재고를 차감하고 (DECR)
  • 발급에 성공하면 발급자 정보를 기록하고 (SADD)
  • 쿠폰 발급 내역을 DB에 저장합니다.

👉 발급은 순차적으로 처리되며, 실질적인 결과는 나중에 확정됨

 

 

 

기존 구조의 장점

⚡ 빠른 응답 Redis는 인메모리 기반이라 사용자 요청에 빠르게 응답 가능
🔒 동시성 제어 Redis의 원자 연산 (SISMEMBER, ZADD, DECR)으로 정합성 확보
🧵 부하 완급 조절 스케줄러가 발급 요청을 적절히 소화 가능 (트래픽 버퍼 역할)
🎯 선착순 정렬 가능 ZSet의 score(timestamp) 기반 정렬로 정밀한 선착순 처리 가능

 

 

기존 구조의 한계

⏱ 발급 시점 불확실 스케줄러 기반 처리로 즉시 확정 어려움
🔄 보상 처리 어려움 Redis 큐 기반이라 실패한 요청의 리플레이, 재시도, DLQ 처리 구조가 없음

 

 


 

 

[  🟢 TO-BE ] Redis + Kafka 를 활용한 선착순 쿠폰 발급

 

 

개선된 시스템은 기존 Redis의 장점을 유지하면서

Kafka를 통합해 분산 처리, 확장성, 안정성을 강화한 구조입니다.

 

아래 이미지를 통해 전체 흐름을 표현하였습니다.

 

 

 

 

▶ 로직 1: 사용자 요청 접수 (Redis 기반 1차 필터링)

  • 중복 확인: Redis SISMEMBER를 통해 이미 발급된 사용자인지 확인
  • 대기열 등록: Redis ZADD로 발급 요청을 ZSet에 저장해 선착순 순서 보장
  • Kafka 발행: Redis ZPOP 대신 Kafka 토픽(coupon.issue.request)으로 메시지 발행
  • 즉시 응답: 요청 접수 결과를 사용자에게 빠르게 반환

 

▶ 로직 2: Kafka 기반 비동기 발급 처리

  • 이벤트 수신: KafkaConsumer가 요청 메시지 수신
  • 중복/재고 확인: DB에서 최종 중복 여부 및 재고 상태 확인
  • 발급 처리: 유효 요청에 대해 재고 차감 및 발급 이력 저장 (트랜잭션 처리)
  • 결과 이벤트 발행: 발급 결과에 따라 내부 이벤트 발행 (성공, 마감, 오류 등)

 

▶ 로직 3: 발급 결과 후처리 (이벤트 기반)

  • Redis 갱신: 발급 성공 시 Redis coupon:issued 추가, 마감 시 coupon:available 비활성화
  • 알림 전송: 앱 푸시 등 사용자 피드백 제공
  • 모니터링/로깅: Slack 등 관리자 알림 및 에러 로깅

 

▶ 예외 및 DLQ 처리

  • 자동 재시도: 처리 실패 시 KafkaConsumer가 설정된 횟수만큼 재시도
  • DLQ 전송: 재시도 실패 시 Dead Letter Queue로 이동
  • 문제 추적 및 재처리: DLQ 기반 문제 분석 및 수동/자동 재처리 가능

 

 

 

개선된 구조의 장점

항목 설명
📈 트래픽 분산 처리 Kafka 기반 비동기 구조로 대규모 동시 요청을 안정적으로 처리
♻️ 실패 복구 가능성 Kafka 재시도 및 DLQ를 통해 장애 발생 시 복구 가능
🧩 유연한 후처리 이벤트 기반 구조로 Redis 갱신, 알림 등 확장 용이

 

 


Kafka 시스템 설계 - 조금 더 나아가기

 

아래 내용은 예상되는 대규모 트래픽 환경에서

Kafka를 어떻게 효과적으로 설정하고 운영할 수 있을지에 대한 가정 기반의 설계입니다.

 

1. 파티션 수 설정

설계 가정: 이벤트 시작 직후 최대 5만 TPS 발생 가능성

설계 제안: 초기에는 6~10개 파티션으로 시작하고, 추후 필요에 따라 증설

근거:

  • Kafka는 파티션 하나당 약 1,000~10,000 TPS 처리 가능
  • 병렬 소비의 최소 단위는 파티션 수 → 컨슈머 확장성과 직접 연결됨
  • 사용자 ID를 파티션 키로 활용 시, 특정 사용자에 트래픽이 집중되면 핫스팟 발생 가능 → 균형 잡힌 분산 필요
  • 지나치게 많은 파티션은 오히려 클러스터 관리 복잡도 증가 유발 → 초기에는 단순성과 운영성 중심으로 설정

 

2. 컨슈머 그룹 구성

설계 제안: 단일 컨슈머 그룹(coupon-issue-group) 구성, 파티션 수에 맞춰 인스턴스 배포

근거:

  • Kafka는 파티션당 하나의 컨슈머 인스턴스만 메시지를 소비함
  • 예: 파티션 8개에 컨슈머 10개 배포 시 2개는 유휴 상태
  • 병렬성 = 파티션 수 → 컨슈머 수가 이를 초과해도 처리량 증가 없음
  • 발급 처리 전담 컨슈머 그룹과 후처리용 그룹(알림, Redis 갱신 등)을 분리해 로직 간 영향도 최소화

 

3. 리밸런싱 대응

발생 조건:

  • 컨슈머 인스턴스 수 변경
  • 장애 및 heartbeat 누락
  • 파티션 수 변경, poll 지연 등

대응 방안:

  • 이벤트 기간 동안 인스턴스 수 고정 (자동 스케일링 제한) → 확장 유연성은 낮지만 안정성 우선 확보
  • Kafka 2.4 이상이면 group.instance.id 활용 → incremental rebalancing으로 리밸런싱 중단 시간 최소화
  • 설정 예시:
    • session.timeout.ms: 30초
    • heartbeat.interval.ms: 10초
    • max.poll.interval.ms: 300000 (5분)
  • 롤링 배포 적용으로 리밸런싱 최소화

 

4. Kafka 주요 설정값

설정 키  권장 값  설명
acks all 리더 + ISR 복제 완료 후 응답 (데이터 유실 방지)
retries Integer.MAX 전송 실패 시 무제한 재시도 허용
delivery.timeout.ms 120000 전체 전송 타임아웃 제한 (2분)
max.in.flight.requests.per.connection 1~5 메시지 순서 보장 (idempotence=true일 때 5, 필요시 1로 낮춤)
enable.idempotence true 중복 메시지 방지 및 Exactly-Once 보장 가능
enable.auto.commit false 수동 커밋으로 처리 완료 후 오프셋 커밋
auto.offset.reset earliest 오프셋 유실 시 처음부터 재처리 시작
consumer.max.poll.records 10~100 poll당 메시지 수 제한 (트랜잭션 부하 제어 목적)

 

5. Dead Letter Queue (DLQ) 및 모니터링 전략

  • 실패 메시지는 coupon.issue.dlq 토픽으로 이관해 추후 수동/자동 재처리
  • Prometheus + Grafana로 Kafka 브로커 상태, 컨슈머 lag, 리밸런싱 이벤트 실시간 모니터링