문제 발견: LazyConnectionDataSourceProxy 톺아보기

반응형

 

 

read-only일때 replica 사용하게 하기

master-slave 구조 이해하기DB에서 master-slave 형태로 변경하다는 뜻은 읽기 전용과 쓰기 전용을 분리한다는 의미입니다.최근에는 master-slave라는 용어보다는 primary-replica라는 용어로 더 많이 사용이

b-programmer.tistory.com

Master-Slave 환경을 구성하던 중 이상한 현상을 발견했습니다. 정상적인 구조라면 @Transactional(readOnly = true) 상황에서는
Primary가 아니라 Replica로 라우팅되어야 합니다. 하지만 테스트를 진행해보니 예상과 다르게 ReadOnly 트랜잭션에서도 Primary가 사용되는 상황이 발생했습니다. 코드를 잘못 작성한 것일까 여러 부분을 확인해보았지만 특별히 문제를 발견하지는 못했습니다. 그러던 중 LazyConnectionDataSourceProxy를 사용하면 이 문제가 해결된다는 것을 발견했습니다. 실제로 적용해보니 ReadOnly 트랜잭션에서 정상적으로 Replica로 라우팅되었습니다. 하지만 여기서 한 가지 의문이 들었습니다. LazyConnectionDataSourceProxy를 하나 추가했을 뿐인데 왜 문제가 해결된 것일까요? 단순히 붙였더니 된다 수준으로 넘어가기에는 이 동작의 원인을 정확히 이해하지 못한 상태였습니다. 그래서 이번 글에서는 LazyConnectionDataSourceProxy가 무엇인지, 그리고 왜 이 문제가 발생했고 어떻게 해결되는지를 제대로 알아보겠습니다.

LazyConnectionDataSourceProxy란 무엇일까?

간단히 말해 LazyConnectionDataSourceProxy는 커넥션 시점을 늦추는 역할을 합니다.
이 문제를 이해하기 위해서는 먼저 스프링이 언제 데이터베이스 커넥션을 획득하는지 살펴볼 필요가 있습니다.
트랜잭션이 새롭게 시작될 때마다 데이터베이스와 통신하기 위해 Connection을 하나 획득하게 됩니다.
다음과 같은 코드가 존재한다고 합시다.

이 코드는 게시글을 수정하는 간단한 코드입니다. 스프링에서 @Transactional이 적용된 메서드가 호출되면 TransactionManager가 실행되며 트랜잭션을 시작합니다. 이 과정에서 DataSource로부터 Connection을 하나 획득하게 됩니다.

우리가 주목해야 하는 부분은
1. TransactionManager가 트랜잭션이 실행이되며 트랜잭션이 시작이 된다. 
2. 그 과정에서 DataSource가 어떻게 Connection을 획득하게 하는가?

이 글을 보면 TransacinalManager이 트랜잭션을 얻는 방법에 대해 나와있습니다. 

 

어떻게 트랜잭셕은 Read only 여부를 알 수 있을까?

read-only일때 replica 사용하게 하기master-slave 구조 이해하기DB에서 master-slave 형태로 변경하다는 뜻은 읽기 전용과 쓰기 전용을 분리한다는 의미입니다.최근에는 master-slave라는 용어보다는 primary-repl

b-programmer.tistory.com

다시 복습해보면 PlatformTransactionManager -> AbstractPlatformTransactionManager으로 트랜잭션을 전달하고 있는것을 확인 할 수 있습니다. 
AbstractPlatformTransactionManager는 PlatformTransactionManager을 상속받고 있는 클래스입니다. 즉, 자식 클래스라는 뜻이죠.
트랜잭션이 시작이 되어질때, getTransaction을 얻게 되어집니다.

트랜잭션이 시작이 된다는것을 알 수 있습니다. getTransaction() 단계에서 기존 트랜잭션의 존재 여부를 확인합니다.
이후 새로운 트랜잭션이 필요하다고 판단되면 doBegin()이 호출되며, 이 과정에서 실제 Connection을 획득하고 트랜잭션이 시작됩니다.

이름에서 알 수 있듯이 AbstractPlatformTransactionManager는 추상 클래스입니다.
따라서 트랜잭션을 실제로 시작하는 doBegin()의 구체적인 동작은 하위 구현체에서 처리하게 됩니다.

DataSourceTransctionanManager을 선택하면,

Connection이 획득된다는것을 확인 할 수 있습니다. 그렇다면 LazyConnectionDataSourceProxy이라는것은 이름에서 알 수 있듯이 이 프록시는 Connection을 획득하는 시점을 지연시키는 역할을 합니다. 

Connection획득시점을 지연을 시키는 이유?

문제의 상황을 다시 살펴보겠습니다.
트랜잭션이 ReadOnly인 상황에서 REPLICA가 아닌 PRIMARY로 동작이 되어지는 상황입니다.

실제 Connection이 획득되는 내부 과정은 잠시 제외하고, 위 흐름만 기준으로 생각해보겠습니다.
DataSource에서 Connection을 획득하는 시점에 라우팅이 이루어지며, 이때 어떤 데이터베이스를 사용할지 결정됩니다.

다시 생각해보면, 원래는 라우팅 이후에 사용할 데이터베이스가 결정되어야 합니다. 하지만 실제로는 라우팅이 수행되기 전에 이미 Connection이 획득되었습니다. 이는 곧 라우팅 로직이 반영되기 전에 데이터베이스 선택이 먼저 이루어졌다는 뜻입니다.

저희가 해야할일은 라우팅 이후에 커넥션을 받도록 유도를 하는것입니다. 이것을  LazyConnectionDataSourceProxy을 통해 커넥션 시점을 뒤로 미루는 이유도 요겁니다.

LazyConnectionDataSourceProxy의 코드를 확인해봅시다.

Connection을 받는 부분을 살펴봅시다.

이걸로 Connection은 프록시로 만들어졌고 다음과 같은 과정을 가지게 됩니다. 

LazyConnectionDataSourceProxy -> Connection Proxy -> Statement 실행 시 -> 실제 Connection 획득

결론

Primary-Replica 환경에서 예상과 다르게 데이터베이스 라우팅이 정상적으로 동작하지 않는 상황을 확인할 수 있었습니다. LazyConnectionDataSourceProxy를 적용하자 해당 문제가 해결되는 것을 확인할 수 있었고, 이를 통해 왜 이 클래스가 필요한지에 대해 살펴보는 시간을 가졌습니다. 결국 문제의 핵심은 Connection을 언제 획득하느냐에 있었으며, LazyConnectionDataSourceProxy는 Connection 획득 시점을 지연시켜 올바른 데이터베이스 라우팅이 이루어지도록 도와주는 역할을 한다는 것을 알 수 있었습니다.

반응형

'개발' 카테고리의 다른 글

Actuator 메트릭 생성 하기  (0) 2026.03.11
Actuator  (0) 2026.03.11
OpenAI는 어떻게 PostgreSQL을 스케일했을까  (0) 2026.03.10
프록시 JDBC(2)  (0) 2026.03.09
프록시 JDBC(1)  (0) 2026.03.08

댓글

Designed by JB FACTORY