올리브영 테크 블로그를 보다가 대규모 트래픽 상황에서 선착순 쿠폰 발급 시스템의 문제에 대해 궁금해졌다.
티켓 오픈 시간에 많은 사용자가 동시에 티켓 예매를 처리하면 어떠한 문제가 발생할까? 그리고 그 문제를 어떻게 해결할 수 있을까? 여기서 가장 큰 문제는 요청 순서에 따라 티켓을 발급하되, 발급 가능 수량만큼만 발급해야 된다는 점이다.
이번 칼럼에서는 해당 문제를 어떻게 해결할 수 있는지 공유해보고자 한다.
아래 코드는 티켓 예매 상황을 간단하게 구현한 예제 코드이다.
IssueCouponService
@Service
@RequiredArgsConstructor
public class IssueCouponService {
private final IssueCouponRepository issueCouponRepository;
private final MemberRepository memberRepository;
private final CouponRepository couponRepository;
@Transactional
public IssueCouponResponse issueCoupon(IssueCouponRequest issueCouponRequest, MemberDetails memberDetails, Long couponId) {
Member member = memberRepository.findById(memberDetails.getMemberId()).orElseThrow(
() -> new MemberNotFoundException(ErrorCode.NOT_FOUND_MEMBER)
);
Coupon coupon = couponRepository.findById(couponId).orElseThrow(
() -> new CouponNotFoundException(ErrorCode.NOT_FOUND_COUPON)
);
coupon.increaseIssueAmount();
IssueCoupon issueCoupon = issueCouponRepository.save(issueCouponRequest.toEntity(member, coupon));
return IssueCouponResponse.fromEntity(issueCoupon);
}
}
아래는 Coupon 도메인 코드 중 일부이다.
@Entity
@Table(name = "coupons")
@SuperBuilder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Coupon extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long couponId;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer totalCount;
@Column(nullable = false)
private Integer issuedCount = 0;
public void increaseIssueAmount() {
if (issuedCount >= totalCount) {
throw new CouponSoldOutException(ErrorCode.COUPON_SOLD_OUT);
}
issuedCount++;
}
}
모든 사용자가 순차적으로 쿠폰 발급을 요청한다고 가정하자.
아래 테스트에서는 10장의 티켓을 30명의 사용자가 예매하는 상황을 가정했다.