지난 시간에 동시성 제어와 데드락에 대해서 알아봤다.
이번에는 프로젝트에 동시성 처리를 적용해본다.
대략 서비스를 설명하자면, 먼저 회원 가입을 하고 팀에 가입한다. 팀에 가입하기 위해서는 Join코드가 필요하고 '1111' 로 세팅했다.
팀 인원이 5명 제한이 걸려있는데, 100명을 동시에 가입시켜보려 한다.
@Test
@DisplayName("5명 제한 팀에 100명이 동시에 가입 신청")
void joinTeamTest()throws InterruptedException{
//given
final int JOIN_USER = 100;
User user1 = createUser();
createTeam(user1.getId());
TeamJoinDto joinDto = TeamJoinDto.builder()
.joinCode("1111")
.build();
CountDownLatch countDownLatch = new CountDownLatch(JOIN_USER);
//101명을 한번에 넣어주기 위함
ExecutorService executorService = Executors.newFixedThreadPool(101);
//when
for (int i = 0; i < JOIN_USER; i++) {
User user2 = createUser();
executorService.submit(()->{
try{
teamService.joinTeam(user2.getId(),joinDto,1L);
} finally {
countDownLatch.countDown();
}
});
}
// 모든 작업이 완료될 때까지 대기
countDownLatch.await();
// then
// 5명이어야한다.
List<MemberEntity> members = memberRepository.findAll();
System.out.println(members.size());
assertThat(members.size()).isEqualTo(5);
}
5명은커녕 29명이 가입된 것을 알 수 있다. 제작한 프로젝트는 팀의 인원수를 제한하고 인원수 증설에 따른 결제를 필요로하게했지만, 이런 오류가 발생하면 치명적일 수 있다. 앞서 배운 동시성 제어가 필요해보인다.
이런 오류가 이커머스나 선착순 타임세일 같은 긴박한 프로젝트였을 경우 문제는 더 커지겠지만, 우리 프로젝트는 그저 팀에 가입하고 업무를 부여받는 식이기 때문에 충돌할 가능성이 낮다고 판단했고 낙관적 락을 사용해보기로 했다.
낙관적 락 개념은 지난 시간에 학습했다.
낙관적 락은 JPA에서 지원해주기에 구현이 간단하다. 먼저 아래를 Entity에 추가한다.
팀에 가입하는 단계에 버전을 조회해야 한다.
즉, joinTeam 메소드를 수정해야 한다.
테스트 코드에서는 변한 게 없다. 테스트 코드에서 체크한 부분의 joinTeam메서드가 수정되어야 한다. 팀을 조회할 때 findById를 주석처리하고 findByIdWithOptimisticLock
를 사용했다.
findByIdWithOptimisticLock는 TeamReposiotry에서 아래처럼 간단하게 작성한다.
@Repository
public interface TeamReposiotry extends JpaRepository<TeamEntity, Long> {
@Lock(value = LockModeType.OPTIMISTIC)
@Query("select t " +
"from TeamEntity t " +
"where t.id =:teamId")
Optional<TeamEntity> findByIdWithOptimisticLock(@Param("teamId") Long teamId);
현재는 팀을 조회하고 5명이 넘었는지 확인하는 과정에 있다.
따라서 JPQL을 위처럼 작성했다.
정상적으로 5명이 가입된 것을 알 수 있다.
다음 시간에는 비관적 락을 사용해서 처리해본다.