현재 restful api 서비스에서는 매일 토큰이 2개보다 적을 경우, 2개로 초기화를 진행해 준다.
여기에 매일 5개씩 선착순 토큰을 발행해서 가져가게 구성하려고 한다.
public class Coupon {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Integer remainingQuantity;
@Column(nullable = false)
private Integer totalQuantity;
@Column(nullable = false)
@CreatedDate
private LocalDateTime createDate;
쿠폰 자체는 선착순으로 가져갈 수 있기에, 매일 하나의 쿠폰 데이터를 만들어서 발행량, 보유량을 관리하기로 결정했다.
문제는 멀티 스레드 환경에서 발생했다.
현재 쿠폰이 5개 남아있는 상태에서, 동시에 30명의 유저가 쿠폰을 획득한다고 가정하고 jmeter로 테스트를 진행.
테스트는 정상적으로 성공.
그러나 남은 쿠폰은 2개가 되었고, 현재 유저는 총 3개의 쿠폰을 획득한 상태이다.
심지어 데드락 발생.
30개의 스레드가 동시에 쿠폰 조회를 진행했고, 전부 5개의 데이터를 가진 상태에서 -1을 진행하니 데이터 정합성에 문제가 발생한 것.
결국 멀티 스레드에서도 데이터를 안전하게 동기화를 진행하여 정합성을 보장해야 한다.
가장 많이 보였던 synchronized를 적용해 보자.
synchronized를 사용하면, 하나의 스레드만 자원에 접근하도록 Lock을 제공한다. (즉 동시에 접근을 못하는 블로킹 방식이다)
public synchronized void acquisitionCoupon() {
메서드 자체에 synchronized를 걸고 진행해 보았다.
쿠폰도 5개 획득을 했고, 유저도 5개의 쿠폰을 잘 얻었다.
그러나 이 방법도 중간에 데드락이 발생했고, 쿠폰 획득 기록은 9개의 데이터가 쌓이는 문제가 발생했다.
문제는 스프링의 트랜잭션 때문에 발생했다.
기존에는 서비스를 호출하면 프록시 서비스를 불러서 트랜잭션을 위아래로 걸어주게 된다.
그런데 커밋 전에 다른 스레드에서 메서드를 호출하게 되면, 이때 문제가 발생하는 것.
트랜잭션을 제거하면 문제가 해결되지만 아래와 같은 문제가 발생한다.
- 싱크로나이즈는 서버가 여러 대일 경우 문제가 발생.
- 프로세스가 하나에서는 동기화를 보장하지만, 멀티 프로세스의 경우 접근을 막지 못한다.
그리고 실제로 싱크로나이즈는 잘 사용하지 않는 방법이기에 패스.
낙관적락, 비관적락
일단 둘의 사용 기준을 찾아보니 "얼마나 동시에 빈번하게 충돌이 일어나냐"의 차이로 나눈다고 한다.
내 기준에서 보면 쿠폰은 12시에 초기화되므로 12시에 동시 요청이 들어올 확률이 매우매우매우 높다.
이렇게 동시 수정이 빈번하면 비관적락을, 아니라면 낙관적락을 추천한다고 한다.(자세한 이유는 더 찾아봐야겠다)
그래서 나는 비관적락을 적용해서 데이터의 정합성을 보장해보려고 한다.
// 당일 쿠폰 조회
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select c from Coupon c where c.createDate >= :startOfDay and c.createDate <= :endOfDay")
Optional<Coupon> findByCoupon(@Param("startOfDay") LocalDateTime startOfDay,
@Param("endOfDay") LocalDateTime endOfDay);
단순하게 쿠폰 조회하는 쿼리에 @Lock를 걸어서 락을 걸었다.
30개의 요청 중 25개의 로그가 발생하고, 정확히 5개의 쿠폰 획득 기록만 남게 되었다.
비관적락을 통해, 실제 데이터에 Lock을 걸어 정합성을 맞춰보았다.
PESSIMISTIC_WRITE를 사용하면 배타적 잠금으로 동작한다.(다른 트랜잭션에서 읽기, 쓰기가 불가능)
'프로젝트 > RESTAPI 추천 서비스' 카테고리의 다른 글
50만개 데이터를 어떻게 페이징을 해볼까 (2) | 2024.04.07 |
---|---|
jpa환경에서 repository 테스트코드 작성해보자 (0) | 2024.04.04 |
너무 많은 요청을 제한해야 한다...(feat 세마포어로 스레드 제한) (0) | 2024.03.25 |
동료들의 불편함을 개선할 RESTFUL API 추천 서비스 (0) | 2024.03.24 |