동시성 해결 과정

김파란·2024년 12월 12일

project

목록 보기
5/9
post-thumbnail

시나리오

  • 상품에 대한 리뷰를 작성할 수 있다
  • 리뷰는 상품에 대한 점수를 줄 수 있다 (0~5)
  • 리뷰를 생성하고 저장한 다음에 리뷰 테이블을 조회해서 전체 리뷰수와 점수를 구해 상품 테이블에 평균 점수와 리뷰 개수를 적는다
  • 상품에 리뷰가 제대로 저장되는지 확인한다

Review 코드

  • Review를 저장하고 커밋한 이벤트 처리를 한다
	@Transactional
    public void create(Long productId, ReviewCreateReqDto createReqDto, MultipartFile file) {
        Review review = reviewRequestMapper.create(productId, createReqDto);
        reviewRepository.save(review);

        eventPublisher.publishEvent(new ProductIncreaseEvent(productId, createReqDto.score()));
        eventPublisher.publishEvent(new ReviewImageFileEvent(file, review));
    }
    
    // 이벤트 처리
    @TransactionalEventListener
    @Async("customTaskExecutor")
    public void updateScore(ProductIncreaseEvent event) {
        productMetricsService.updateReviewMetrics(event.productId());
    }

Product 코드

  • Review Table에서 해당하는 리뷰수를 찾고 구한 평균 점수와 총 리뷰수를 넣고 저장한다
	@Transactional
    public void updateReviewMetrics(Long productId) {
        Product product = getProduct(productId);
        ReviewUpdate totalReviewScore = getTotalReviewScore(productId);
        product.updateReviewData(totalReviewScore.count(), totalReviewScore.totalScore());
        productRepository.save(product);
    }

기본

  • 아무짓도 하지 않았을 때도 200개 중에 198 ~ 199개 정도로 꽤 정확한 정합성을 보여준다
  • 스레드풀에는 32개를 사용한다
  • 시간초는 450 ~ 500ms 정도이다

방법 1. Synchorizned

  • 정합성은 대체로 맞는다
  • 100번 실행하면 1개가 틀릴까 말까인데 이것도 sync때문에 실패했다고는 볼 수 없을 정도이다
  • 시간은 대체로 왔다갔다 하는데 450 ~ 570ms 정도로 기본보다는 10% 성능차이를 보인다
	@Transactional
    public synchronized void updateReviewMetrics(Long productId) {
        Product product = getProduct(productId);
        ReviewUpdate totalReviewScore = getTotalReviewScore(productId);
        product.updateReviewData(totalReviewScore.count(), totalReviewScore.totalScore());
        productRepository.save(product);
    }

방법 2. 낙관적 락

  • 락은 select 시에만 적용할 수 있다
  • 200개로 저장하고 조회하고 수정까지 하다보니 락 충돌이 엄청나다
  • 락 충돌이 엄청나다 보니 시간도 700~1,000ms로 꽤 걸린다
	@Lock(LockModeType.OPTIMISTIC)
    @Query("select re from ReviewEntity re join re.productEntity pe where pe.id = :productId")
    List<ReviewEntity> findAllByOptimistic(@Param("productId") Long productId);

방법 3. 비관적 락

  • 가장 시간이 오래 걸린다
  • 정합성은 정확하다
  • 소요시간은 650 ~ 800ms까지 기존보다 40% 성능차이가 난다
  • 내 코드에서는 리뷰를 저장하고 실행을 하다보니 락을 거는 시간이 너무 길다.
	@Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select re from ReviewEntity re join re.productEntity pe where pe.id = :productId")
    List<ReviewEntity> findAllByPessimistic(@Param("productId") Long productId);

0개의 댓글