Skip to content

[3주차 보완] redisson 분산락 도입#87

Merged
pyuseon merged 51 commits intohanghae-skillup:dbdb1114from
dbdb1114:dev
Feb 4, 2025
Merged

[3주차 보완] redisson 분산락 도입#87
pyuseon merged 51 commits intohanghae-skillup:dbdb1114from
dbdb1114:dev

Conversation

@dbdb1114
Copy link

@dbdb1114 dbdb1114 commented Feb 2, 2025

redisson 분산락 도입

지난주에 PR merge를 못 해서 지난주 커밋 내용까지 섞여 있습니다. 죄송합니다.
아래 커밋 이후로 리뷰해주시면 될 것 같습니다!
[ feat ] rds-repo module @Version 제거


작업 내용

이번 PR에서 진행된 주요 변경 사항을 기술해 주세요.
코드 구조, 핵심 로직 등에 대해 설명해 주시면 좋습니다. (이미지 첨부 가능)
ex: ConcurrentOrderService에 동시 주문 요청 처리 기능 추가

  • redisson aop기반 분산락 도입
  • redisson 함수형 분산락 도입

발생했던 문제와 해결 과정을 남겨 주세요.

  • 문제 1 - 여러 좌석에 대해 개별적으로 lock 설정을 하다보니 교착상태가 발생했었습니다.
  • 해결 방법 1 - 락 순서 및 좀 디테일한 부분들을 재설정 했습니다.

이번 주차에서 고민되었던 지점이나, 어려웠던 점을 알려 주세요.

과제를 해결하며 특히 어려웠던 점이나 고민되었던 지점이 있다면 남겨주세요.

  • wait_time과 lease_time 설정에서 고민이 많았습니다. 어느 한쪽을 위주로 생각해야 되는지 감이 잘 안 왔습니다.
  • 분산락과 트랜잭션의 관계를 이해하지 못 해서 처음에 조금 힘들었습니다.

리뷰 포인트

  • Redis 락 설정 부분의 타임아웃 값이 적절한지 의견을 여쭙고 싶습니다.
  • TicketService의 reservation 메소드에서 예외처리 부분과 저장부분을 validateReserve 메소드로 분리했는데 적절한지 궁금했습니다.
  • 주석이 적절한지 궁금합니다.
  • DistributesLockAop, FunctionalDistributedLock 두 부분에서 개별락을 점유하고 해제하는 부분이 조금 번잡스럽다고 느껴지는데 멘토님이 보시기에도 그런지 궁금합니다.

기타 질문

  • 외람되지만,,, 요즘 신입 개발자 및 저연차 개발자에게 요구되는 개발 실력이 어느 정도인지 여쭤봐도 될까요?
  • 4주차를 포함해서 한 1~2주 가량 주석, 설계 등 리팩토링 및 추가구현 할 예정입니다. 이런 것들이 만족된 이후에 애플리케이션을 하나 더 추가구현 해서 카프카를 써본다던가, 레디스를 조금 더 활용해본다거나 하고싶은데 혹시 추천해주실 추가기능이나 기술스택이 있을까요?

dbdb1114 and others added 30 commits January 11, 2025 14:55
포크 레포지토리로 옮기면서 약간의 착오가 있어 다시 커밋합니다.
[ 2주차 잔여 ] Feature/caching 캐싱 테스트 완료
- init.sql : 변경된 칼럼명 반영
- application-dev , docke-compose : 컴포즈 실행 환경 수정
- redisConfig : @value 어노테이션 도입
- redis-repo/build.gradle : bootJar enabled=false  설정 추가
외부 서비스 연동을 위한 모듈
dbdb1114 and others added 21 commits January 27, 2025 07:13
티켓 조회, 유저 티켓 조회, 티켓 예매 요청
AopForTransaction : 트랜잭션 분리를 위한 클래스
CustomSpringELParser :  SPEL표현식 파서
DistributedLock : 분산락 어노테이션 정의
DistributedLockAOP : aop 분산락 작성
FunctionalForTransaction : 트랜잭션 분리를 위한 클래스
FunctionalDistributedLock : 함수형 분산락 구현
PEAK 타임을 고려한 최대 동시간대 요청 건수 계산

----------------------------------------------------------
Junghyun's hanghaeho

인기영화 100편
비인기 영화 489편
모든 영화 1일 1회씩 상영, 2일 뒤 상영정보까지 전시

----------------------------------------------------------

