FCFS
Redis
Kafka
Coupon
선착순 이벤트 시스템 개발하기
- 발생할 수 있는 문제
- 쿠폰을 100개만 발급해야 하는데, 쿠폰이 100개보다 많이 발급되었다
- 트래픽이 급증해 이벤트 페이지 접속이 안된다
- 이벤트랑 전혀 상관없는 페이지들도 느려졌다
- 해결책
- 트래픽이 몰렸을 때 대처하는 방법 적용
- Redis를 사용해 쿠폰 발급 개수를 보장하기
- Kafka를 활용해 다른 페이지에 미치는 영향을 줄이기
Redis로 Race Condition 해결하기
- 기존 락 활용의 문제
- 선착순 쿠폰 발행은 쿠폰 개수에 대한 정합성을 요구함
- 락 활용은 요구사항의 임계영역이 길어서 성능 불이익 발생
- 발급된 쿠폰 개수를 가져오는 것부터 쿠폰 생성까지 락을 걸면 임계영역이 길어져서 성능 불이익
- 해결책: 레디스는 싱글 스레드로 동작해 Race Condition 해결
- 애플리케이션의 모든 스레드는 언제나 최신 값을 가지게 됨
- 레디스
incr
명령어 활용
- key의 value를 1씩 증가시킴
-
성능이 매우 빠른 명령어
- 남은 문제점
-
쿠폰 발급 개수가 많아질수록 RDB에 부담을 주어 서비스 지연 및 오류 발생
- 짧은 시간 내 많은 요청 -> DB 서버 리소스 과부하
- 쿠폰 전용 DB가 아니라면 다른 서비스에도 영향
- e.g. MySQL이 1분에 100개 Insert가 가능하다고 가정
-
-
-
- -> 쿠폰 생성으로 인해 100분 이후에 주문 및 회원가입 요청이 처리됨
- -> 심지어 보통은 타임아웃이 있으므로, 쿠폰 생성 일부분과 주문 및 회원가입 처리 실패
Kafka로 처리량 조절하기
- 데이터 정합성은 Redis로 이미 확인했으므로, 쿠폰 생성만 처리
- Kafka
- 분산 이벤트 스트리밍 플랫폼
- 이벤트 스트리밍: 소스에서 목적지까지 이벤트를 실시간으로 스트리밍하는 것
- Producer(소스) - Topic(큐) - Consumer(목적지)
- 장점
- API에서 직접 생성하는 것에 비해 처리량 조절이 가능 -> DB 부하 감소
-
큐를 사용하므로 이벤트가 하나가 끝난 후 다음 이벤트가 처리되어 DB에 한 번에 쏠리지 않음
- 단점
- 이벤트 생산과 이벤트 처리는 약간의 텀이 발생
- Producer의 이벤트 생산은 매우 빠르지만, Consumer는 이벤트를 처리하느라 시간차 발생
부록: 쿠폰 1인당 1개로 제한하기
- DB 레벨 제한: Unique key 사용하기
-
userId
, couponType
에 유니크 제약 조건 걸기
- 문제점: 보통 서비스는 한 유저가 같은 타입의 쿠폰을 여러개 가질 수 있으므로, 실용적이지 않음
- 락 범위 넓혀서 쿠폰 발급 여부 조회해 판단하는 로직 추가하기
- 쿠폰 발급 여부 판단 로직
- 쿠폰 발급 여부 조회:
select * from coupon where userId = ?
- 쿠폰이 이미 있다면 발급하지않고, 미지급일 때만 발급
- 락 범위: 쿠폰 발급 여부 조회 ~ Redis 동시성 체크 ~ 카프카 이벤트 생산
- 문제점
- 생산자와 소비자의 시간차 때문에 쿠폰이 여러 개 발급될 수 있음
- 소비자에서 아직 발급 중인데 유저의 쿠폰 발급 요청이 한 번 더 온다면?
- API에서 쿠폰 생성까지 하더라도 락 범위가 너무 넓어 성능 저하 발생
-
Set 자료구조 사용하기 (권장)
-
userId
를 Set에 저장하면 쿠폰 발급 여부를 바로 알 수 있음
- Redis도 Set을 지원하므로 활용
부록: Consumer 예외 처리하기
- 문제: Consumer에서 예외가 발생하면, 발급된 쿠폰 개수는 올라갔는데 쿠폰은 발급되지 않은 상황 발생
- 즉, 100개보다 적은 쿠폰이 발생하는 상황 발생 가능
- 해결책: Consumer에서 예외 발생 시, 백업 데이터(
FailedEvent
테이블)와 로그 남기기
- 추후 배치 프로그램으로 주기적으로 실패한 이벤트를 다시 처리해 쿠폰 발급