스프링 Race Condition 해결 방안
레이스 컨디션 해결방안
1. 메소드 선언 부에 synchronized 적용
사용방법
-> public synchronized void decrease(~~)
주의점 !
메소드에 Transactional 적용되어 있으면 안됌, Transactional 이 있으면 클래스를 프록시로 만들어 decrease 메소드 위 아래로 트랜젹서널을 적용하는데 decrease 후 트랜잭셔널 닫히기 전 사이에 다른 스레드가 접근 가능해짐
, 또한 서버 1대가 아니라 2대 이상일 경우에도 단일 스레드가 보장되지 않는다.
2. Pessimistic Lock
서버 1이 락을 걸고 데이터를 가져가면 다른 서버는 데이터를 가져갈 수 없다.
데이터는 락을 가진 서버만 접근하다.
Low나 Table 단위로 락을 걸 수 있음
사용방법
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
장점 : 충돌이 빈번하게 일어나면 Optimistic 보다 효율 적이다
단점 : 별도의 락을 걸기 때문에 성능을 좀 잡아먹는다
3. Optimistic Lock
실제로 Lock을 이용하지 않고 버전을 이용하여 데이터 정합성을 맞춤
데이터 업데이트 시 현재 버전이 맞는지 확인하고 업데이트를 함, 버전이 안맞으면 데이터베이스에서 데이터를 다시 읽고 업데이트를 해야함
사용방법
1. 해당 Entity에 private Long version 추가한 뒤 , @Version도 적용
2. @Lock(value = LockModeType.OPTIMISTIC)
레포지토리 메소드나 쿼리에 적용
3. 실패했을 때 재시도 하는 로직을 만들어 줘야함
클래스를 추가로 만들어서 해당 레포지토리 호출 메소드를 try catch (Exception e) 로 감싸 재 시도 하게끔, Thread.sleep(50) 등 term 줄 것
장점 : 별도의 락을 잡지 않아 성능은 더 좋다.
단점 : 빈번하게 충돌이 일어나면 Pessimistic 이 더 좋다. , 재시도 로직 추가해줘야함
4. Named Lock
이름을 가진 Metadata Lock을 사용함, 다만 Transactional이 끝난다 하여 Lock이 풀리지 않으니 별도로 해제해줘야함
사용방법
실무에선 별도의 데이터소스를 사용해야함
1. Repository에 이렇게
@Query(value="select get_lock(:key,3000)", nativeQuery = true)
void getLock(String key);
@Query(value="select release_lock(:key)", nativeQuery = true)
void releaseLock(Stirng key);
2. 실제 로직 위 아래로 getLock, releaseLock을 적용
클래스를 따로 만들거나 서비스에 추가하거나 해서 구현
try {
lockRepository.getLock(id.toString());
stockService.decrease(id,quantitiy);
} finally {
lockRepository.releaseLock(id.toString());
}
3.
부모의 트랜잭셔널과 별도로 적용 되어야 하므로 Transactional(propagation = Propgation.REQUIRES_NEW)
4. 같은 커넥션 풀을 사용할꺼면 커넥션 늘려서..