낙관적락을 통한 예외처리시, Catch 구문에서 잡히지 않는 문제

Terror·2024년 10월 30일

최종 프로젝트

목록 보기
9/28
post-thumbnail

Overivew

  • jmeter를 활용한 동시성 제어 처리 테스트중, 낙관적락을 활용함으로써 발생되는 예외처리를 할 수 있습니다

코드

    @Transactional
    @Override
    public ApiResponse<Void> updateMatching(Long matchingId, MatchingRequest.MatchingRequestUpdate updateReqDto, AuthUser authUser) {
        try {
            User user = User.fromAuthUser(authUser);
            Matching findMatching = matchingRepository.findByIdWithOptimisticLock(matchingId);
            MatchingValidator.isMe(user.getId(),findMatching.getUser().getId());
            Matching updateMatching = Matching.builder()
                    .id(findMatching.getId())
                    .user(findMatching.getUser())
                    .portfolio(findMatching.getPortfolio())
                    .outsourcing(findMatching.getOutsourcing())
                    .status(updateReqDto.status())
                    .version(findMatching.getVersion())
                    .build();
            matchingRepository.save(updateMatching);
            return ApiResponse.of(MATHCING_SUCCESS);
        } catch (ObjectOptimisticLockingFailureException e) {
            log.error("동시성 문제 발생 ! {}", e.getMessage());
        }
        return null;
    }

시나리오

  • A 사람이 A회사의 공고를 보고 신청(매칭)을 한다
  • 회사의 담당자(여러명이 있을 수 있음)들은 해당 매칭에 대해 수락,거절을 할 수 있다
  • 회사의 담당자는 여러명이 있을 수 있고, 매칭에 대해 수락,거절을 동시에 누르는 경우는 사실 거의 전무하다고 보기 때문에 낙관적락을 통해 동시성제어를 처리하고자한다

문제의 발단

  • Jmeter를 활용하여 100개의 유저(서버)에서 updateMatching 메서드를 호출하여 동일한 신청(매칭)에 대해 상태를 수락상태로 변경 하게한다
  • 분명 version이 맞지않으면서 예외처리가 발생될것이기 때문에 로그에서 동시성 문제 발생 이라는 로그가 찍힐것이다

하지만?

  • 실제 Jmeter를 통해서 호출을 해보자

  • 100개의 유저에서 호출하였을 경우, 57퍼센트의 에러율을 달성하는 것을 확인 할 수 있다

서버에서 확인해보자

  • 서버에서 확인해보았을떄도 분명 낙관적락을 통한, version 불일치 문제로 예외처리가 잘 나오는 것을 확인 할 수 있다
  • 하지만 catch 구문에서의 로그가 찍히지 않는다

원인이 뭘까?

  • ... 삽질중 ...

  • 원인은 다음과 같다

    1. 서비스 로직의 트랜잭션환경이 걸려져있음
    2. 여러개의 쓰레드가 들어오며 updateMatching을 실행함
    3. 여러개의 쓰레드가 들어와서 서로 다른 version에 해당하는 데이터를 수정하였지만, 바로예외처리가 실행되지 않는다
    4. 왜? 바로 트랜잭션환경이기 떄문에 해당 메서드가 종료되고 커밋되는 시점에 DB에 반영되서 예외처리가 실행되기 때문에 도무지 서비스 로직에서는 예외처리를 할 수 없었던 것이다
  • 삽질 끝!

원인은 알았는데, 서비스에서 어떻게 동작 시키지?

  • 하지만 우리는 비즈니스 로직에서 try catch 구문을 처리하여야 하는데 어떤방식으로 해결 가능할까?

(일단) 해결

    @Transactional
    @Override
    public ApiResponse<Void> updateMatching(Long matchingId, MatchingRequest.MatchingRequestUpdate updateReqDto, AuthUser authUser) {
        try {
            MatchingValidator.isNotCompany(authUser);
            Matching findMatching = findById(matchingId);
            findMatching.statusChange(updateReqDto.status());
            matchingRepository.flush();
            return ApiResponse.of(MATHCING_SUCCESS);
        } catch (OptimisticLockingFailureException e) {
            log.error("Optimistic lock error: {}", e.getMessage());
            throw new MatchingException(MATCHING_LOCK);
        }
    }
  • 중간에 flush()를 활용하여 현재까지의 변경사항을 즉시 DB에 반영시킨다
  • DB에 반영시켜, 예외를 발생시키면 아직 서비스 로직이 실행되고 있는 시점이기 떄문에 예외처리가 가능하다

찜찜...

  • 일단 해결은 하였지만, 뭔가 더 뾰족한 수가 있으면 좋을것같다..
profile
테러대응전문가

0개의 댓글