-
r2dbc-pool. R2dbcNonTransientResourceException: Connection validation failed개발 기록 2023. 3. 14. 12:50728x90
지난 기록에서 webflux + jwt(es256) 을 구현하던중 db의 연결에서 문제가 발생되었던 부분이 있다.
환경, 증상, 원인, 해결방법에 대해 정리한다.
코드는 github에 있다.( https://github.com/neunggu/auth )
1. 관련된 환경
java 17
spring boot 2.7.6
spring-boot-starter-data-r2dbc ( r2dbc-pool: 0.9.2 포함)
r2dbc-mariadb:1.0.3
postman을 이용한 api 테스트
2. 증상
호출 시 중간중간 db connection을 못가져옴
org.springframework.dao.DataAccessResourceFailureException: Failed to obtain R2DBC Connection; nested exception is io.r2dbc.spi.R2dbcNonTransientResourceException: Connection validation failed
3. 원인
db의 설정과 r2dbc의 설정(pool에 관련된 부분)이 맞지 않았다.
구체적으로는 db의 wait_timeout 설정이 r2dbc에 설정된 값보다 작을 경우 db에서 연결을 먼저 끊으면서 문제가 발생한다.
당연히 db연결이 종료되었다는 에러로그가 떨어진다.
4. 해결방법
r2dbc-pool을 이용해 pool의 설정값을 바꿔준다. r2dbc-pool을 사용하기 위한 몇가지 방법이 있다.
a. 프로퍼티에서 db를 설정할 경우 pool을 넣어준다.
spring.r2dbc.url=r2dbc:pool:mariadb://{host}:{port}
=> pool이 기본값으로 생성된다. pool에 대한 설정을 할 수가 없어 보인다.(확실하지 않다.)
b. URL Connection Factory Discovery (r2dbc-pool에 나와 있다. 하단 참조링크)
// Creates a ConnectionPool wrapping an underlying ConnectionFactory ConnectionFactory pooledConnectionFactory = ConnectionFactories.get("r2dbc:pool:<my-driver>://<host>:<port>/<database>[?maxIdleTime=PT60S[&…]"); // Make sure to close the connection after usage. Publisher<? extends Connection> connectionPublisher = pooledConnectionFactory.create();
=> url 뒤에 [?maxIdleTime=PT60S[&…] 부분을 보면 설정을 넣을 수 있는 듯 하다.(시도해 보진 않았다.)
c. Programmatic Connection Factory Discovery (r2dbc-pool에 나와 있다. 하단 참조링크)
// Creates a ConnectionPool wrapping an underlying ConnectionFactory ConnectionFactory pooledConnectionFactory = ConnectionFactories.get(ConnectionFactoryOptions.builder() .option(DRIVER,"pool") .option(PROTOCOL,"postgresql") // driver identifier, PROTOCOL is delegated as DRIVER by the pool. .option(HOST,"…") .option(PORT,"…") .option(USER,"…") .option(PASSWORD,"…") .option(DATABASE,"…") .build());
=> pool이 기본값으로 생성된다. r2dbc-pool 링크에 보면 pool에 대한 옵션을 넣을 수 있는 듯 하다.(시도해보진 않았다.)
예상) .option(Option.valueOf("initialSize"), 3)
d. Programmatic Configuration (선택) (r2dbc-pool에 나와 있다. 하단 참조링크)
문제해결을 위해 선택한 방법이다.
// Creates a ConnectionFactory for the specified DRIVER ConnectionFactory connectionFactory = ConnectionFactories.get(ConnectionFactoryOptions.builder() .option(DRIVER,"postgresql") .option(HOST,"…") .option(PORT,"…") .option(USER,"…") .option(PASSWORD,"…") .option(DATABASE,"…") .build()); // Create a ConnectionPool for connectionFactory ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) .maxIdleTime(Duration.ofMillis(1000)) .maxSize(20) .build(); ConnectionPool pool = new ConnectionPool(configuration);
ConnectionFactory를 ConnectionPoolConfiguration에 담아서 ConnectionPool객체를 만든다.(ConnectionPool은 ConnectionFactory를 상속받고 있기 때문에 ConnectionFactory bean에 ConnectionPool을 생성해 사용하면 된다.)
AbstractR2dbcConfiguration을 상속받아 ConnectionFactory를 구현하면 DatabaseClient가 자동으로 Bean에 등록되기 때문에 db 호출하기에 편하다. (pool은 최초 쿼리 호출시 생성된다.)
참고
https://github.com/neunggu/auth/blob/main/src/main/java/com/quackquack/auth/config/DBConfig.java
@Configuration public class DBConfig extends AbstractR2dbcConfiguration { @Bean @Override public ConnectionFactory connectionFactory() { ConnectionFactory connectionFactory = ConnectionFactories.get(builder() .option(DRIVER, driver) .option(HOST, host) .option(PORT, port) .option(DATABASE, database) .option(USER, user) .option(PASSWORD, password) .build()); ConnectionPoolConfiguration connectionPoolConfiguration = ConnectionPoolConfiguration.builder(connectionFactory) .initialSize(2) .minIdle(2) .maxSize(10) .maxLifeTime(Duration.ofSeconds(1800)) .maxIdleTime(Duration.ofSeconds(1800)) .build(); return new ConnectionPool(connectionPoolConfiguration); } }
** 주의할 점
- 옵션에 DRIVER, PROTOCOL을 사용하면 안된다.
pool에 대한 설정은 ConnectionPoolConfiguration에서 해주기 때문이다. DRIVER에 바로 "mariadb" 같은 드라이버를 바로 적어줘야한다. 혹시나 실수로 DRIVER, PROTOCOL을 넣게되면 기본값으로 pool이 생성되고 아래의 ConnectionPoolConfiguration설정으로 또 한번 pool이 생성되기 때문에 예상치 못한 오류에 빠질 수 있다. - ConnectionPoolConfiguration에서 initialSize를 설정하지 않으면 기본값으로 10개가 생성된다. ConnectionPoolConfiguration Builder의 applyDefaults()을 참고.
- maxIdleTime을 설정하면 설정된 시간 이후에 사용안하는 풀의 연결이 제거된다.
설정한 시간마다 제거되는게 아니라 그 이후에 제거된다. 이러한 이유로 db의 wait_timeout 설정보다 20~30초 정도 짧게 설정하는게 좋을 듯 하다.
----------
참고
https://github.com/r2dbc/r2dbc-pool
728x90반응형'개발 기록' 카테고리의 다른 글
Spring Boot[2.x.x] is not compatible with this Spring Cloud release train (0) 2023.04.03 java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration (0) 2023.03.27 DB 이중화하기 with Spring AOP (0) 2023.03.19 Spring AOP에서 만난 예상치 못한 오류 --enable-preview (0) 2023.03.15 Webflux + Jwt(es256) (1) 2023.03.14 - 옵션에 DRIVER, PROTOCOL을 사용하면 안된다.