동시성 제어란 무엇인가?

Untitled

동시성 제어란 동시에 실행되는 여러 개의 트랜잭션이 작업을 성공적으로 마칠 수 있도록 트랜잭션의 순서를 제어하는 기법이다. 동시성 제어의 목적은 트랜잭션의 직렬성을 보장하고 데이터의 무결성일관성을 보장하기 위함이다.

동시성 제어를 하지 않는다면 무슨 일이 벌어질까?

  1. 갱신 손실(Lost Update)

    public void 1번_트랜잭션() {
    				// PK 1번 상품을 조회
            Products products = basketQueryRepository.findProductsById(1L);
    				// 해당 객체의 재고를 -1하는 메소드 실행
            products.updateStock(1);
    				// 업데이트된 내용 저장하기
            productRepository.save(products);
        }
    
    public void 2번_트랜잭션() {
    				// PK 1번 상품을 조회
            Products products = basketQueryRepository.findProductsById(1L);
    				// 해당 객체의 재고를 -3하는 메소드 실행
            products.updateStock(3);
    				// 업데이트된 내용 저장하기
            productRepository.save(products);
        }
    
    1. 2개의 트랜잭션이 동시에 실행될 경우 상품을 조회할 때 같은 재고를 조회하게 된다.
    2. 1번 트랜잭션은 재고를 10개에서 9개로 수정하고 2번 트랜잭션은 10개에서 7개로 수정한다.
    3. 1번 트랜잭션이 먼저 업데이트 쿼리를 날려 재고를 9개로 업데이트 했지만 뒤이어 실행된 2번 트랜잭션이 7개로 수정하는 업데이트 쿼리를 날리게 된다.
    4. 동시성을 제어했다면, 1번 트랜잭션이 9개로 업데이트 된 이후 2번 트랜잭션이 실행되어 재고를 9개로 읽어와 최종적으로는 6개가 되었을 것
  2. 현황파악오류(Dirty Read)

    @Transactional
    public int 1번_트랜잭션() {
    				//PK가 1, 2, 3인 제품을 읽어옴 
            Products products1 = basketQueryRepository.findProductsById(1L);
    				Products products2 = basketQueryRepository.findProductsById(2L);
    				Products products3 = basketQueryRepository.findProductsById(3L);
    				// 제품의 재고를 각각 -3하는 메소드 실행
    				Products1.updateStock(3);
    				Products2.updateStock(3);
    				Products3.updateStock(3);
    				// 트랜잭션 커밋 시점에 JPA 더티 체킹으로 업데이트 쿼리 발생
        }
    
    public int 2번 트랜잭션() {
    				//PK가 1, 2, 3인 제품을 읽어옴 
            int productsStock1 = basketQueryRepository.findProductsById(1L).getStock();
    				int productsStock2 = basketQueryRepository.findProductsById(2L).getStock();
    				int productsStock3 = basketQueryRepository.findProductsById(3L).getStock();
    				// 제품의 재고를 각각 더해서 리턴
    				int stock = products1 + products2 + products3
    				return stock;
        }
    
    1. 1~3번 제품을 불러와 재고를 업데이트하는 메소드를 1번 트랜잭션이 실행
    2. 제품의 재고는 각 10개에서 7개로 업데이트하는 쿼리가 커밋 시점에 발생
    3. 2번 트랜잭션이 products1까지만 업데이트된 상태에서 SELECT 쿼리를 발생시킨다면 7 + 10 + 10으로 27이 나오게 됨으로 의도에서 벗어나게 된다.
  3. 모순성(Inconsistency)

    내 코드보다 사진이 나은 것 같아서 사진으로 대체한다. 근데 마지막 계산이 잘못됐다. Y에 100을 더해서 1100이다.

    내 코드보다 사진이 나은 것 같아서 사진으로 대체한다. 근데 마지막 계산이 잘못됐다. Y에 100을 더해서 1100이다.

    1. 트랜잭션 1번에서 X의 값을 500 → 600으로 업데이트
    2. 트랜잭션 2번은 X와 Y의 값을 불러와 * 2를 해준다.
    3. 트랜잭션 1번은 Y의 값에 100을 더해준다.
    4. 트랜잭션의 1번의 기대값은 X = 600, Y = 600이지만 결과값은 X = 1200, Y = 1100이 나오는 모순되는 결과를 가져온다.
  4. 연쇄복귀(Cascading Rollback)

Untitled

  1. 트랜잭션1이 X + 100으로 업데이트 진행
  2. 트랜잭션2가 X * 2로 커밋까지 완료 후 데이터 저장
  3. 트랜잭션1이 Y를 읽어오는 과정에서 오류로 인하여 롤백 진행
  4. 하지만 트랜잭션2가 완료하고 시스템을 떠난 상태이기 때문에 트랜잭션1은 다른 트랜잭션이 처리한 부분에 대해서는 취소 불가능