쿠폰 발급 - 동시성 이슈

최명진·2024년 6월 16일
0

locust를 통해 부하테스트를 1000명의 유저가 쿠폰을 발급하는 api 요청을 보냈다. 내가 원하는건 500명의 선착순 쿠폰 제한을 두고, 그 이상 발급되지 않게 하는 것이 테스트의 주 목적이었다.
하지만 진행하고 나니 내가 생각했던 500명의 유저만 쿠폰을 발급받은게 아닌 4996명의 유저가 쿠폰을 발급받게 됐다.


이 문제는 쿠폰 발급 요청이 순차적으로 발생하는 경우는 문제가 없지만, 동시에 발생되는 경우는 발급된 쿠폰 수량을 확인할때 유저1, 유저2가 동시에 발급 수량이 같을 때 접근해서 같이 issue가 insert 된 문제이다. 이 문제를 해결하려면, 유저1이 발급된 쿠폰 수량을 확인할때 유저2가 발급 시도를 하면 LOCK으로 대기 후 유저1가 쿠폰 발급이 완료되고, 그 후 유저2가 쿠폰 발급을 진행한다. 대신 LOCK은 처리량의 병목이 발생시킨다는 단점이 있다.

먼저 테스트 해볼 것은 자바에서 제공하는 synchronized를 이용해 Lock을 테스트해본다.

확실히 아까 전보다는 락을 걸어서 TPS가 내려간 모습이다. 그럼 이제 500명만 발급이 되었겠지 하고 확인했더니.. 어라 그대로 2418개의 데이터가 있는 모습이다.


현재 sync의 문제점은 1번 요청이 로직을 마치고, 2번 요청이 락을 획득후 트랜잭션이 커밋되기 전에 쿠폰의 정보를 습득하기 때문에 전 처럼 같은 정보를 사용해서 문제가 생긴다. 해결 방법은

lock을 트랜잭션 커밋 후에 반납되게 순서를 보장하게 하면 된다.

트랜잭션 위에 synchronized를 배치하여 lock을 생성시켰다.


정상적으로 500명의 유저가 쿠폰을 발급받음을 확인할 수 있다.
문제는 synchronized는 자바 애플리케이션에 종속되기 때문에 여러 서버로 확장되면 락을 제대로 관리할 수 없다는 문제가 있다. 그래서 우리는 분산 락을 구현해야한다. 우리가 사용할 스택에 맞춰서 분산 락을 구현하는게 좋다.
우리가 사용하는 건 mysql, redis 이므로 각각 분산 락 구현을 해보도록 하겠다.
먼저 redis의 Redisson이라는 레디스 클라이언트로 분산락을 구현해 동시성을 제어해보겠다.




레디슨 클라이언트 Configuration과 DistributeLockExecutor를 추가하고 실제로 락이 걸려야하는 서비스에 적용시킨 후 정상적으로 분산 락이 적용됨을 확인했다.


성공적으로 500명이 등록된 모습을 확인할 수 있다.
레디스를 통해 적용하면 여러 서버가 가동되는 상황에서도 락을 잘 적용시킬 수 있다.
다음으로 mysql을 통한 분산락을 구현해보자.




redisson은 lockName을 통해 락을 지정하고, mysql은 lock을 걸 레코드가 존재한다는 차이점이 있다.

profile
주니어 백엔드 개발자 Choi입니다.

0개의 댓글