동시성 이슈

NNIIE·2022년 8월 15일
0

java

목록 보기
2/7
post-custom-banner

동시성이슈?

여러 스레드가 동시에 같은 인스턴스의 값에 접근하면서 발생하는 문제
스레드는 cpu 작업의 단위 인데, 멀티스레드 방식은 멀티태스킹 방식 중 하나의 코어에서 여러 스레드를 사용하여 작업을 처리하는 방식이다.
여러 스레드의 자원을 사용하기 때문에 성능상 강력하지만, 자원을 공유하기 때문에 이런 동시성 이슈도 발생하게 된다.
재고를 감소시키는 클래스를 실행할 때, 동시에 100군데에서 요청이 왔다고 가정해 보자.
재고를 1감소 시키는 로직이 실행되면 3초후에 재고가 1이 감소되는데 3초가 지나기전에 다른 스레드에서 공유데이터에 접근하면 문제가 생긴다.

레이스 컨디션(Race Condition)
둘 이상의 스레드가 공유 데이터에 액세스할 수 있고 동시에 변경하려고 할 때 발생하는 문제


둘 이상의 스레드 : 요청
공유 데이터 : 재고 데이터
동시에 변경하려고 할 때 : 업데이트 할때
발생하는 문제 : 값이 정상적으로 바뀌지 않는 문제

Application Level

Synchronized

자바의 synchronized 어노테이션을 활용하는 방법이다.

@Transactional
public synchronized void decrease(long id, long quantity) {

}
  • 단점 : 하나의 프로세스 안에서만 접근을 막아준다. 즉, 서버가 1대일때는 가능하지만 여러대의 서버를 사용할 경우 동일한 문제가 발생한다.
    인스턴스단위로 thread-safe 이 보장이 되고, 여러서버가 된다면 여러개의 인스턴스가 있는것과 같기 때문

Database Lock

Pessimistic Lock (exclusive lock)

@Lock(value = LockModeType.PESSIMISTIC_WRITE)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithPessimisticLock(long id);

database Lock 을 이용하는 방식

  • 장점
    • 충돌이 빈번하게 일어날 때 Optimistic Lock 보다 좋은 성능을 가진다.
    • Lock을 통해 업데이트를 제어하기 때문에 데이터 정합성이 보장된다.
  • 단점
    • 별도의 락을 잡기 때문에 성능 감소가 있을 수 있다.

Optimistic Lock

@Entity
public class Stock {
	@Version
	private long version;
}
@Lock(value = LockModeType.OPTIMISTIC)
@Query("select s from Stock s where s.id = :id")
Stock findByIdWithOptimisticLock(long id);
@Service
public class OptimisticLockStockFacade {

    public void decrease(long id, long quantity) throws InterruptedException {
        while (true) {
            try {
                optimisticLockStockService.decrease(id, quantity);
                break;
            } catch (Exception e){
                Thread.sleep(50);
            }
        }
    }
}

Lock 을 이용하지 않고 버전관리를 이용하는 방법

  • 장점
    • 별도의 락을 잡지 않기 때문에 충돌이 별로 없을 시 Pessimistic Lock 보다 성능상 이점이 있다.
  • 단점
    • 업데이트가 실패 했을때 재시도 로직을 직접 작성해 줘야 한다.
    • 충돌이 빈번하게 일어난다면 Pessimistic Lock 보다 성능이 떨어진다.

Named Lock

@Component
public class NamedLockStockFacade {

    public void decrease(long id, long quantity) {
        try {
            lockRepository.getLock(String.valueOf(id));
            stockService.decrease(id, quantity);
        } finally {
            lockRepository.releaseLock(String.valueOf(id));
        }
    }

}
// 실무에선 기존 entity 말고 별도의 jdbc등을 사용해야 한다
@Query(value = "select get_lock(:key, 1000)", nativeQuery = true)
void getLock(String key);

@Query(value = "select release_lock(:key)", nativeQuery = true)
void releaseLock(String key);
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void decrease(long id, long quantity) {
    Stock stock = stockRepository.findById(id).orElseThrow();
    stock.decrease(quantity);
}      

이름을 가진 메타데이터 Lock을 이용하는 방법 (분산Lock을 구현할 때 사용)

  • 장점
    • 타임아웃을 손쉽게 구현 가능
    • 데이터 삽입시에 정합성을 맞춰야 할 경우 적합
  • 단점
    • 트랜잭션 종료 시 Lock 해제를 안해주기 때문에 수동으로 작업해줘야 한다.
    • 세션관리를 잘 해서 사용해야 하기 때문에 구현방법이 복잡하다.
post-custom-banner

0개의 댓글