🤔 왜 비관적 락을 선택했나?
- 주문번호는 매장/날짜별로 반드시 유일해야 하며,
- 여러 사용자가 동시에 주문을 생성할 때 중복된 번호가 발생하면 절대 안 됨
- 단 하나의 트랜잭션만이 해당 카운터 row에 접근하도록 제어해야 함
🛡️ 비관적 락(Pessimistic Lock) 적용 이유
- 비관적 락을 사용하면, 해당 row에 대한 트랜잭션이 완료될 때까지 다른 트랜잭션의 접근이 차단됨
- 즉, 동시에 여러 사용자가 주문을 해도 race condition을 원천적으로 예방
- 예외 상황(충돌 등)에 대한 처리도 단순해짐
- 주문 생성 트래픽이 아주 높지 않다면, 성능 저하 없이 데이터 무결성을 보장할 수 있음
⚙️ JPA에서 비관적 락 적용 방법
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT c FROM OrderDailyCounter c WHERE c.id = :id")
Optional<OrderDailyCounter> findByIdForUpdate(@Param("id") OrderDailyCounterId id);
- 서비스에서
findByIdForUpdate로 카운터를 조회하면, 해당 row에 쓰기 락이 걸림
- 락이 해제될 때까지(트랜잭션 종료 시점까지) 다른 트랜잭션은 대기
🚦 실제 적용 흐름
- 트랜잭션 시작
findByIdForUpdate로 카운터 row 조회(비관적 락)
- 카운터 값 증가
- 저장 및 트랜잭션 커밋
- 락 해제, 다음 트랜잭션 진행
✅ 장점
- Race condition 완벽 방지
- 예외 상황 처리 단순(재시도 로직 등 불필요)
- 데이터 무결성 보장
⚠️ 단점
- 트래픽이 매우 높은 상황에서는 대기/병목 발생 가능
- 트랜잭션이 길어지면 데드락 위험
- DB에 락이 많이 걸리면 성능 저하 가능
📝 결론
- 주문번호처럼 절대 중복이 허용되지 않는 데이터에는 비관적 락이 매우 효과적
- 단, 트래픽 규모와 시스템 특성을 고려해 적용 필요
- 트래픽이 급격히 늘어날 경우, Redis나 Sequence 등 다른 대안도 함께 검토할 것!
🛠️ 실제 코드 변경 상세 (feat/#71-orderNumber-race-condition)
1. Repository에 비관적 락 메서드 추가
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT c FROM OrderDailyCounter c WHERE c.id = :id")
Optional<OrderDailyCounter> findByIdWithPessimisticLock(@Param("id") OrderDailyCounterId id);
- 기존
findById 대신, PESSIMISTIC_WRITE 락을 거는 쿼리 메서드를 추가
2. 서비스 로직에서 락 메서드 사용
변경 전:
OrderDailyCounter counter = orderDailyCounterRepository.findById(id)
.orElseGet(() -> new OrderDailyCounter(id, 0));
변경 후:
OrderDailyCounter counter = orderDailyCounterRepository.findByIdWithPessimisticLock(id)
.orElseGet(() -> {
OrderDailyCounter newCounter = new OrderDailyCounter(id, 0);
orderDailyCounterRepository.saveAndFlush(newCounter);
return newCounter;
});
- findByIdWithPessimisticLock로 row-level lock을 적용하여 카운터를 조회
- 만약 해당 row가 없으면 새로 생성 후 즉시 저장(
saveAndFlush)하여 락이 적용된 상태로 카운터 관리
3. 카운터 증가 및 저장
변경 전/후 동일:
counter.increment();
orderDailyCounterRepository.save(counter);
4. 전체 흐름
- 트랜잭션 시작
- findByIdWithPessimisticLock로 카운터 row 조회(또는 신규 생성)
- 카운터 값 증가
- 저장 및 트랜잭션 커밋
- 락 해제, 다음 트랜잭션 진행
💡 변경 효과
- 동시성 문제(race condition) 완전 차단
- 주문번호 중복 없이, 안정적으로 주문 처리 가능
- 예외 상황 처리 단순화(재시도 로직 필요 없음)
- 트래픽이 아주 높지 않다면 성능 저하 없이 데이터 무결성 보장
변경후 결과

