💧 선착순 시스템
이번 쇼핑몰 프로젝트를 통해 다양한 도전을 해보았고 이번에는 선착순을 통해 할인 쿠폰을 발급하는 로직을 만들어 대규모 트래픽을 어떻게 조종할 수 있을까하고 생각해보았다.
대표적으로 배민의 쿠폰 할인이 가장 먼저 떠올랐다. 만약 밑의 사진 처럼 딱 정해진 시간에 선착순으로 할인권이 발급된다고 한다면 어떻게 될까?
이 때 굉장히 많은 트래픽이 모이게 되고 굉장히 많은 트래픽은 서버를 과부하되게 만들고 문제는 곧바로 서비스의 장애로 이어진다. 그래서 대용량 트래픽이 발생하면 기존의 서비스에 문제가 가지 않도록 처리를 해야한다.
줄을 세워보자
세상에 어딜가던 사람이 과하게 몰리면 좀 어렵게 말하면 공급에 비해 수요가 너무 많은 곳이라면 어디든 줄 세우기를 하는 것을 알 수 있다.
아주 큰 예로 최근에 푸바오가 중국으로 돌아가게 되면서 마지막으로 만날 수 있는 날에 굉장히 많은 사람들이 푸바오를 보기위해 동물원으로 모였다. 대기 시간을 보면 400분이라는 것을 볼 수 있다. 5분을 만나는 시간에 비교해보면 400분의 시간이 얼마나 긴 시간이고 그만큼 얼마나 많은 사람들이 모인지 알 수 있다.
중요한것은 과연 이 많은 사람들이 만약 줄을 서지 않고 그냥 마구잡이로 들어갔다면 그 대참사는 말로 할 수 없을 것이다.
이것을 토대로 나의 서비스도 마찬가지이다. 대규모로 들어온 사람들을 줄을 세운다면 훨씬 편하게 관리를 할 수 있다
어떻게?
일단 줄을 세우겠다는 의지는 생겼다 하지만 어떻게?가 가장 중요하다. 어떻게 줄을 세우는것을 형상화 할 것인가
알고리즘이 여기서 떠오른다.
- 줄을 세운다 = Queue 를 사용한다 또한 여기서 신청을 누른 순서대로 줄을 세워야 하기 때문에 우선순위 큐를 사용해야 한다는 것을 알 수 있다.
- 줄을 세우는 곳은 Scheduler를 사용하고 쿠폰이 끝날때까지 진행한다.
- 그리고 줄에 있는 사용자의 쿠폰 발급을 담당하는 것은 Service에 제작하여 스케쥴러에서 불러서 처리하는 것
추가 요구 사항
우리가 줄을 섰는데 앞에 사람이 얼마나 있는 건지 내가 얼마나 기다려야 하는건지 모른다면 굉장히 답답하지 않을까?
우리가 꽉 막힌 고속도로에서 화가 나는 이유가 있는 것이다
그래서 우리는 대기하는 사람에게 본인의 대기번호가 몇 번인지 알려주고자 한다.
그렇게 하면 서비스의 사용자가 훨씬 더 마음 편하게(?) 기다릴 수 있는 것이다.
기술의 선택, Redis 의 SortedSet
위에서 로직을 구현하고자 하는 내용을 구현하는 것은 굉장히 복잡하다. 우리는 줄서있는 사용자를 굳이 DB에 저장할 필요가 없다. 그래서 일일히 우선순위큐에 넣어주고 정렬하는 작업을 서비스에서 작업하고 실제로 발급받는 회원만 저장하면 되는데 이 작업이 생각보다 길어지고 느려진다. 그래서 나는 RAM을 통해 굉장히 빠른 처리속도를 가진 Redis를 사용해서 실시간으로 대용량처리를 더욱 빠르게 처리해보고자 한다
그리고 Redis에도 ZSET이라고 해서 SortedSet을 이용해서 Priority Queue 처럼 사용 할 수 있기 때문에 이 데이터를 이용해서 굉장히 빠르게 선착순 작업 처리를 해보고자 한다.
💧 플로우
내부 로직
우선 쿠폰이 등록되어 있다는 전제하로 시작된다
쿠폰이 등록될때 해당 쿠폰의 이벤트가 언제 시작될지도 저장이 된다. 그리고 스케쥴러를 통해 쿠폰의 상태를 확인하고 만약 startTime이 되면 Redis에 저장이 되면서 시작된다.
- 쿠폰 발급받기 버튼을 누르면 ZADD 를 통해 대기열의 마지막에 넣는다. 이때 사용자가 들어온 시간을 score로써 저장해서 score가 작은대로 정렬되기 때문에 Priority Queue처럼 사용할 수 있다.
- 그리고 1초마다 진행하는 대기열을 관리하는 스케쥴러가 작동한다
- 대기열의 앞에서부터 10명씩 쿠폰을 발급하고 10명을 대기열에서 삭제하는 enter() 메소드가 먼저 실행된다
- 나머지 인원의 대기번호를 전달해주기 위해 getOrder()메소드가 실행된다.
- 각 메소드에서는 쿠폰의 잔여 갯수가 없다면 해당 이벤트를 멈추는 isEnd메소드가 존재한다.
- 종료되면 현재 대기열에 있는 모든 인원에게 쿠폰 발급이 끝났다고 전달한다.
테스트 결과
테스트 전 상황
- 쿠폰이 등록되어 있다. ( 한정 갯수는 30개 )
- 쿠폰 이벤트가 열리는 상황에서 100명이 동시에 누른 상황
테스트 후
보면 위 테스트 전에 redis에 등록된 회원의 순서대로 쿠폰이 발급되었다는 것을 확인 할 수 있다. 또한 10명씩 처리하기 때문에 그 이후의 회원은 모두 대기번호를 전달하는 것으로 대체되는 것을 확인 할 수 있다.
만약 30개의 쿠폰이 모두 발급이 된다면 마감 소식을 알리는 것으로 이벤트가 종료되었음을 알린다.
💧 결과
서비스에서 발생할 수 있는 문제가 실제 환경에서도 나타나는 것을 볼 수 있었고 그 해결책 또한 거기서 아이디어를 얻을 수 있다는 것을 알게 되었다.
그리고 역시 Redis를 사용해서 굉장히 빠른 데이터 처리가 가능하다는 것을 알 수 있다. 역시 내부에서 로직을 진행하는 것 보다 연산처리속도가 굉장히 빠른 것에 큰 장점이 있다는 것이 좋다.
그리고 아직 UserService에 쿠폰이 등록된 회원에게 쿠폰을 발급(INSERT)하는 로직을 전달하지 않는다. 이는 다음 블로그에서 진행할 예정이다.
아쉬운 점은 화면단이 없어서 사용자에게 어떻게 전달할 지 고민이라는 것이다. 내부에서는 정상적으로 처리되는 것을 확인 할 수 있지만 각각의 사용자에게 자신의 대기열을 어떻게 전달할지도 고민해봐야 한다.
'프로젝트 > 항해99 개인 프로젝트' 카테고리의 다른 글
🚢 상품의 캐싱처리 (feat. 캐싱하기) (0) | 2024.06.01 |
---|---|
🚢 쿠폰 발급 시 사용자에게 전달하는 비동기통신 Kafka (0) | 2024.05.15 |
🚢 중간테이블로 인한 JPA의 N+1문제 (0) | 2024.05.09 |
🚢 MSA의 동시성 제어를 위한 Lock 사용(feat. Redis의 분산락) (1) | 2024.04.30 |
🚢 Feign Client 와 RestTemplate (0) | 2024.04.30 |