수 많은 우문은 현답을 만든다

@Transactional 과 DB Connection 반환 이슈 본문

개발지식/Springboot

@Transactional 과 DB Connection 반환 이슈

aiden.jo 2023. 6. 14. 16:44

안녕하세요 조영호입니다.

오늘은 Transational 어노테이션을 사용하면서 발생했던 한 가지 문제에 대해서 공유하고자 합니다.

@Transactional 이 선언된 하나의 메소드에서 내부적으로 3개의 메소드를 호출하고 있는 메소드에서 아래 오류가 발생했습니다.

 

오류내용

Aborted connection
- Error enlisting in transaction - connection might be broken? Please check the logs for more information...
- nested exception is org.apache.ibatis.executor.ExecutorException: Error preparing statement: the XA resource has become unavailable

서비스를 운영하다보니 자꾸 Database Connection이 끊기는 문제가 발생했고, 처음에는 Network나 Database의 문제가 아닐까 생각했습니다. 그러나 모니터링 결과 Network나 Database 단의 순단이나 오류 로그는 따로 없었습니다.

 

분석

"Aborted connection"은 클라이언트와 데이터베이스 서버 간의 연결이 예기치 않게 종료된 경우를 나타내는 메시지인데, 하루에도 수십건의 Abort connection 이벤트가 발생하고있었습니다.
Case 1. 클라이언트가 연결을 강제로 종료한 경우
Case 2. wait_timeout 또는 과 interactive_timeout같은 설정 값에 의해서 클라이언트의 비활동 연결을 종료하는 경우. 이러한 설정 값이 지정된 시간 동안 클라이언트로부터 요청이 없는 경우 연결이 종료 합니다.
Case 3. 클라이언트와 MySQL 서버 간의 통신 오류가 발생한 경우
=> 결론적으로 네트워크와 MySQL 서버 장애는 없었으므로, 클라이언트 쪽에서 트랜잭션 이후 커넥션이 제대로 반환되지 않는것은 아닌지 확인 해보기로 했습니다.

 

소스코드 분석

1. @Transactional이 적용된 메소드는 public인가?
@Transactional 어노테이션은 기본적으로 public 메소드에 적용됩니다. 스프링은 프록시 패턴을 사용하여 트랜잭션 처리를 구현하는데, 이를 위해 public 메소드에 대해서만 프록시를 생성합니다. public에 대해서만 프록시를 생성하는 이유는 외부에서 객체에 접근할 때 추가적인 로직을 수행하기 위해서입니다. 자식 메소드까지 모두 public일 필요는 없습니다.

 

2. 자식 메소드들에도 @Transactional이 선언되어있는가?
부모 메소드에 @Transactional 어노테이션이 선언되어있으면 내부 메소드들은 모두 하나의 트랜잭션으로 묶입니다.

 

3. Rollback이 제대로 되었는가?

@Transactional 어노테이션은 Unchecked Exception(Runtime Exception) 에서만 Rollback이 발생합니다.

=> 코드상에 IOException(Checked Exception) 예외를 던지는 곳이 있었는데, 이 경우 @Transactional은 롤백을 수행하지 않기 때문에 커넥션이 정상적으로 반환되지 않고 유지 됩니다 (누수 발견)

 

4. 커넥션 수는 적절한가?

기본 권장사항인 max-pool-size : 10을 쓰고있었는데, 커넥션 수에 따른 성능을 찾아보다가 좋은 포스트를 보고 아래와 같이 조정했다.

max-pool-size : 10 -> 32+1 (인프라 접근 권한이 없어 Disk 스펙은 알 수 없어서 여유롭게 50으로 설정)

 

결과

아직 8시간정도 지나면 mysql 접속이 끊기는 오류는 동일하다..

 

application.yml을 자세히 보니 아래와같이 작성되어있었다.

env.datasource.dbname.props.jdbc-url = ~&autoReconnect=true

 

음 과연 이렇게 하면 autoReconnect 옵션이 재대로 작동할까?

원칙적으로는 spring.datasource.dbname.jdbc-url 에다가 작성을 해줘야한다. 내 판단으로는 동료가 설정한 위와같은 옵션은 custom 설정이기 때문에 autoRecoonect가 되지 않은것같았다.

 

그래서 아래와같이 Datasource 빈을 생성할때 명시적으로 옵션을 넣어주었다.

@Bean
public DataSource dataSource(@Qualifier("dataSourceProperty") DataSourceProperty dataSourceProperty) {
    Properties property = new Properties();
    property.setProperty("url", dataSourceProperty.getProps().getJdbcUrl() + "&autoReconnect=true");

 

결과는 내일 아침에 다시 본다..