«   2026/09   »
일 월 화 수 목 금 토
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
«   2026/09   »
일 월 화 수 목 금 토
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

코더

jmeter(새로운 예약 시스템 적용) - [부하 테스트 낙관락/분산락] 본문

카테고리 없음

jmeter(새로운 예약 시스템 적용) - [부하 테스트 낙관락/분산락]

자바스티안 2026. 9. 22. 18:17

배경 :
전의 부하테스트에서 락을 걸지 않아 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 개념이 필요하다.

즉, 해결방안은

  1. refresh(,)를 사용한다.
  2. 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가 정상적으로 줄어들었다.