자바생
article thumbnail
728x90

배경 및 목표

업무를 진행하다가 db 설정을 살펴보니 따로 라우팅을 해주지 않고 있었습니다.

read write 라우팅을 해줘야 @Transactional readOnly 옵션에 따라 read, write db를 읽는 것으로 알고 있습니다. (baeldung 참고)

 

명시적으로 라우팅하지 않으면 쿼리가 write db, read db를 구분하지 않고 실행되는지 확인하기 위해 실험을 해봤습니다.

 

예시

spring:
  datasource:
    url: [write db],[read db]

 

가정

  • writer, reader를 적어놓으면 connection 갯수가 동일하게 유지되면서 failover 대응
  • 앞에 먼저 적어놓은 writer를 읽고 reader를 읽음

version

spring boot 2.3.x

maria db 2.6.x

aws aurora

 

@Transactional readonly true 설정 후 db host 확인

회사 db host로 디버깅만 한 사진 첨부

readOnly 어노테이션을 갖는 로직을 실행하고 디버깅을 해보면 currentHost에 ro가 붙지 않은 host가 찍힙니다.

read 연산이어도 db host는 write db를 향하고 있었습니다.

 

확실히 알기 위해 read 연산을 실행하고 connection timeout까지 기다린 후 디버깅을 하면 이미 닫힌 connectino에 접근하는데, 이때 db host도 write db를 향하고 있었습니다.

 

위의 가정에서  "앞에 먼저 적어놓은 writer를 읽고 reader를 읽는 것으로 알고 있었다."라고 했는데 언제 read db를 읽는 것일까?

테스트

@Transactional readOnly 옵션의 유무에 따라 간단한 로직을 만들고, 40번씩 호출했습니다.

코드는 매우 간단한 생성과 조회를 하기 때문에 생략하고, 로깅에 사용한 AOP만 작성해 보겠습니다.

@Aspect
@Component
@RequiredArgsConstructor
public class TransactionAspect {

    private final DataSource dataSource;

    @Before("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void beforeTransactionalMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();

        System.out.println("트랜잭션 시작: " + className + "." + methodName);

        if (TransactionSynchronizationManager.isActualTransactionActive()) {

            System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
            System.out.println("readOnlyOption = " + TransactionSynchronizationManager.isCurrentTransactionReadOnly());

            try {
                Connection connection = dataSource.getConnection();
                ConnectionSpy connection1 = (ConnectionSpy) connection;
                Connection realConnection = connection1.getRealConnection();
                HikariProxyConnection realConnection1 = (HikariProxyConnection) realConnection;

                MariaDbConnection mariaDbConnection = realConnection1.unwrap(org.mariadb.jdbc.MariaDbConnection.class);

                System.out.println("mariaDbConnection.isReadOnly() = " + mariaDbConnection.isReadOnly());

                System.out.println("mariaDbConnection.getHostname() = " + mariaDbConnection.getHostname());

            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        } else {
            System.out.println("현재 활성화된 트랜잭션이 없습니다.");
        }
    }
}

 

Connection의 메타 데이터를 보면 write, read 연산에 따라 db 라우팅 되지 않고 무작위로 연결하고 있었습니다.

routing을 설정해주지 않았으니 당연한 결과입니다. 그러면 현재까지 read & write을 구분하지 않고 쿼리를 날리고 있었던 걸까요?

 

maria db 공식 문서를 보니 aurora를 사용하면 읽기 로드 밸런싱을 지원한다고 합니다.

그래서 위 로그들을 믿을 수 없어 aurora grafana를 확인해 보았습니다.

 

 

초록색이 write, 노란색이 read db입니다.

로그들을 보면 라우팅이 되지 않지만 실제 지표를 보면 write db에는 read 연산이 가지 않고 있습니다.

즉, 라우팅이 되고 있는데 connection의 host는 실제 연결할 때의 host가 아니라는 점을 알 수 있습니다.

 

그러면 왜 로그들이 저렇게 찍히는지 알아보기 위해 connectino 맺는 과정을 디버깅해보았습니다.

 

maria db 공식문서

 

 

maria db 공식문서에서 본 HA 모드를 선택하는 과정입니다. aurora mode를 사용하는 것을 알 수 있습니다.

 

failover가 제대로 되는지 확인하기 위해 connection을 생성할 때 일부러 시간을 늦춰서 해봤습니다.

failover 후 다음 url인 ro에 접근하게 됩니다.

 

Retry 횟수를 지정할 수 있는데 이는 기본값으로 120입니다.

 

 

readOnly 옵션은 쿼리에 상관없이 현재 connection이 master라면 readOnly는 false가 됩니다.

구분자 , 로 구분할 경우에는 모두 master라고 가정하기 때문에 readOnly false로 설정됩니다.

 

결론

처음에 Spring에서 Connection 객체를 생성할 때 write/read 연산에 따라 DB 라우팅을 한다고 생각했습니다. 그러나 콘솔 로그를 확인해 보니 연산과 무관하게 동일한 db connection URL이 로깅되는 것을 발견했습니다. 이로 인해 write 연산이 read db에도 갈 수 있다는 의문이 들었지만, 그라파나 지표를 통해 실제로는 write 연산이 write db에만 가는 것을 확인했습니다. 이를 통해 로그에 표시된 URL이 실제 DB에 전송되는 URL과 다를 수 있다는 점을 알게 됐습니다.

 

결과적으로, Spring은 자체적으로 라우팅을 하지 않으며, Connection 객체 생성 시 지정된 URL은 실제 DB 연결을 위한 url과 다를 수 있습니다. 실제 쿼리 실행 시에는 maria db 드라이버와 aurora가 읽기 로드 밸런싱을 처리합니다. 즉, Connection 객체는 DB 연결을 위한 추상화로, 실제 연결 정보와 완전히 일치하지 않을 수 있습니다.

 

현재 팀에서는 DB 클러스터의 모든 host를 적고, Aurora에 라우팅을 위임하는 방식을 사용하고 있습니다. 그렇다면 왜 Spring에 직접 라우팅을 설정하는 걸까요?

Spring의 메트릭 수집에 영향을 줄 수 있거나, AWS Aurora를 사용하지 않는 환경에서의 필요성 때문일 수 있습니다. 

이외에도 많은 이유가 있겠지만 잘 모르겠습니다.

 

글을 마치며 오랜만에 디버깅을 하며 취준생 때처럼 학습한 것 같아 재밌었습니다.

 

 

 

REFERENCES

 

https://mariadb.com/kb/en/about-mariadb-connector-j/#aurora

https://dba.stackexchange.com/questions/116361/how-do-i-configure-aws-aurora-to-separate-write-read-operations

 

 

728x90
profile

자바생

@자바생

틀린 부분이 있다면 댓글 부탁드립니다~😀

검색 태그