본문 바로가기
Spring

Transactional / 동시성 제어 / DB Replication (Master-Slave) 관련정보

by 리승우 2022. 11. 26.

Transactional 관련정보

1. saveandflush는 @Transactional을 대체할 수 있다.

Transational은 작업단위가 끝날 시 DB에 flush를 내린다.

saveandflush는 이와 같은 작업을 해주는 것이기에, flush만 생각하고 있다면 이를 대체할 수 있다.

 

2. Transactional 격리레벨

 

1. Read Uncommitted - 거의 사용하지 않음

 - 트랜잭션에서 처링 중인 아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 것을 허용

2. Read Committed

 - 커밋되어 확정된 데이터만 다른 트랜잭션이 읽도록 허용함으로써 Dirty Read를 방지

3. Repeatable Read

 - 트랜잭션 내에서 쿼리를 두 번 이상 수행할 때, 첫 번째 쿼리에 있던 레코드가 사라지거나 값이 바뀌는 현상을 방지

 - 오라클에서 구현할려면 for update 구문 사용

4. Serializable

 - 트랜잭션 내에서 쿼리를 두번 이상 수행할 때, 첫 번째 쿼리에 있던 레코드가 사라지거나 값이 바뀌지 않음은 물론 새로운 레코드가 나타나지도 않음

 

3. Transactional과 동시성 제어의 차이점?

 

트랜잭션 격리 수준

> 트랜잭션 동안의 일관된 데이터 읽기를 고려하기 위해 적용한다.

 

동시성 제어

> 엔티티에 대한 동시 접근에 대한 처리이다. 한 트랜잭션이 특정 엔티티에 접근하고 있을 때 다른 트랜잭션이 해당 엔티티를 변경할 수 없도록 버전을 사용하거나 락을 걸어 해결한다.

 

즉, 트랜잭션 격리 수준은 동시성 제어와 관계가 없다.

 

동시성 제어 관련정보

1. 동시성 제어가 어려운 이유

동시성과 일관성은 트레이드 오프 관계이다.

즉, 동시성을 높이려고 Lock의 사용을 최소화하면 일관성을 유지하기 어렵고, 일관성을 높이려고 Lock을 적극적으로 사용하면 동시성이 저하된다.

 

2. 동시성 제어

비관적 동시성 제어

 - 데이터를 읽는 시점에 Lock을 걸고 트랜잭션이 완료될 때까지 이를 유지한다.

낙관적 동시성 제어

 - 데이터를 읽을 때는 Lock을 설정하지 않는다. 대신 수정 시점에 , 다른 사용자에 의해 값이 변경됐는지를 반드시 검사해야한다.

   - select 할 때 값을 가지고 있다가, 수정 시점에 where 조건에서 비교한다.(최종 변경일시 칼럼을 사용하면 간단하게 할 수 있다)

 

 

3. Redisson은 Lock을 부여한 후, Transactional에 돌입해야한다.

>> Lock을 해제하기 전에 Database에 commit이 되어야하기 때문.

이유 : 현재 찾고있는 중임.

public createdOrdersResponseDto productOrderRedisson(Long productId, Account account) {
        RLock lock = redissonClient.getLock(productId.toString());

        createdOrdersResponseDto responseDto;
        try {
            // 몇 초동안 점유할 것인지에 대한 설정
            boolean available = lock.tryLock(7, 1, TimeUnit.SECONDS);

            // 점유하지 못한 경우
            if(!available) {
                System.out.println("lock 획득 실패");
                throw new RuntimeException("락 획득 실패");
            }

            // lock 획득 성공
            responseDto = createOrderWithRedisson(productId, account.getId());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 락 해제
            lock.unlock();
        }
        return responseDto;
    }

    @Transactional
    protected createdOrdersResponseDto createOrderWithRedisson(Long productId, Long accountId) {
        Product findProduct = validateProduct(productId);
        Account findAccount = validateAccount(accountId);

        findProduct.decreaseStock(1);
        productRepository.save(findProduct);
        System.out.println("findProduct.getStock() = " + findProduct.getStock());
        // 상품에 대한 주문은 여러개도 발생할 수 있다..?
        Order savedOrder = orderRepository.save(new Order(findAccount, findProduct, getTotalOrderPrice(findProduct.getPrice())));

        return new createdOrdersResponseDto(
                savedOrder.getId(), savedOrder.getTotalPrice(), findAccount.getUsername(), findProduct
        );
    }

 

4. Persimistic lock 구동원리 (비관적 잠금)

 

비관적락은 내가 접근한 Database 리소스에 다른사람이 접근조차 하지못하도록 락을 걸고 작업을 진행하는 것을 말한다.

 

이미지 설명은 아래와 같다.

좌측 사용자가 잔여좌석수를 UPDATE하면서 LOCK이 풀린 뒤,

우측 사용자가 SELECT 및 UPDATE가 가능해지는 구조이다.

 

 

이 때, 2가지 옵션을 선택할 수 있다.

1. 배타락(exclusive lock)

다른 트랜잭션에서 읽기와 쓰기가 모두 불가능하다.

