시나리오
- 상품에 대한 리뷰를 작성할 수 있다
- 리뷰는 상품에 대한 점수를 줄 수 있다 (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);