인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

냥덕님의 프로필 이미지

작성한 질문수

재고시스템으로 알아보는 동시성이슈 해결방법

Optimistic Lock 활용해보기

낙관적 락 테스트 실패

작성

·

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 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. 냥덕님! 낙관적 락을 활용한 동시성 테스트에서 문제가 발생하고 있어 문의 주셨군요.

낙관적 락은 여러 스레드가 동시에 데이터를 업데이트할 때 보통 정상적으로 작동하지만, 필요에 따라서 업데이트가 실패할 수 있습니다. 특히, 코드에서 catch (ObjectOptimisticLockingFailureException e) 구문이 작동하지 않는다는 부분이 나타나고 있는 것처럼 보입니다.

아래 사항들을 검토해 보시기를 권장드립니다:

  1. 엔티티의 @Version 필드: 낙관적 락은 JPA의 @Version 필드를 사용하여 버전 관리를 합니다. @Version이 올바르게 설정되어 있는지(특히 데이터베이스에도 해당 컬럼이 존재하는지)를 확인해 주세요.

  2. 동일 스레드에서 실행 여부: 테스트가 실패하는 이유 중 하나는, 사실 Thread 수가 기대한 만큼 늘어나지 않아서일 수 있습니다. ExecutorService를 통해 32개의 스레드 풀을 생성하였지만, 테스트 환경에 따라 모든 스레드가 적절하게 생성 및 실행되어야 합니다. 스레드 개수를 조정해 보세요.

  3. ObjectOptimisticLockingFailureException의 사용: 실제로 이 예외가 던져지지 않는 경우, JPA의 기본 동작을 확인해 보시고 예외가 발생하도록 강제할 필요가 있을 수 있습니다.

  4. DB 상태 확인: 테스트 전에 데이터베이스에서 Board 데이터의 초기 상태를 확인하시고, 각 테스트가 데이터 일관성을 유지하는지 체크해 보세요.

비슷한 상황에 대해 이해를 돕기 위해 다음 유사한 질문을 추천드립니다:

이외에도 추가로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏

냥덕님의 프로필 이미지

작성한 질문수

질문하기