본문 바로가기
SpringBoot

@Transactional(readOnly = true) 중간에 save(insert) 하기

by 오렌지마끼야또 2023. 8. 3.
728x90
반응형

 

 

 

 

현재 시스템은 readerDB와 writerDB로 나뉘어져 있습니다.

부하를 분산시키기 위해서입니다.

https://orange-makiyato.tistory.com/75

 

DB 부하분산(간단 정리)

일정 크기 이상의 서비스를 운영하는 회사에서는 대부분 데이터베이스 부하 분산을 위해 master-slave 형태로 reader-writer 인스턴스를 를 분리해 놓습니다. Primary cluster : 데이터베이스 클러스터에서

orange-makiyato.tistory.com

 

 

한 API를 개발해야하는데 5개의 스텝중에 중간에 한 스텝만 save를 하는 로직입니다. 이걸 위해서 5개의 스텝을 모두 readerDB를 타게 하자니, 하루 평균 500만 call이 들어오는 API라 writerDB의 성능 저하가 우려되는 상황입니다.

 

 

그래서 처음엔 아래와 같이 작성하였습니다.

@Service
@Transactional(readOnly = true)
public class Testservice {

    public String testMothod() {

        // 1번 스텝. Select

        // 2번 스텝. Select

        // 3번 스텝. Insert!!
        testDomainService.saveData();

        // 4번 스텝. Select

        // 5번 스텝. Select

        return "";
    }
}

 

 

@Component
public class TestDomainService {

    @Transactional(readOnly = false)
    public void saveData() {

        // save 로직

    }
}

 

@Transactional(readOnly = true)는 읽기 전용 트랜잭션으로, 데이터 변경 작업을 허용하지 않습니다.

 

Testservice 클래스를 @Transactional(readOnly = true) 로 하여 전체 로직을 기본적으로 rederDB로 가게 하되 TestDomainService의 saveData() 메서드만 @Transactional(readOnly = false)로 하여 writerDB로 가게 의도하였습니다. 

 

하지만 테스트를 해보니 DB에 저장이 되지 않았습니다.

 

 

그래서 확인을 위해 이것저것 출력해보았습니다.(잘 몰라서 그냥 여러가지 출력해보았습니다.)

더보기

        boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        log.debug(">>> save 메소드 안 readOnly: {}", readOnly);
        String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.debug(">>> save 메소드 안 currentTransactionName: {}", currentTransactionName);
        Map<Object, Object> resourceMap = TransactionSynchronizationManager.getResourceMap();
        log.debug(">>> resourceMap: {}", resourceMap);
        TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
        log.debug(">>> transactionStatus: {}", transactionStatus);
        try {
            Connection connection = DataSourceUtils.getConnection(dataSource);
            String DBurl = connection.getMetaData().getURL();
            log.debug(">>> DBurl: {}", DBurl);
        } catch (Exception e) {}

 

예상하기론 testMothod() 안에서 출력한 것과 saveData() 메서드 안에서 출력한 것은 내용이 달라야 하는데 readOnly 도 똑같이 true 였고 DB url도 역시 readerDB를 연결하고 있었습니다.

 

이유를 알아보니 @Transactional의 propagation(전파속성) 옵션이 기본값인 Propagation.PROPAGATION_REQUIRED로 설정되어 있기 때문이었습니다. 기본적으로 Spring의 @Transactional 에서 Propagation 옵션을 명시하지 않으면 Propagation.PROPAGATION_REQUIRED가 적용됩니다. 이 옵션은 이미 시작된 트랜잭션이 있으면 해당 트랜잭션을 사용하고, 없으면 새로운 트랜잭션을 시작하는 것입니다. 그래서 Testservice 클래스에서 생성한 트랜잭션이 있기 때문에 그것을 그대로 사용하였고 readOnly = true 였기 때문에 saveData()가 커밋되지 않았던 것입니다.

 

 

그래서 saveData()의 @Transactional을 아래와 같이 수정하였습니다.

@Component
public class TestDomainService {

    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void saveData() {

        // save 로직

    }
}

 

Propagation.REQUIRES_NEW 는 기존 트랜잭션과는 상관없이 항상 새로운 트랜잭션을 만드는 옵션입니다. 그래서 새로운 트랜잭션이 readOnly = false로 만들어져서 해당 로직은 writerDB로 수행이 되어 정상적으로 save가 됩니다.

 

 

Transaction의 전체적인 생성, 종료를 보면 다음과 같습니다.

 

728x90
반응형

댓글