Redisson에서는 lock 취득 실패 스레드들은 Redis pub/sub과 spin lock이 혼합된 방식으로 lock을 얻을 수 있을 때 까지 대기한다.
따라서 Lettuce를 사용할 때와 달리 아래와 같은 lock 대기 코드를 구현하지 않아도 된다.
while (!lockRedisRepository.acquireLock(lockKey, Duration.ofMillis(30000))) {
try {
Thread.sleep(100); //100ms
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("예매 프로세스가 중단되었습니다.", e);
}
}
try-finally 구조로 lock 해제 시 메서드 실행이 끝나는 지점에서 바로 해제된다.
반면 TransactionSynchronizationManager.registerSynchronization 의 afterCompletion()로 lock 해제 시 메서드 종료 이후 트랜잭션이 끝나는 시점에 맞춰 lock이 해제된다.
@Transactional 사용 시 AOP 프록시 내부에서는 아래와 같은 순서로 트랜잭션 흐름이 이루어진다.
이 흐름에서 try-finally 구조는 3번 완료 시점에 lock을 해제하며, afterCompletion()은 4번 완료 시점에 lock을 해제한다.
큰 차이는 아니라고 생각될 수도 있다.
하지만 이로 인해 트랜잭션이 commit 되기 전에 다른 스레드가 lock을 잡아 작업할 수 있게 되며, 이로 인해 동시성 문제가 발생할 수 있다.
따라서 afterCompletion()을 적용해 동시성 문제를 예방할 수 있다.
조회 작업들은 서로 간섭하지 않으므로 쓰기 작업의 lock을 조회 시에도 사용할 필요가 적다.
따라서 쓰기 작업이 자주 발생하지 않고, 조회가 빈번하게 발생하는 경우 read/writeLock을 분리해서 사용하면 경합을 줄이고 전체 시스템의 throughput을 늘릴 수 있다.
읽기 락을 제대로 활용하기 힘들어지고, read/write lock을 구분하기 위한 추가적인 오버헤드의 부담이 일반 lock에 비해 더 크게 다가올 수 있다.
또한 writeLock 위주로만 사용되면 throughput 증가량이 체감되기 힘들고 사실상 단일 lock과 비슷하게 동작한다.
참고: https://incheol-jung.gitbook.io/docs/q-and-a/spring/redisson-trylock