개발 관련 부가 지식/자바, 스프링

스프링 Race Condition 해결 방안

Developer-Choi 2023. 3. 2. 17:05
728x90
728x90

레이스 컨디션 해결방안

 

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. 같은 커넥션 풀을 사용할꺼면 커넥션 늘려서..