240709 실전 프로젝트 - 낙관적 Lock

노재원·2024년 7월 10일
0

내일배움캠프

목록 보기
78/90

Redis로 하던 동시성 제어를 낙관적 Lock으로 바꿔보기

과제의 심화 구현 사항에서 한번 조사해보라는 의미로 MySQL Lock으로 바꾸는 내용이 있었는데 이를 조사해보면서 팀원 분과 비관적 Lock, 낙관적 Lock을 각자 조사해보기로 했고 나는 낙관적 Lock을 사용해봤다.

// Entity 에서 추가
@Version
var version: Long? = null

// Service
override fun issueCouponToUser(couponId: Long, userId: Long): CouponResponse {
        var lockResult = false
        var response: CouponResponse? = null
        while (!lockResult) {
            try {
                Transactional {
	                /*  기존 쿠폰 발급 로직*/
                }
            } catch (e: OptimisticLockingFailureException) {
                println("issueCouponToUser OptimisticLockingFailureException error: $e")
                Thread.sleep(500)
            } catch (e: Exception) {
                println("issueCouponToUser error: $e")
                throw e
            }
        }

내가 조회한 Version을 Commit 시점의 레코드의 Version과 비교해서 수정을 방지하는 Lock이 낙관적 Lock이다.

만약 다른 트랜잭션이 해당 레코드를 수정해서 Version이 1 증가했다면 이전 버전을 조회해서 수정하던 트랜잭션은 실패로 떨어지게 된다. (org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction)

테스트 결과 Lock이 제대로 걸리긴 하지만 Lock의 성공 횟수가 이상하게 찍힌다. 이는 테스트 할 때마다 결과적으로 버전은 보장되지만 횟수가 다르다.

낙관적 Lock은 테스트 결과로 보아 조회에서 강점을 보이고 수정에서 일관적이지 않은 결과를 제공하므로 선착순 쿠폰 발급 시나리오엔 적합하지 않다.

성능적으로 조회가 굉장히 빠르지만 버전의 체크는 커밋에서 이루어지니 리소스의 낭비가 커지고 재시도 로직 구현도 추가해야하니 코드 복잡도가 증가하고 트랜잭션이 동시에 접근하는 것을 우선적으로 허용한 상태라 성공/실패에 대한 여부를 개발자가 확신할 수가 없다.

이런 이유에 따라 MySQL로 동시성을 제어하겠다면 접근 자체를 제어하는 비관적 Lock이 수정이 빈번하게 일어나는 시나리오에서 훨씬 적합하다.

0개의 댓글