-
[JPA] Optimistic, Pessimistic LockJAVA/SPRING 2024. 8. 10. 16:06728x90
낙관적 락(Optimistic Lock)
낙관적 락은 사전에 테이블 로우에 락을 거는 방식이 아닌 충돌이 발생했을 경우에 대비하는 방식입니다.
테이블에 특정 컬럼을 추가하여 조회 시점의 값과 저장하려는 시점의 값이 동일한지 확인하여 충돌을 방지하도록 동작합니다.
이는 충돌 발생 빈도수가 낮은 상황에 적합하며 지속적인 락으로 인한 성능 저하를 막을 수 있습니다.아래는 Spring boot, JPA에서 낙관적 락을 적용하는 예시입니다.
Entity
@Entity class UserEntity( id: Long, name: String, version: Int, ) { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = id protected set var name: String = name protected set @Version var version: Int = version protected set fun updateName(name: String) { this.name = name } }
Service
@Service class UserService( private val userEntityRepository: UserEntityRepository ) { @Transactional fun update(id: Long, name: String) { val user = userEntityRepository.findById(id) .orElseThrow { RuntimeException("User not found") } user.updateName(name) } }
테스트 케이스
class UserServiceTest @Autowired constructor( private val userService: UserService ) { @Test fun `10명 동시접근시 낙관락 테스트`() { val executorService = Executors.newFixedThreadPool(10) val latch = CountDownLatch(10) for (i in 1..10) { executorService.submit { val name = (1..100).random().toString() try { userService.update(1, name) } catch (e: Exception) { println(e) } finally { latch.countDown() } } } latch.await() } }
위 테스트 케이스는 10명의 사용자가 동시에 접근하여 리소스를 수정하려는 상황을 연출했습니다.
테스트를 실행시 의도한대로 총 9번의 ObjectOptimisticLockingFailureException이 발생했습니다org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1] org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1] org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value ma pping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1] org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1] org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1] org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1] org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1] org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1] org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
비관적 락(Pessimistic Lock)
비관적 락은 변경하려는 테이블 로우에 락을 걸어 다른 트랜잭션에서 접근을 하지 못하도록 막아 충돌을 방지하는 방식입니다.
낙관적 락에 비해 설정에 따라 데이터에 접근조차 하지 못하여 성능상에서는 좋지 않습니다.
JPA에서 제공하는 비관적 락은 PESSIMISTIC_READ, PESSIMISTIC_WRITE가 있습니다.- PESSIMISTIC_READ: 공유락으로 락이 걸린 데이터 로우를 읽은 수 있지만 쓰기 작업은 불가능합니다.
- PESSIMISTIC_READ: 배타락으로 락이 걸린 데이터에 읽기, 쓰기 작업이 모두 불가능합니다.
아래는 비관적 락의 예시 코드입니다.
Entity
@Entity class UserEntity( id: Long, name: String, ) { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = id protected set var name: String = name protected set fun updateName(name: String) { this.name = name } }
Repository
공유락을 적용
interface UserEntityRepository : JpaRepository<UserEntity, Long> { @Lock(LockModeType.PESSIMISTIC_READ) @QueryHints(QueryHint(name = "jakarta.persistence.lock.timeout", value = "10000")) fun findWithPessimisticLockById(id: Long): Optional<UserEntity> }
Service
@Service class UserService( private val userEntityRepository: UserEntityRepository ) { @Transactional fun update(id: Long, name: String) { val user = userEntityRepository.findWithPessimisticLockById(id) .orElseThrow { RuntimeException("User not found") } user.updateName(name) } }
테스트 케이스
class UserServiceTest @Autowired constructor( private val userService: UserService ) { @Test fun `10명 동시접근시 비관락 테스트`() { val executorService = Executors.newFixedThreadPool(10) val latch = CountDownLatch(10) for (i in 1..10) { executorService.submit { val name = (1..100).random().toString() try { userService.update(1, name) } catch (e: Exception) { println("$e") } finally { latch.countDown() } } } latch.await() } }
낙관적 락과 동일하게 10명의 사용자가 동시에 접근하는 상황을 가정하였습니다.
기대했던 대로 10건중 9건의 요청은 CannotAcquireLockException이 발생하였습니다.org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?] org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?] org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?] org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?] org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?] org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?] org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?] org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?] org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
728x90'JAVA > SPRING' 카테고리의 다른 글
[Spring] IoC(Inversion of Control) (0) 2023.09.03 [Spring] AOP (Aspect Oriented Programming) (0) 2023.06.11 [Spring] 의존성 주입 (Dependency Injection) (0) 2023.05.29 [Spring] org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: No constructor found in (0) 2020.04.15 [SPRING] SPRING 게시판 (8) - CRUD [게시글 수정] (0) 2020.03.18