인기 영화 100편의 경우
상영정보 오픈과 동시에 반 정도의 티켓이 바로 판매된다고 가정
100 * 13 = 1300건

비인기 영화 489편의 경우
상영정보 오픈과 동시에 약 3매 티켓 정도 바로 판매된다고 가정
489 * 3 = 1467건

피크시간 전체 판매 티켓 수 : 2767건

----------------------------------------------------------

예매는 한 사람이 여러좌석 예매 가능하므로
피크시간 예상 요청건수 = 922.33333 건 (전체 판매 티켓 수/3)
상영정보 조회 및 선택 - 좌석 선정 프로세스 소요시간 : 최대 30초 추정

----------------------------------------------------------

테스트상 30초까지 점진적으로 1000건의 요청시 최대 요청 처리시간 : 483.43ms

- waitTime 동안 Lock을 기다릴 이유는 무엇인가?
먼저 좌석을 선정한 고객이 불가피한 이유로 결제 과정까지 넘어가지 못 했을 때

결제 과정까지 넘어가지 못하는 상황
1. 로그인, 연령 부적합 등 애초에 좌석 선정 자체가 불가한 경우
2. 결제 중 이탈
3. 단순 변심
4. ...

사실상 1번 말고는 기다렸다가 재시도 해야하고, 1번의 경우 매우 적은 수일 것으로 예상되어
실제로 Lock 점유를 기다리는 시간이 그다지 길 필요가 없다는 판단이 됩니다.
최악의 경우를 생각하여 앞의 요청 처리 두 건에서 lock점유 후 다음 프로세스로 진행이 못 했을 때 까지만 보장하기로 하여, wait_time은 1초, lease_time은 2초로 선정

lease_time 선정 이유는 만약 여러 좌석 lock 중 일부 좌석에 대한 lock을 대기하여야 할 수 있으므로 wait_time을 고려한 선정
@pyuseon
Copy link
Contributor

pyuseon commented Feb 3, 2025

안녕하세요! 이번 주차도 고생 많으셨습니다! 리뷰 진행하도록 하겠습니다!