쿼리의 형태는 SELECT FOR ~ UPDATE 쿼리가 쏴진다.

  • PESSIMISTIC_WRITE – 해당 리소스에 베타락을 겁니다. 타 트랜잭션에서는 읽기와 쓰기 모두 불가능해집니다. (DBMS 종류에 따라 상황이 달라질 수 있습니다)

 

2. 공유락(shared lock)

다른 트랜잭션에서는 읽기는 가능하지만 쓰기는 불가능하다.

  • PESSIMISTIC_READ – 해당 리소스에 공유락을 겁니다. 타 트랜잭션에서 읽기는 가능하지만 쓰기는 불가능해집니다.

 

 

아래의 Persimistic lock이 구동될 시, 쿼리문은 3번째 이미지와 같이 SELECT FOR ~ UPDATE 쿼리가 쏴진다.

SELECT FOR UPDATE = 동시성 제어를 위해 특정row에 배타적 LOCK을 거는 행위
“데이터 수정하려고 찾은 것이니, 다른분들은 건드리지 마세요!” 라는 의미이다.

 

위 작업으로, 해당 작업은 lock을 보유한 채로 진행된다.

만약 다른 접근이 있을 경우, 이전 작업이 Transactional로 인해서 아직 완료되지 않았을 시에는 아직 Lock이 걸려있으므로 다른 세션이 데이터에 접근할 수 없게된다.

 

이전 작업의 Transactional이 끝나 update가 실행될 시, lock이 풀림과 동시에 그 뒤 작업이 lock을 얻게되며 진행된다.

 

이와 같은 작업방식 덕분에 데이터 정합성을 더욱 보장할 수 있게된다.

하지만!

어떤 이유로 인해 작업이 영구지연될 경우 Dead-lock 현상이 발생하여 장기지연 현상이 발생할 수 있으니 주의해야한다. 또한 성능상 저하된다는 점도 인지하고 있어야 한다.

public interface ProductRepository extends JpaRepository<Product, Long>, ProductRepositoryCustom {

    /**
     * 비관적 락
     */
    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query("select p from Product p where p.id = :id")
    Product findByIdWithPessimisticLock(@Param("id") Long id);
@Transactional
    public createdOrdersResponseDto productOrderWithPessimisticLock(Long productId, Long accountId) {
        Account findAccount = validateAccount(accountId);
//        Product findProduct2 = validateProduct(productId);
        Product findProduct = productRepository.findByIdWithPessimisticLock(productId);

        findProduct.decreaseStock(1);
//        productRepository.saveAndFlush(findProduct);

        Order savedOrder = orderRepository.save(new Order(findAccount, findProduct, getTotalOrderPrice(findProduct.getPrice())));
        return new createdOrdersResponseDto(savedOrder.getId(), savedOrder.getTotalPrice(), findAccount.getUsername(), findProduct);
    }
Hibernate: 
    select
        product0_.product_id as product_1_5_,
        product0_.created_at as created_2_5_,
        product0_.modified_at as modified3_5_,
        product0_.category as category4_5_,
        product0_.delivery as delivery5_5_,
        product0_.img_url as img_url6_5_,
        product0_.is_deleted as is_delet7_5_,
        product0_.is_sale as is_sale8_5_,
        product0_.price as price9_5_,
        product0_.seller as seller10_5_,
        product0_.stock as stock11_5_,
        product0_.title as title12_5_ 
    from
        product product0_ 
    where
        product0_.product_id=? for update

 

궁금사항

왜.

Redisson >> Transactional 전후로 lock획득 및 해제

Pesimistic lock >> Transactional 안에서 lock획득 및 해제

로 작업이 이뤄지는 것일까....

내가 잘못된 자료를 참고하고 있기때문일까?

이유를 계속 알아봐야하는 상태이다.

 

DB Replication (Master-Slave)

대규모 트래픽 처리를 원활히 진행하기 위해 추후 DB Replication (Master-Slave) 전략을 사용할 예정이다.

이로 인해 DB는 1개의 Master와 N개의 Slave 서버로 분산될 예정이다.

 

insert / update / delete에 대한 작업은 Master Database에서만 이뤄지며,

select에 대한 작업은 Slave Database에서만 이뤄지게 할 것이다.

 

Master Database에서 데이터 변경이 이뤄질 경우, 자동적으로 Slave Database가 업데이트 되는 구조이다.

 

여기서 질문.

Master Database에서의 동시성을 제어하기위해서는

단일 DB에서만 사용이 가능한 낙관/비관적 잠금을 사용해도 되는 것일까?

아니면 분산 DB에서도 사용이 가능한 Redisson을 필수불가결적으로 사용해야 되는 것일까?

 

공부해야할 필요성이 느껴진다.

 

 

 

 

참고용 링크

https://mangkyu.tistory.com/30

'Spring' 카테고리의 다른 글

Servlet (서블릿) 정리  (0) 2022.12.07
Web Server / Web Application Server 차이정리  (0) 2022.12.07
Querydsl 관련정보  (0) 2022.11.21
페치조인 (Fetch Join)  (0) 2022.11.07
JOIN종류 / ON절의 의미 / Query 조회기능 최적화 팁  (0) 2022.11.07

댓글