이전 글에서 분산락에 대한 기준을 어느정도 세웠다.
이번에는 분산락을 적용한 다른 코드가 있는데, 내가 세웠던 기준에 대해 부합하여 코드를 작성했는지 검증해 보고 개선점이 있다면 그 포인트를 잡아보려 한다.
@Transactional
@DistributedLock(key = "#lockName")
public SuggestionResponseDTO createSuggestion(
String token,
String lockName,
String suggestionType,
SuggestionRequestDTO requestDto
) {
...
Card fromCard = cardRepository.findByCardIdAndUser(requestDto.fromCardId(), user)
.orElseThrow(() -> new BaseException(ErrorCode.USER_NOT_MATCHED));
Card toCard = cardRepository.findById(requestDto.toCardId())
.orElseThrow(() -> new BaseException(ErrorCode.CARD_NOT_FOUND));
//validation 로직
validateCard(fromCard, toCard);
...
return SuggestionResponseDTO.from(savedSuggestion);
}
위 코드의 로직을 간단히 설명하자면 다음과 같다.
나는 이전 글에서 분산락 vs Non분산락 판단 기준을 다음과 같이 세웠다.
여기에 제안을 생성하는 상황을 대입해 보자.
1번 : 우리 프로젝트 상황과 부합한다.
2번 : A→B요청과 B→A요청은 엄밀히 다르다. (제안 entity에서 fromCard와 toCard가 별도의 column으로 관리되기 때문에.) 따라서 사용 순서가 중요하다.
3번 : A→B요청을 실시간으로 영속화 하여, 이후 들어올 A→B요청 혹은 B→A요청을 block하여 데이터베이스 무결성을 지켜야 하기 때문에 실시간 업데이트(영속화) 또한 중요하다.
결론 : 일단 분산락 사용 기준에는 부합한다.
전제조건 2번 상황 : 하나의 WAS에서 A→B요청을 캐싱하고 다른 WAS서버에서 A→B요청 혹은 B→A요청을 날린다고 가정해 보자. 이럴땐 못 막는다.
전제조건 3번 상황 : 하나의 WAS에서 A→B요청을 캐싱하고 동시에 A→B요청 혹은 B→A요청이 들어올 시, 캐시에 존재하는지 검사하면 되므로 막을 수 있다.
전제조건 4번 상황 : 3번 상황과 같이 막을 수 있다.
위의 방법들은 synchronized를 적용해도 해당 키워드는 각각의 서버에서만 적용되기 때문에, 결국 2번 상황을 막지 못한다.
일단 unique key를 걸어두긴 하였다. 하지만 어플리케이션단에서 방어로직을 작성하지 않아 write작업이 실패할 때 생기는 비용(작업 롤백) vs 어플리케이션단에서 방어로직이 실행되는 비용(캐싱 확인)을 비교했을 때 어플리케이션단에서 체크하는 것이 더 이득이라고 판단을 했다.
위의 내용을 종합하여, 결론적으로 이 기능에는 기존의 선택대로 분산락 적용이 맞다고 생각한다.