
🚨 발급 제한 수량이 1000개인 쿠폰 테스트에서 1200건이 모두 발급되는 문제 발생
- 동시에 여러 스레드가 쿠폰 수량을 조회 → 동일 수량(예: 568개)을 기준으로 중복 발급
- 여러 스레드가 동일한 수량을 읽음 → 락이 제대로 작동하지 않음

Spring AOP는 기본적으로 프록시 (체인) 기반이며, 스프링 빈에만 AOP를 적용할 수 있음.
원래 객체를 프록시 객체가 감싸고 있는 형태. 따라서 접근을 제어하거나, 부가 기능을 추가할 수 있음. (관심사의 분리처럼.)
@Transactional도 AOP 기반, 즉 프록시 기반으로 동작한다.
프록시는 @Transactional, @WithRedissonLock을 감지해서 메소드 실행 전에 필요한 작업(락 획득, 트랜잭션 시작 등)을 함

초기에는 다음과 같은 AOP 실행 순서를 기대했다:
[예상]
🔵RedissonLock AOP → 🟢Transactional AOP → 🟡실제 쿠폰 서비스 메소드
그러나 실제로는 트랜잭션이 먼저 시작된 상태에서 RedissonLock AOP가 실행되고 있었다:
[실제]
🟢Transactional AOP → 🔵RedissonLock AOP → 🟡실제 쿠폰 서비스 메소드
이 사실은 아래와 같은 로그로 확인할 수 있었다:
2025-05-22T20:14:44.589+09:00 INFO 6168 --- [FourChak] [pool-3-thread-3] o.e.fourchak.aop.WithRedissonLockAspect : REDISSON AOP TX START: true
❗즉, 락 획득하기 전에 트랜잭션이 시작되는 것이 문제였다.
트랜잭션: DB에 대한 일관된 작업 보장 (입장 후 처리)
락: 동시 접근을 막는 선착순 입장권
따라서 다음과 같은 문제의 시나리오가 발생할 수 있음.
[1] 트랜잭션 A 시작
[2] 트랜잭션 A: 쿠폰 수량 조회 → 402
[3] 트랜잭션 B 시작
[4] 트랜잭션 B: 쿠폰 수량 조회 → 402 ← 💥 트랜잭션 A와 거의 동시에 시작
[5] 트랜잭션 A: 락 획득 성공
[6] 트랜잭션 A: 수량 차감 (402 → 401)
[7] 트랜잭션 A: 커밋
[8] 트랜잭션 A: 락 해제
[9] 트랜잭션 C 시작
[10] 트랜잭션 C: 쿠폰 수량 조회 → 401
[11] 트랜잭션 B: 락 획득 성공
[12] 트랜잭션 B: 이전에 읽은 402를 기준으로 수량 차감 → 401
[13] 트랜잭션 B: 커밋
[14] 트랜잭션 B: 락 해제
[15] 트랜잭션 C: 락 획득 성공
[16] 트랜잭션 C: 수량 차감 (401 → 400)
[17] 트랜잭션 C: 커밋
[18] 트랜잭션 C: 락 해제
트랜잭션은 데이터베이스에 대한 작업(읽기, 삽입, 수정, 삭제 등)을 하나의 작업 단위로 묶어 일관성과 원자성을 보장하지만, 동시에 여러 스레드에서 여러 트랜잭션이 병렬로 실행될 수 있다.
반면 락은 이러한 작업들이 경쟁 상태에 놓이지 않도록 제어하며, 특정 자원에 대해 한 번에 하나의 스레드만 작업할 수 있도록 보장한다.
그렇기 때문에 스레드 A가 먼저 락을 획득한 다음 트랜잭션 A를 시작해 쿠폰 수량(예: 402개)을 조회해야 함.
하지만 트랜잭션 A가 먼저 시작해 쿠폰 수량을 읽은 후에 락을 획득하는 구조라면
그 사이에 트랜잭션 B도 동일한 시점의 수량(402개)을 읽을 수 있고,
이후 트랜잭션 A가 수량을 차감하고 커밋하더라도, 트랜잭션 B는 여전히 과거의 402개를 기준으로 작업하게 되는 문제가 발생.
따라서 실제 수량보다 더 많은 유저 쿠폰이 발급되고, 쿠폰 수량이 제대로 차감되지 않는 문제가 발생한 것.
RedissonLockAOP에 @Order(0) 명시 → 락이 트랜잭션보다 먼저 실행되도록 우선순위 지정@Transactional은 @Order가 지정되어 있지 않아서 우선순위가 Integer.MAX_VALUE (가장 낮음)@Order가 숫자가 더 작은 쪽이 먼저 실행됨