코더
jmeter(새로운 예약 시스템 적용) - [부하 테스트 낙관락/분산락] 본문
배경 :
전의 부하테스트에서 락을 걸지 않아 deadLock이 발생해 오류가 나는것을 확인했다.
낙관락,비관락,분산락을 적용 시켜 오류가 없이 예약을 만드려 한다.
과정 :
@Version
private Long version;
을 추가해서 낙관락을 걸어주었다.
문제점 :
오류는 발생하지 않았지만 공유락(s)이 배타락(x)을 가져오는 과정에서 데드락이 발생하였다.

2026-09-22T15:10:03.538+09:00 ERROR 11328 --- [dream_stadium_V2] [o-8080-exec-214] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update match_seat set capacity=?,is_reserved=?,match_id=?,seat_id=?,seat_type=?,version=? where match_seat_id=? and version=?]; SQL [update match_seat set capacity=?,is_reserved=?,match_id=?,seat_id=?,seat_type=?,version=? where match_seat_id=? and version=?]] with root cause


과정 :
비관락을 예약에 걸어주었다.
발생한 문제점 :
10명이 동일한 예약을 했을때 capacity 가 10이 주는게 아니라 1만줄었다.
과정 :
맨처음에 matchSeat과 reservationId를 fetch join시켜서 가져왔다
그 이유로는 manytoone의 기본적인 lazy로딩을 유지하면서 matchSeat관련한 로직만 eager로 가져오려 했기 때문이다.
그 결과, matchSeat이
이미 그 MatchSeat이 현재 영속성 컨텍스트에서 관리되고 있었다,.
즉, JPA의 PESSIMISTIC_WRITE는 트랜잭션 간 업데이트를 직렬화하는 락이기에
관리 엔티티를 계속 사용하게 되었고,
관리 엔티티의 상태를 DB에서 다시 읽어 최신화하는 것은 별도의 refresh 개념이 필요하다.
즉, 해결방안은
- refresh(,)를 사용한다.
- reservationId따로 matchSeatId따로 가져오는 로직을 만든다
의 해결방안이 있었고 쉽게 사용 가능한 2번 방법을 사용했다.
Reservation reservation2 = reservationRepository.findByIdWithMatchSeat(reservationId) .orElseThrow(() -> new BaseException(ErrorCode.RESERVATION_NOT_FOUND));*/
을
Reservation reservation = reservationRepository.findById(reservationId)
.orElseThrow(() -> newBaseException(ErrorCode.RESERVATION_NOT_FOUND));
로 변경을 해주고
MatchSeat matchSeat = matchSeatRepository.findByIdForUpdate(reservation.getMatchSeat().getId())
.orElseThrow(()-> newBaseException(ErrorCode.MATCH_SEAT_NOT_FOUND));
를 추가해준다.
Reservation customerReservation = Reservation.createCustomer(
reservation.getName(),
customer,
matchSeat,
cost,
true);
로 위에 matchSeat은 원래는 reservation.getMatchSeat()
이 로직이였으나, 변경을 해주었다.
만약 1번으로 하고 싶다면
Reservation reservation =
reservationRepository.findByIdWithMatchSeat(reservationId);
기존에 이것을 살리고
entityManager.refresh(reservation.getMatchSeat(),LockModeType.PESSIMISTIC_WRITE
);
이런식으로 refresh()를 사용하면 된다.
결과 : 455 → 155 로 300개의 예약에 관한, capacity가 정상적으로 줄어들었다.
