봉 블로그

Spring read-only transaction & MySQL Replication 본문

개발환경/Spring

Spring read-only transaction & MySQL Replication

idkbj 2014. 2. 21. 15:17

Spring MVC + MyBatis + MySQL Replication 환경에서

Spring transaction설정이 read-only=true & propagation="SUPPORTS"인 서비스 메소드 실행시 query 수행이 MySQL Replica 서버로 가지않고 master 서버로 가게된다.

반대로 propagation="SUPPORTS"설정을 제거한 read-only=true 서비스는 replica 서버로 수행되어 부하분산된다.

테스트결과 성능차이도 매우 심하게 차이가 났다. propagation="SUPPORTS" 가 설정되어 있는경우가 훨신좋다.

먼저  read-only=true & propagation="SUPPORTS" 설정이 mysql master 서버로 질의되는 현상은(read-only=true 설정이 있음에도 불구하고) db connection 의 readOnly 설정이 true로 셋팅되지 않기때문이다. 이현상을 좀더 자세히 알려면 Spring Transaction 과 MyBatis 와의 작동방식을 이해해야 한다.


먼저 <tx:method name="get*" read-only="true"/> 의 propagation 은 기본값인 REQUIRED 이다.   propagation="REQUIRED" 는 서비스 메소드 실행 직전에 기존에 생성된 Transaction 이 있으면 생성된 Transaction 에 참여하게 되고 생성된 Transaction 이 없으면 신규로 생성해서 사용한다. 신규로 생성하는 시점에 Spring의 DataSourcePlatformTransactionManager 에 의해 read-only=true 설정에 따라 db connection 의 readOnly 설정을 true 로 셋팅하게 되고, replica 서버로 부하가 분산된다.

하지만 propagation 이 SUPPORTS 로 설정되면 기존에 생성된 Transaction 이 생성됬을때만 참여하고 생성된 Transaction 이 없으면 신규생성하지 않는다. 

결국 <tx:method name="get*" read-only="true" propagation="SUPPORTS"/> 의 상황에서는 Spring Transaction 이 생성되지 않고, MyBatis에게 Transaction 관리가 위임되며, 아시다시피 MyBatis는 Spring 처럼 선언적 트랜젝션 처리를 지원하지 않아 자동으로 db connection 의 readOnly 설정을 처리하지 않는다. 따라서, mysql이 master 서버로 query 가 수행되는것이다.

<tx:method name="get*" read-only="true" propagation="SUPPORTS"/> 로 설정해도 mysql의 replica 서버로 보낼수 있는 방법을 고민한 결과 mybatis-spring 연동모듈에서 db connection 을 처리하는 TransactionFactory 를 확장해서 readOnly 를 true로 설정하게끔 하였다.

Spring 환경에서 MyBatis 가 db connection 을 가져오는 소스(SpringManagedTransaction)는 아래와 같다.

private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    if (logger.isDebugEnabled()) {
      logger.debug(
          "JDBC Connection ["
              + this.connection
              + "] will"
              + (this.isConnectionTransactional ? " " : " not ")
              + "be managed by Spring");
    }
  }

위 소스 마지막 부분에 아래와 같은 코드를 삽입하여 새로운 클래스를 만들고

if (this.connection != null && this.isConnectionTransactional && this.autoCommit) {
	    	
	    	if (this.logger.isDebugEnabled()) {
	    		this.logger.debug("set connection read only");
	    	}
	    	this.connection.setReadOnly(true);
	    }

Spring의  org.mybatis.spring.SqlSessionFactoryBean 설정시 transactionFactory 설정을 통해 위 새로 만든 클래스를
사용하도록 하면 된다.