작성
·
86
0
안녕하세요 낙관적 락을 활용해서 조회수 증가 동시성 테스트를 하고 있습니다! 영상처럼 동일하게 로직을 작성해서 테스트 하는데 동시성 처리가 전혀 안되는 상태라 질문 드립니다ㅜㅜ
아래는 Board 엔티티입니다!
import jakarta.persistence.*;
import lombok.Getter;
@Getter
@Table(name = "board")
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Column(name = "view")
private long view;
@Version
private Long version = 0L;
public Board(String title, long view) {
this.title = title;
this.view = view;
}
public void increaseView() {
this.view += 1;
}
public Board() {
}
}
서비스 로직입니다! Catch 부분을 전혀 타지 않는 상태인거 같습니다
@Transactional
public void increaseViewCountOpticLock(final long boardId) throws InterruptedException {
while (true) {
try {
Board board = boardRepository.findByIdWithOptimistLock(boardId);
board.increaseView(); // 조회수 증가
boardRepository.save(board); // 저장
break; // 성공 시 루프 탈출
} catch (ObjectOptimisticLockingFailureException e) {
log.info("=========================");
Thread.sleep(50);
}
}
}
import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.tkdgus.concurrdemo.entity.Board;
public interface BoardRepository extends JpaRepository<Board, Long> {
default Board getBoardById(long boardId) {
return findById(boardId).orElseThrow(IllegalArgumentException::new);
}
@Lock(LockModeType.OPTIMISTIC)
@Query("SELECT b FROM Board b WHERE b.id = :boardId")
Board findByIdWithOptimistLock(long boardId);
}
DB는 MySQL이고 트랜잭션 격리 수준이나 이런건 다 기본 설정 그대로입니다!
@Test
@DisplayName("낙관적 락 동시성 테스트")
void increaseViewCountOptimisticLock() throws InterruptedException {
long boardId = 1L;
int concurCnt = 100;
ExecutorService executorService = Executors.newFixedThreadPool(32);
CountDownLatch latch = new CountDownLatch(concurCnt);
for (int i = 0; i < concurCnt; i++) {
executorService.submit(() -> {
try {
boardService.increaseViewCountOpticLock(1L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
latch.countDown();
}
});
}
latch.await();
BoardDto afterBoard = boardService.findBoard(boardId);
assertThat(afterBoard.view()).isEqualTo(concurCnt);
}
위 테스트 코드로 테스트 하면 100이 아니라 항상 10으로 나오는데 원인을 도저히 모르겠습니다ㅜㅜ
답변 2
0
냥덕님 안녕하세요.
첨부해주신 코드는 트랜잭션과 재시도를 하는 부분이 같이 있습니다.
강의 예제에서는 트랜잭션(서비스)에서는 데이터를 조작하는 부분만 있고, 재시도하는 부분은 facade 에 존재합니다.
@Transactional
public void increaseViewCountOpticLock(final long boardId) throws InterruptedException {
while (true) {
try {
Board board = boardRepository.findByIdWithOptimistLock(boardId);
board.increaseView(); // 조회수 증가
boardRepository.save(board); // 저장
break; // 성공 시 루프 탈출
} catch (ObjectOptimisticLockingFailureException e) {
log.info("=========================");
Thread.sleep(50);
}
}
}
트랜잭션이 열린이후로는 오류가 발생한 후 재시도를 할때 같은 데이터를 읽어오기때문에 트랜잭션 종료후 재시도를 해야합니다.
감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다. 냥덕님! 낙관적 락을 활용한 동시성 테스트에서 문제가 발생하고 있어 문의 주셨군요.
낙관적 락은 여러 스레드가 동시에 데이터를 업데이트할 때 보통 정상적으로 작동하지만, 필요에 따라서 업데이트가 실패할 수 있습니다. 특히, 코드에서 catch (ObjectOptimisticLockingFailureException e)
구문이 작동하지 않는다는 부분이 나타나고 있는 것처럼 보입니다.
아래 사항들을 검토해 보시기를 권장드립니다:
엔티티의 @Version
필드: 낙관적 락은 JPA의 @Version
필드를 사용하여 버전 관리를 합니다. @Version
이 올바르게 설정되어 있는지(특히 데이터베이스에도 해당 컬럼이 존재하는지)를 확인해 주세요.
동일 스레드에서 실행 여부: 테스트가 실패하는 이유 중 하나는, 사실 Thread
수가 기대한 만큼 늘어나지 않아서일 수 있습니다. ExecutorService
를 통해 32개의 스레드 풀을 생성하였지만, 테스트 환경에 따라 모든 스레드가 적절하게 생성 및 실행되어야 합니다. 스레드 개수를 조정해 보세요.
ObjectOptimisticLockingFailureException
의 사용: 실제로 이 예외가 던져지지 않는 경우, JPA의 기본 동작을 확인해 보시고 예외가 발생하도록 강제할 필요가 있을 수 있습니다.
DB 상태 확인: 테스트 전에 데이터베이스에서 Board 데이터의 초기 상태를 확인하시고, 각 테스트가 데이터 일관성을 유지하는지 체크해 보세요.
비슷한 상황에 대해 이해를 돕기 위해 다음 유사한 질문을 추천드립니다:
이외에도 추가로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