프로젝트 진행 중 상세페이지 한 명이 상세 페이지를 조회 시 조회수를 Update 처리해줘야 하는 과정을 구현하며 겪은 문제들에 대해 얘기해 보겠습니다.
저는 상세 페이지를 조회할 때마다 DB에 Update Query를 날려 조회수를 업데이트해주고 있었습니다.
문제점
하지만 이렇게 상세페이지를 조회할 때마다 DB에 Update쿼리를 날리게 되면 DB에 부하가 증가하고, 또한 Lock으로 인해 성능저하가 생길 수 있습니다.
해결책
프로젝트에서 조회수가 아주 중요한 정보는 아니라고 생각하여 Redis에 조회수를 저장한 후 Scheduler를 통해 DB에 Update 하여 저장하기로 하였습니다.
이렇게 한 이유는 DB에 Update쿼리를 조회할 때마다 날리지 않아도 되어 성능상 이득을 취할 수 있다고 생각하였기 때문입니다.
따라서 바뀐 코드는 아래와 같습니다.
스케줄러 테스트
@Scheduled를 통해 시간마다 Redis에 저장된 조회수를 DB에 Update Query를 날려 저장하고, Redis에서 삭제하는 코드입니다.
이를 테스트하기 위해 코드를 작성하였습니다.
테스트코드를 아래와 같습니다.
테스트는 실패하게 됩니다.
테스트 코드에서 @Transactional
코드는 오류가 없다고 생각하고, @Transactional에 대해 알아보게 되었습니다.
현재 테스트 코드에 @Transactional이 있고, Scheduler에도 @Transactional이 있는 상황입니다.
두 개의 트랜잭션이 하나의 context에 있지 않아, Test 트랜잭션이 commit 되지 않은 상태를 바라보고 있는 상태입니다.
따라서 Scheduler 트랜잭션이 Test 트랜잭션이 commit이 된 상태를 바라보도록 하고 싶다면, TestTransaction을 통해 Test 트랜잭션을 Commit 시킬 수 있습니다. 바뀐 코드는 다음과 같습니다.
TestTransaction.flagForCommit()을 통해 트랜잭션이 끝나도 Rollback이 되지 않도록 하였습니다.
테스트는 통과하게 됩니다.
테스트에 @Transactional을 쓰지 않고 테스트를 통과할 수도 있습니다.
위와 같이 코드를 작성하고 @Transactional을 사용하지 않으면 테스트를 통과하게 됩니다.
@Transactional을 사용하지 않으면 테스트 후 @AfterEach를 통해 데이터를 직접 삭제해줘야 합니다.
이렇게 스케줄러 테스트를 작성하고 테스트 트랜잭션이 commit이 되지 않은 상태에서 TestTransaction을 사용하여 commit 하여 스케률러 트랜잭션이 정상적으로 동작하게 코드를 작성해 보았습니다.
리팩토링 with awaitlity
위 테스트 코드에서 맘에 안 드는 부분은 Thread.sleep이 상당히 맘에 들지 않았습니다.
따라서 스케줄러 메서드가 실행되면 더 이상 기다리지 않고 끝내는 awaitlity 라이브러리를 통해 특정 시간내에 특정 메소드가 몇 번 실행되면 더 이상 기다리지 않고 끝내는 코드를 작성할 수 있었습니다.
productCacheService에 존재하는 applyViewCountToRDB가 적어도 20초 내에 2번 실행되면 끝내는 코드가 추가되었습니다.
이번 포스팅을 통해서 테스트 트랜잭션에 대해 자세히 공부할 수 있었고, 내가 느끼는 불편함은 다른 사람도 불편함을 느껴 라이브러리로 구현해 놓은 것을 쉽게 찾을 수 있었습니다.
또한 테스트를 구현할 때 @Transactional을 사용할 때, side effect에 대해 알아볼 수 있었습니다.
'스프링' 카테고리의 다른 글
분산 시스템에서 메시지 안전하게 다루기 (0) | 2023.12.22 |
---|---|
쿠폰 발급 동시성 제어하기, 성능테스트로 성능 개선하기 (0) | 2023.11.29 |
Spring WebSocket Ping / Pong (2) | 2023.03.27 |
WAS, Servlet 용어 정리 (0) | 2023.01.04 |
Controller , Service , Repository 이해하기 (0) | 2022.05.19 |