Comment on lines +43 to +49
if(keys instanceof Collection<?>){
keysArr = ((Collection<?>)keys).toArray();
} else if( keys.getClass().isArray() ){
keysArr = (Object[]) keys;
} else {
keysArr = new Object[] {keys};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분은 따로 객체를 배열로 반환하는 메서드로 분리하는 것이 좋을것 같습니다.

private Object[] convertToArray(Object keys) {
		if (keys instanceof Collection<?>) {
			return ((Collection<?>) keys).toArray();
		} else if (keys.getClass().isArray()) {
			return (Object[]) keys;
		}
		return new Object[]{keys};
	}

Comment on lines +60 to +70
for (RLock lock : lockList) {
try{
if(!lock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(),distributedLock.timeUnit())){
allAvailable = false;
break;
}
} catch (InterruptedException e){
allAvailable = false;
break;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock 획득 부분도 따로 메소드로 빼는 것이 좋을 것 같습니다.
그리고 해당 메서드의 리턴값을 boolean으로 설정하고 이 값에 따라 아래 해제 로직을 적용하면 될 것 같아요.

Comment on lines +87 to +92
if (lock.isHeldByCurrentThread()) {
try {
lock.unlock();
} catch (IllegalMonitorStateException e) {
log.warn("Lock was already released: {}", lock.getName());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해제 로직도
위의

if (lock.isHeldByCurrentThread()) {
	lock.unlock();
}

과 중복 되는데요. 예외처리를 포함해 하나의 해제 매서드를 만들어서 리팩토링 하시면 가독성이 좋은 코드가 될것 같아요.

Comment on lines +20 to +21
private static final Long LOCK_WAIT_TIME = 1L;
private static final Long LOCK_LEASE_TIME = 2L;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait time과 lease time 설정은 서비스에따라서 적절한 값이 달라지게 됩니다.
현재는 wait time을 1초로 설정해 주셨는데요. 빠르게 실패하고 다음에 다시 시도해야 하는 API가 아니라 일정 대기를 고려한다면 적절한 시간으로 보입니다. lease time은 트랜잭션이 오래 걸리지 않는 단순 작업이면 2-3초도 적절합니다.

Comment on lines +153 to +159
for (Ticket ticket : ticketList) {
ticket.setTicketStatus(TicketStatus.RESERVED);
Sales sales = Sales.builder().price(9000)
.user(user).ticket(ticket)
.createBy("system").build();
salesRepository.save(sales);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분은 검증(validation)이라기보다는 예약 완료 처리에 해당하는 로직으로 보입니다.
따로 분리 하시는 것을 추천 드립니다!

Comment on lines +63 to +70
if (lock.isHeldByCurrentThread()) {
try {
lock.unlock();
} catch (IllegalMonitorStateException e) {
log.warn("Lock was already released: {}", lock.getName());
}
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 해제 로직은 하나의 메소드로 관리 할 수 있을것 같습니다! 중복 코드를 줄여보세요!

return false;
// 순차적으로 락을 해제하도록 변경
for (RLock lock : lockList) {
if (lock.isHeldByCurrentThread()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 스레드에서 락 해제하는 것을 방지하기 위해서 isHeldByCurrentThread를 넣어 주셨네요! 👍

Comment on lines +103 to +112
functionalDistributedLock.executeLock("TICKET:", keys, () -> {
// 예외처리 [ 존재하지 않는 사용자 ]
Optional<User> optionalUser = userRepository.findByUsername(username);
if (optionalUser.isEmpty()) {
throw new UserNotFoundException();
}

User user = optionalUser.get();
validateAndReserve(showingId, user, ticketDtoList);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 이 부분에 Lock이 걸려있는데요. 락이 필요한 부분(티켓 상태 변경, 결제)만 보호하고, 검증 로직과 유저 조회 로직은 락 없이 처리하면 성능적으로 더 효율적 입니다!

@pyuseon
Copy link
Contributor

pyuseon commented Feb 3, 2025

좋았던 점

  • 3주차를 마무리하기 위해서 분산락 (함수형, Aop)를 둘다 끝까지 구현해 주신 점이 좋았습니다. 또한 테스트코드를 통한 검증도 추가해 주셨는데 1000명을 테스트 해보신 점이 인상 깊었네요. 실제 서비스를 고려하신 것 같습니다. ⭐
  • 서비스 테스트 코드는 동시성 테스트 외에도 기능과 예외처리도 같이 고민해서 여러 케이스를 구현해 주신점이 좋았습니다.

아쉬운 점

  • 현재 FCM발송 로직이 보이지 않는데요 요구사항인만큼 같이 추가해 주시면 더 완성도 높은 프로젝트가 될 것 같습니다.

리뷰포인트

  • 코드리뷰에 반영해 두었습니다!

기타 질문

요즘 신입 개발자 및 저연차 개발자에게 요구되는 개발 실력이 어느 정도인지 여쭤봐도 될까요?

  • 일반적으로 신입 개발자에게는 기본적인 CRUD 구현 능력과, 회사의 서비스 코드를 빠르게 파악하는 능력이 중요합니다. 이것에 더해 동시성 처리, MSA 등의 개념을 요구하는 회사도 많아지는 추세입니다. 특히, 회사의 규모가 클수록 더 다양한 기술 스택과 아키텍처적 이해를 필요로 하는 경우가 많습니다. 결국 개발자에게 가장 중요한 것은, 회사의 기술 스택에 맞춰 빠르게 적응하고 학습하는 능력입니다. 기술 스택이 궁금하시다면 요새 채용 공고를 많이 찾아보시는 것도 추천 드립니다. 👍

4주차를 포함해서 한 1~2주 가량 주석, 설계 등 리팩토링 및 추가구현 할 예정입니다. 이런 것들이 만족된
이후에 애플리케이션을
하나 더 추가구현 해서 카프카를 써본다던가, 레디스를 조금 더 활용해본다거나 하고싶은데 혹시 추천해주실 추가기능이나 기술스택이 있을까요?

  • 말씀하신대로 프로젝트의 완성도를 위해서는 4주차 내용의 먼저 구현 되어야 합니다. 그 후에 동시성 테스트를 심화해서 공부해 보시면 좋을 것 같습니다. 이 부분은 코딩 테스트나 과제에서도 자주 나오는 주제라 실무뿐만 아니라 면접 대비에도 도움이 될 것입니다. 추가적으로는 kafka를 활용해 보시는 것도 좋습니다. FCM 푸시 알람을 구현 할때 심화해서 kafka 메세지 큐를 활용해 볼수도 있을 것 같네요. 😄

@pyuseon pyuseon merged commit c19f190 into hanghae-skillup:dbdb1114 Feb 4, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants