Spring/SpringBoot

SpringBoot 동시성 이슈 해결방법

SK_MOUSE 2023. 11. 26. 02:40
반응형

본 글은, 스프링부트 환경에서, 데이터에 동시에 접근하게 되는 경우 트랜잭션 동시성 이슈를 해결하는 방법( 참고강의 )에 대해 설명한다.

동시성 이슈 제어 방법

 


1. Synchronized 이용

Java에서 지원해주는 synchronized를 활용하여 한개의 쓰레드만 접근 가능하게 만들어준다.

//@Transcational
public synchronized void decrease(Long id, Long 수량){
	Stock stock = stockRepository.findById(id).orElseThrow();
    stock.decrease(수량);
    
    stockRepository.saveAndFlush(stock);
}

이때, Spring에서는 @Transactional을 사용하게 되면, 트랜잭션이 종료되기전에 다시 쓰레드(동시성으로 접근)호출하는 경우가 나타날 수 있음.

 

=> 문제점 : synchronized는 각 Process 안에서만 유효함. 서버가 2대 이상인 경우, 문제생김.

https://www.inflearn.com/course/동시성이슈-재고시스템


2. DB 기능 사용하기

- 충돌이 빈번하게 일어날것같으면 Pessimistic Lock(비관적 락) 추천.

- 충돌이 빈번하지 않을것같으면 Optimistic Lock(낙관적 락) 추천.

- 상세한 타임아웃과 데이터삽입시 활용하려면 Named Lock 사용.

https://velog.io/@hye_b/JPA-비관적-락과-낙관적-락

 

  • Pessimistic Lock : 실제 데이터에 Lock 걸어서 정합성 맞춤. 데드락 주의해야함
public interface StockRepository extends JpaRepository<Stock, Long> {
	@Lock(LockModeType.PESSIMISTIC_WRITE)//<---이 어노테이션 기능을 활용
	@Query("select s from Stock s where s.id= :id")
 	Stock findByIdWithPessimisticLock(Long id);
}
  • Optimistic Lock :버전을 이용하여 정합성을 맞춤. 먼저 데이터를 읽은 후 update 수행시, 읽은 버전이 맞는지 확인. 내가 읽은 버전에서 수정사항이 생긴 경우, application에서 다시 읽은 후 수행.
public interface StockRepository extends JpaRepository<Stock, Long> {
	@Lock(LockModeType.OPTIMISTC)//<---이 어노테이션 기능을 활용
	@Query("select s from Stock s where s.id= :id")
 	Stock findByIdWithOptimisticLock(Long id);
}

 

@Component
@Requiredargsconstructor
public class OptimisticLockStockFacade{
	private final OptimisticLockStockService optimisticLockStockService;
    
   public void decrease(Long id, Long quantity){
   		while(true){//실패할때 재시도를 계속 해야함.
        	try{
            	optimisticLockStockService.decrease(id, quantity);
                break;//<--성공했다면 while문을 나오기
            }catch (Exception e){
            	Thread.sleep(50);//<--실패하면 50ms동안 쉬었다가 다시 시도
            }
        }
   }
}

장점) Lock을 잡지 않으므로 성능상 이점이 있다.

단점) 충돌이 많이 일어날 시(=update 많은 경우), 재시도 로직(개발자가 일일이 작성해야된다는 단점 포함)을 타야하므로 오래걸릴 수 있음.

 

  • Named Lock : 이름을 가진 metadata locking. 이름을 가진 lock 획득 후, 해제할때까지 다른 세션은 이 lock을 획득 할 수 없음.(*주의할점 - 트랜잭션이 종료될때 lock이 자동 종료되지 않으므로 별도의 명령어로 해제하거나 선점 시간이 끝나야 해제됨)
public interface StockRepository extends JpaRepository<Stock, Long> {
	@Lock(value = "select get_lock(:key, 3000)", nativeQuery = true)
    void getLock(String key);
    
    @Query(value = "select release_lock(:key)", nateiveQuery = true)
    void releaseLock(String key);
}

 

@Component
@Requiredargsconstructor
public class NamedLockStockFacade{
	private final LockRepository lockRepository;
    private final StockService stockService;
   
   @Transactional
   public void decrease(Long id, Long quantity){
   		try{
        	lockRepository.getLock(id.toString());
            stockService.decrease(id,quantity);
        }finally{
        	lockRepository.releaseLock(id.toString());
        }
   }
}

 

장점) Timeout 손쉽게 구현. 데이터 삽입 시, 정합성을 맞추는 데에 사용할 수 있음.

단점) 실제로 사용시, 구현 방법이 복잡할 수 있음.(트랜잭션, Lock해제 주의해서 사용해야함)

 


3. Redis 이용하기

=> 실무에서는 재시도가 필요하지 않은 Lock은 Lettuce, 필요한 Lock은 Redission을 활용함.

 

  • Lettuce
장점 단점
구현 간단 spin lock 방식이기때문에 동시에 많은 쓰레드가 Lock 획득 대기상태라면, redis에 부하가 갈 수 있다
Spring data redis 사용시, 별도 라이브러리 사용하지 않아도됨  

- setnx 명령어 활용하여 분산락 구현

- spin lock 방식

 

  • Redission
장점 단점
Lock 획득 재시도를 기본으로 제공 lock을 라이브러리 차원에서 제공해주기 떄문에 사용법 공부가 필요함
pub-sub 방식 구현으로 lettuce와 비교했을 때, redis에 부하가 덜 간다  

- pub-sub 기반으로 Lock 구현 제공

 

 

Redis 부분 소스 부분은 이 링크 참고

 

[Spring boot & java] 동시성 이슈 (feat. Redis)

Issue 회사에서 프로젝트를 진행중에 있었던 동시성 이슈에 대한 포스팅이다. spring boot 로 진행했던 프로젝트이므로 java 의 멀티 쓰레드 환경이다. 수강신청 인원에 리밋이 걸려있었는데 인원을

velog.io

 

 

 


동시성 해결 : MySql vs Redis

 

MySql Redis
이미 Mysql 사용시, 별도 비용없이 사용 가능 활용중인 Redis가 없다면 별도 구축비용 및 인프라 관리비용 발생
어느정도의 트래픽까지는 문제없이 활용 가능 Mysql보다 성능이 좋음
Redis보다 성능이 좋지 않음  

 

반응형