동시성 문제 - 낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)

김건우·2023년 1월 23일
1
post-thumbnail

이전 블로그에서는 자바 Synchronized를 이용한 동시성 이슈 해결방법을 살펴보았습니다. 하지만 서버가 여러대일 경우, 다른 서버에서 가변 공유데이터에 접근하는 것을 막을 수 없는 문제가 존재하였습니다.

1.Pessimistic Lock

  • 실제로 데이터에 Lock을 걸어서 정합성을 맞추는 방법입니다.
  • exclusive lock(베타적 잠금) 을 걸게되면 다른 트랜잭션에서는 lock 이 해제되기전에 데이터를 가져갈 수 없게됩니다.
  • 자원 요청에 따른 동시성문제가 발생할 것이라고 예상하고 락을 걸어버리는 비관적 락 방식입니다.
  • 하지만, 데드락이 걸릴 수 있기 때문에 주의하여 사용해야합니다.

    예를들어 Server 1 DB 데이터를 가져올 떄, Lock 을 걸어버리면, 다른 서버에서는 Server1의 작업이 끝나 락이 풀릴 때 까지, 데이터에 접근하지 못하게 됩니다.
    🔍결국 Pesimistic Lock이란, 데이터에는 락을 가진 스레드만 접근이 가능하도록 제어하는 방법입니다.

pessimistic Lock을 이용한 repository

public interface StockRepository extends JpaRepository<Stock, Long> {

    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query("select s from Stock s where s.id = :id")
    Stock findByWithPessimisticLock(final Long id);
}

Pessimistic Lock 의 장점

  • 충돌이 빈번하게 일어난다면 롤백의 횟수를 줄일 수 있기 때문에, Optimistic Lock 보다는 성능이 좋을 수 있습니다
  • 비관적 락을 통해 데이터를 제어하기 때문에 데이터 정합성을 어느정도 보장할 수 있습니다.

Pessimistic Lock 의 단점

  • 데이터 자체에 별도의 락을 잡기때문에 동시성이 떨어져 성능저하가 발생할 수 있습니다.
  • 특히 읽기가 많이 이루어지는 데이터베이스의 경우에는 손해가 더 크다고 합니다.
  • 서로 자원이 필요한 경우, 락이 걸려있으므로 데드락이 일어날 가능성이 있습니다.

2.Optimisitc Lock

낙관적 락(Optimisstic Lock)은 DB의 Lock을 사용하지 않고 Version관리를 통해 애플리케이션 레벨에서 처리합니다.

  • 실제로 Lock 을 이용하지 않고 버전을 이용함으로써 정합성을 맞추는 방법입니다.
  • 먼저 데이터를 읽은 후에 update 를 수행할 떄 현재 내가 읽은 버전이 맞는지 확인하며 업데이트 합니다.
  • 자원에 락을 걸어서 선점하지 않고, 동시성 문제가 발생하면 그때가서 처리하는 낙관적 락 방식입니다.
  • 내가 읽은 버전에서 수정사항이 생겼을 경우에는 application에서 다시 읽은 후에 작업을 수행하는 롤백 작업을 수행해야 합니다.

Optimisitc Lock의 과정
1. 서버 1이 version1 임을 조건절에 명시하면서 업데이트 쿼리를 날립니다🔽

2. version1 쿼리가 업데이트 되어서, 디비는 version 2가 됩니다🔽

  1. server2 가 version1 로 업데이트 쿼리를 날리면 버전이 맞지않아 실패합니다.

  2. 쿼리가 실패하면 server2 에서 다시 조회하여 버전을 맞춘 후 업데이트 쿼리를 날리는 과정을 거칩니다.

Optimistic Lock 은 실제 락을 사용하지 않고, 버전을 이용해서 락과 유사한 과정을 가지는 논리적인 락이라고 생각이 듭니다

stock 클래스에 version 컬럼 추가하기

  • @Version을 사용하여 버전 컬럼을 추가해야 합니다.

    @Version을 명시할때는 다음과 같은 주의사항이 있습니다.

    1. 각 엔티티 클래스에는 하나의 버전 속성 만 있어야합니다.
    2. 여러 테이블에 매핑 된 엔티티의 경우 기본 테이블에 배치되어야합니다.
    3. 버전에 명시할 타입은 int, Integer, long, Long, short, Short, java.sql.Timestamp 중 하나 여야합니다.
@Entity
@Getter
@NoArgsConstructor
public class Stock {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private Long productId;

    private Long quantity;

	//버전 칼럼 추가
    @Version
    private Long version;

   

}
public interface StockRepository extends JpaRepository<Stock, Long> {
 //pessimistic Lock
    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query("select s from Stock s where s.id = :id")
    Stock findByWithPessimisticLock(final Long id);

	//Optimistic Lock
    @Lock(value = LockModeType.OPTIMISTIC)
    @Query("select s from Stock s where s.id = :id")
    Stock findByWithOptimisticLock(final Long id);

}
  • ✅엔티티를 수정할 때 마다 버전이 하나씩
    자동으로 증가합니다.

Optimistic Lock 의 장점

  • 충돌이 안난다는 가정하에, 별도의 락을 잡지 않으므로 Pessimistic Lock 보다는 성능적 이점을 가집니다.

Optimistic Lock 의 단점

  • 업데이트가 실패했을 떄, 재시도 로직을 개발자가 직접 작성해 주어야 합니다.
  • 충돌이 빈번하게 일어나거나 예상이되면, 롤백처리를 해주어야하기 때문에 Pessimistic Lock 이 더 성능이 좋을 수도 있습니다.

다음 블로그에서는 Redis를 이용한 동시성 이슈 해결에 대해 알아보겠습니다.

profile
Live the moment for the moment.

0개의 댓글