목표
기존에는 동시성 문제가 생길 수 있는 리소스에 대해서는 X Lock을 통해 데이터를 가져왔었다.
해당 방식에서 조금 더 업그레이드해서 동시성 제어 문제는 Redis의 분산 락에게 맡기고, Transaction과 별개로 흐르되, 정합성을 잘 유지해보도록 하자!
또한, Transaction과 별개로! 라고 말했으니, 특정 Entity에만 국한되는 서비스가 아닌, 분산 락이 필요할 땐 언제든 해당 어노테이션을 달아 별개의 AOP로 작동하도록 만들자.
기존 동시성 제어방식
// extend JpaRepository
/**
* cabinetId로 사물함을 조회한다.(조회 이후 업데이트를 위해 X Lock을 건다.)
*
* @param cabinetId 사물함 ID
* @return 사물함 {@link Optional}
*/
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT c "
+ "FROM Cabinet c "
+ "WHERE c.id = :cabinetId")
Optional<Cabinet> findByIdWithXLock(@Param("cabinetId") Long cabinetId);
Java
복사
다음과 같이 비관적 락 방식으로 데이터에 X Lock을 걸어 트랜잭션이 실행되는 동안, 다른 요청을 모두 막아낸다.
그래서 뭐가 문제임?
개선 방법
동시성 문제는 AOP가 처리했다구!
•
@ContributeLock 커스텀 어노테이션을 만든다
•
해당 어노테이션이 붙어있으면 AOP로 Redisson의 fairLock을 진행한다
◦
fairLock? : 요청을 순서대로 큐에 담아 FIFO 방식으로 처리.
기아현상을 막기 위해 순차적으로 처리하는 방식을 택했음. 성능이 일반 락에 비해 조금 떨어진다고는 하지만 트래픽이 무지막지하게 몰렸을 때 30% 차이고, 우리 서비스는 그정도로 요청이 동시다발적으로 몰릴 케이스가 없어 데이터 정합성을 더 중요시생각해 fairLock을 택했다.
•
실행중인 Transactional Context가 커밋을 성공하면 안전하게 lock을 해제한다
•
CUD할 리소스는 단순히 getById 형식으로 가져오도록 하자.
X Lock? 데드락? 기아? 예? 필로소퍼 과제 켜라고요?
아주 깔끔하게 이미 정리를 잘 해주셨다. 뭔소리여.. 싶으면 일단 드셔보셈
진행
위의 글들을 통해 대략 무슨말을 하고있는지 느꼈다면, 이제 코드로 ㄱㄱ
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
String entity(); // Lock을 걸 Entity 이름
String pk(); // 해당 entity 에서 특정할수 있는 PK 이름
TimeUnit timeUnit() default TimeUnit.SECONDS;
long waitTime() default 5L; // lock 획득을 위한 대기시간
long leaseTime() default 3L; // lock 획득 시 갖고있을 시간
boolean requiresTransaction() default true; // 이 분산락을 진행할 때 Transactional Context가 필수임?
}
Java
복사
Redis의 key로 저장될 값을 3:CABIENT_LOCK 이런 형식으로 구상했다
3은 entity의 pk value(꼭 id가 아니어도댐), CABIENT은 entity명, _LOCK을 SUFFIX로 지정!
구현 목표
1.
3번 Cabient에 100명의 유저가 몰린다
2.
fair lock 방식으로 첫 요청만 성공하고, 나머지는
1. 대여 처리중 에러 반환
2. 첫번 째 요청이 Transaction까지 끝나버렸다면 redis unlock → 대여완료 상태로 변경까지 끝난거임 → 이 사물함은 이미 대여중이라고 에러 받음
캬~ 가 아니라.. 발생할 수 있는 에러들도 생각해보자
1.
Redis 서버가 죽었슴다 --;
a.
기존의 X lock 방식으로 대처하기?
2.
예상치 못한 에러가 발생하면 어떻게 안전하게 unlock을 할 것인가?
캬~ 가 아니라 성능 테스트도 진행하자
테스트 코드
세상에서 테스트코드가 제일 어렵다..
1.
단위 테스트 → 서비스와 별개로, AOP 부분만 mock으로 시나리오 테스트 진행
Thread 0 failed with lock acquisition error
Thread 5 failed with lock acquisition error
Thread 82 failed with lock acquisition error
Thread 39 failed with lock acquisition error
Thread 86 succeeded
Thread 85 failed with lock acquisition error
Thread 61 failed with lock acquisition error
... 여튼 100개 결과값
Java
복사
1차 트라이 - 우선 일반 락으로 100개의 스레드 대기, 동시에 진행
참고자료
다음은..
해당 영상을 보며 베리머치 빅 공감을 했다. 테스트 불편하고, 이거 기능 다 쓰는거같지도 않은데..흠
필요할 때만 JpaRepository를 상속받고 있었나? 1단 상속하죠?를 무의식중에 사용하고있던 것 같은 스스로를 돌아보며 해체분석쑈를 진행한 후 Repository를 개선해보자.