[ 보고서 ] Kafka 도입을 통한 선착순 쿠폰 발급 기능 개선
🟡 보고서 목적
이 보고서는 기존 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, 리밸런싱 이벤트 실시간 모니터링