2013년 7월 6일 토요일

MySQL Replication 환경 하에서 Hibernate 사용하기

타 RDBMS 보다 MySQL이 좋은 점 중에 하나가 다중 서버 환경으로의 전환이 용이하고, 안정적이라는 점이다. 보통의 Legacy 시스템에서는 RDBMS 서버 한대를 두고, 백업 서버를 두는 방식을 택하고, 성능이 모자라면 scale up 을 수행하는데, MySQL은 Replication을 이용하여 scale out 을 수행하므로서 부하 분산으로 성능을 높히는 방식입니다.



Hibernate의 경우 SessionFactory가 RDBMS 와 1:1 매핑이 되는 관계라 위와 같이 멀티 서버의 경우에는 중간에 Proxy 서버를 두던가,  MySQL Replication Driver 를 이용해야 합니다.
Proxy 서버를 둔다는 것은 Clustering 을 구성하는 것과 같으니 Proxy 서버 주소만 알면 되지만, Replication Driver를 사용 시에는 Master / Slave 서버별로 작업의 특성에 따라 구분해 줘야 합니다.

즉 Master 서버는 Read/Write를 할 수 있지만, Slave 서버는 Read 작업만 수행해야 합니다. 이렇게 하려면 작업 전에 Connection 의 isReadOnly 속성을 변경하여, 원하는 종류의 서버를 선택하게끔 해야 합니다.

이를 위해 Spring AOP 를 이용하여, 작업 시작 전에 작업 종류에 따라 Connection 속성을 변경하는 interceptor 를 제작합니다.

ConnectionInterceptor.java
@Aspect
@Component
public class ConnectionInterceptor {
private static final Logger log = LoggerFactory.getLogger(ConnectionInterceptor.class);
private static final boolean isTraceEnabled = log.isTraceEnabled();
@Autowired
private SessionFactory sessionFactory;
@Around("@annotation(kr.hconnect.data.mysql.ReadOnlyConnection)")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
log.debug("읽기전용 작업을 수행하기 위해 현 connection를 readonly로 설정합니다...");
SessionImpl session = (SessionImpl) sessionFactory.getCurrentSession();
Connection connection = session.connection();
boolean autoCommit = connection.getAutoCommit();
boolean readOnly = connection.isReadOnly();
try {
// MySQL SLAVE 서버에 접속하기 위해 Connection 속성을 설정합니다.
connection.setAutoCommit(false);
connection.setReadOnly(true);
// @ReadOnlyConnection이 선언된 메소드를 실행합니다.
return pjp.proceed();
} finally {
connection.setAutoCommit(autoCommit);
connection.setReadOnly(readOnly);
log.debug("읽기전용 작업을 수행하고, connection의 원래 설정으로 재설정했습니다.");
}
}
}

line 11 에 있는 @Arount 를 보시면,  ReadOnlyConnection 이라는 annotation 이 있는 메소드를 intercept 하도록 합니다.  이 메소드는 readonly 작업을 뜻하므로 connection의 readonly 값을 true 로 하여, MySQL 의 Slave 서버에 접속하도록합니다.

ReadOnlyConnection annotation은 메소드에만 적용되도록 합니다.

ReadOnlyConnection.java
@Target( { ElementType.METHOD } )
@Retention( RetentionPolicy.RUNTIME )
@Inherited
public @interface ReadOnlyConnection {
}

자 이제 테스트를 위한 서비스를 제작해 봅시다.

SimpleEntityServiceImpl.java
@Slf4j
@Service
@SuppressWarnings("unchecked")
public class SimpleEntityServiceImpl implements SimpleEntityService {
@Autowired SessionFactory sessionFactory;
@Transactional( readOnly = true )
@ReadOnlyConnection
@Override
public List<SimpleEntity> findAll() {
Session session = sessionFactory.getCurrentSession();
return (List<SimpleEntity>)session.createCriteria(SimpleEntity.class).list();
}
}
서비스 클래스에서 조회 메소드에서 @ReadOnlyConnection 을 사용하므로서 AOP 를 통해 ConnectionInterceptor 를 통해, Slave 서버에 접속하도록 했습니다.

이를 통해 Master/Slave 를 구분하고,  여러대의 Slave 도 Replication Driver 가 RoundRobin 방식으로  서버를 지정해주니, Application 개발자는 RDBMS 환경에 크게 신경 쓰지 않고, 자신의 분야에만 집중 할 수 있게 될 겁니다^^

또 한가지 SessionFactory가 하나로 유지되므로, 2nd Cache 도 하나가 되므로, 성능상의 잇점과 Cache와의 불일치에 대해 다른 방식보다 어느 정도 잇점이 있을 것입니다

댓글 없음: