강의

멘토링

커뮤니티

Cộng đồng Hỏi & Đáp của Inflearn

Hình ảnh hồ sơ của gfds
gfds

câu hỏi đã được viết

Cách giải quyết các vấn đề đồng thời bằng hệ thống kiểm kê

Hãy thử Khóa lạc quan

낙관적 락 테스트 실패

Viết

·

233

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으로 나오는데 원인을 도저히 모르겠습니다ㅜㅜ

 

 

 

javaspring동시성

Câu trả lời 2

0

169developer9973님의 프로필 이미지
169developer9973
Người chia sẻ kiến thức

냥덕님 안녕하세요.

 

첨부해주신 코드는 트랜잭션과 재시도를 하는 부분이 같이 있습니다.

강의 예제에서는 트랜잭션(서비스)에서는 데이터를 조작하는 부분만 있고, 재시도하는 부분은 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) 구문이 작동하지 않는다는 부분이 나타나고 있는 것처럼 보입니다.

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

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

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

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

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

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

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

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

Hình ảnh hồ sơ của gfds
gfds

câu hỏi đã được viết

Đặt câu hỏi