본문 바로가기
Java & Kotlin/Spring

Spring Data JPA - Write, Read Only 분리 적용하기 - 2. 무력화 되는 경우

by devson 2021. 12. 18.

이전 포스팅을 통해 Spring Data JPA를 사용하면서 AbstractRoutingDataSource, LazyConnectionDataSourceProxy를 사용하여 Write, Read Only DB를 분리하여 사용하는 방법의 원리를 알아보았다.

그럼 이 설정을 무력화하려면 어떻게 하면될까?

 

앞서 봤던 설정의 핵심은 LazyConnectionDataSourceProxy를 사용하여 DataSource로 부터 Connection을 가져오는 타이밍을 늦추는 것이었다.

그럼 이를 무력화 할 수 있는 방법은 Transaction이 시작되자마자 DataSource로부터 Connection을 가져와 이를 직접 사용하게 만드는 것이다.

이러한 방법 중에 당장 테스트해볼 수 있는 방법은 기존 설정에 p6spy를 사용하는 것이다.

 

p6spy

JDBC query 추적을 위한 framework로 개발시에 DB query 분석에 있어 편리함을 가져다 주지만, 앞서 살펴본 Write, Read Only DB 분리 설정 시에는 이를 무력화시킬 수 있다.

간단하게 확인해보자.

 

먼저 아래와 같이 p6spy-spring-boot-starter 의존성을 추가한다.

dependencies {
    // ...
    implementation("com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.6.2")
    // ...
}

그리고 더 설정할 것이 없이 이대로 애플리케이션을 실행시키면 된다.

 

실행을 시켜보고 확인을 해보면 Write(Primary) DB로만 라우팅이 되는 것을 확인할 수 있을 것이다.

앞서 debugger를 통해 call stack을 확인했던 것 처럼, 이번에도 @Transactional이 붙은 메서드를 시작할 때의 call stack을 확인해보자.

아래 이미지를 보면 p6spy 관련하여 call stack이 추가된 것을 확인할 수 있다.

p6spy 내부 코드를 살펴보면 P6DataSource는 Connection을 가져올 때 해당 Connection의 getMetaData 메서드를 호출한다.

즉, Connetion을 직접 사용한다는 것이다.

 

package com.p6spy.engine.spy;

public class P6DataSource implements DataSource, ConnectionPoolDataSource, XADataSource, Referenceable, Serializable {
  // ...

  @Override
  public Connection getConnection() throws SQLException {
    if (realDataSource == null) {
      bindDataSource();
    }
    
    final long start = System.nanoTime();
    
    if (this.jdbcEventListenerFactory == null) {
      this.jdbcEventListenerFactory = JdbcEventListenerFactoryLoader.load();
    }

    final Connection conn;
    final JdbcEventListener jdbcEventListener = this.jdbcEventListenerFactory.createJdbcEventListener();
    final ConnectionInformation connectionInformation = ConnectionInformation.fromDataSource(realDataSource);
    jdbcEventListener.onBeforeGetConnection(connectionInformation);
    try {
      conn = ((DataSource) realDataSource).getConnection();
      connectionInformation.setConnection(conn);
      if (conn.getMetaData() != null) { // ######## HERE ########
        connectionInformation.setUrl(conn.getMetaData().getURL());
      }
      connectionInformation.setTimeToGetConnectionNs(System.nanoTime() - start);
      jdbcEventListener.onAfterGetConnection(connectionInformation, null);
    } catch (SQLException e) {
      connectionInformation.setTimeToGetConnectionNs(System.nanoTime() - start);
      jdbcEventListener.onAfterGetConnection(connectionInformation, e);
      throw e;
    }

    return ConnectionWrapper.wrap(conn, jdbcEventListener, connectionInformation);
  }

  // ...
}

 

그래서 LazyConnectionDataSourceProxy 를 사용하더라도 WriteOrReadOnlyRoutingDataSource(AbstractRoutingDataSource)에서 항상 Write(Primary) DB의 DataSource를 가져오는 것이다.

 


사실 운영 환경에서는 (아마도... 👀) p6spy를 사용하지 않을 것이다.

그러나 위에서 살펴보듯 상황에 따라 이렇게 AbstractRoutingDataSourceLazyConnectionDataSourceProxy를 사용한 설정도 무력화가 될 수 있음과,

어떤 기술을 적용할 때 내부 원리를 알게되면 이를 파악하기가 좀 더 수월해짐을 보여주고 싶었다 :)

댓글