DBCP 간단히 정리하기

라모스·2023년 3월 26일
1
post-thumbnail

DBCP를 안쓸 때 문제점

  • 매번 connection을 열고 닫는 시간적인 비용 발생
  • 서비스 성능에 좋지 않다.

커넥션을 새로 만드는 것은 과정도 복잡하고 시간도 많이 소모되는 일이다. DB는 물론이고 애플리케이션 서버에서도 TCP/IP 커넥션을 새로 생성하기 위한 리소스를 매번 사용해야 한다. 이는 SQL을 실행하는 시간에 더해 커넥션을 새로 만드는 시간까지 추가되기 때문에 애플리케이션의 응답 속도에 영향을 준다.

DBCP (database connection pool)

미리 DB Connection을 맺어둔다. 연결된 커넥션들을 마치 풀처럼 두고 connection을 재사용한다.

  • connection 재사용
  • 열고 닫는 시간 절약

애플리케이션 로직은 커넥션 풀에서 받은 커넥션을 사용해서 SQL을 DB에 전달하고 그 결과를 받아 처리한다. 커넥션을 모두 사용하고 나면 커넥션을 종료하는 것이 아닌, 다음에 다시 사용할 수 있도록 해당 커넥션을 그대로 커넥션 풀에 반환한다.

  • 커넥션 풀은 서버당 최대 커넥션 수를 제한할 수 있다. 따라서 DB에 무한정 연결이 생성되는 것을 막아주어 DB를 보호하는 효과도 있다.
  • 커넥션 풀은 얻는 이점이 매우 크기 때문에 실무에서는 항상 기본으로 사용한다.
  • 대표적인 커넥션 풀 오픈소스는 commons-dbcp2, tomcat-jdbc-pool, HikariCP 등이 있다.
  • Spring Boot 2.0 부터는 기본 커넥션 풀로 hikariCP를 제공한다.

DBCP 설정 방법

MySQL과 HikariCP를 기준으로 중요한 것 위주로 정리해보자.

DB 서버의 설정은 다음과 같다.

  • max_connections: client와 맺을 수 있는 최대 connection 수
  • wait_timeout: connection이 inactive 할 때 다시 요청이 오기까지 얼마의 시간을 기다린 뒤에 close 할 것인지를 결정. 시간 내에 요청이 도착하면 0으로 초기화.

wait_timeout은 비정상적인 connection 종료, connection을 다 쓰고 반환이 안되는 경우, 네트워크 단절의 경우와 같은 문제 시 connection을 반환할 수 있도록 한다.

Backend Application에서의 설정은 다음과 같다.

  • minimumIdle: pool에서 유지하는 최소한의 idle connection 수
    • idle connection 수가 minimumIdle보다 작고, 전체 connection 수도 maximumPoolSize보다 작다면 신속하게 추가로 connection을 만든다.
  • maximunPoolSize: pool이 가질 수 있는 최대 connection 수. idle과 active(in-use) connection 합쳐서 최대 수.
  • maxLifetime: pool에서 connection의 최대 수명. maxLifetime을 넘기면 idle일 경우 pool에서 바로 제거, active인 경우 pool로 반환된 후 제거.
    • pool로 반환이 안되면 maxLifetime이 동작하지 않는다. pool로 반환을 잘 시켜주는 것이 중요함.
    • pool로 반환이 안된다면 어쨌든 active한 상태로 인식되는데, MySQL의 wait_timeout 설정으로 DB상의 커넥션을 끊게 되면 backend application 상에 남아있는 같은 connection을 가지고 요청이 또 들어왔을 경우 DB에 요청이 전달되지 않고 exception이 발생하게 된다.
    • DB의 connection time limit보다 몇 초 짧게 설정해야 한다.
  • connectionTimeout: pool에서 connection을 받기 위한 대기 시간.

HikariCP minimumIdle 권장 사항

HikariCP에서의 minimumIdle의 기본 값은 maximumPoolSize와 동일 (= pool size 고정)하게 사용하도록 권장한다.

minimumIdle < maximumPoolSize일 경우, 트래픽이 몰려올 때마다 connection을 추가로 만들어야 하는데 이 connection을 생성하는 속도보다 트래픽이 몰려오는 속도가 더 빠르다면, 백엔드 서버의 응답이 느려질 수 있기 때문에 처음부터 적절한 갯수로 pool size를 고정하도록 가이드를 제시하는 것이다.

Commons DBCP2 Datasource 설정하기

만약, Spring Boot의 HikariCP 대신 Commons DBCP2로 변경하고자 한다면 다음과 같이 pom.xml에 의존성을 설정하면 된다.

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.9.0</version>
</dependency>

Apache Commons DBCP 설정

커넥션풀 설정값 (DBCP2에서 이름)설명
initialSize클래스 생성 후 최초로 getConnection() 메서드를 호출할 때 커넥션 풀에 채워 넣을 커넥션 개수 (기본값 0)
minIdle최소한으로 유지될 Connection 객체의 수 (기본값 0)
maxIdle반납된 유휴 Connection 객체를 유지할 수 있는 최대 값 (기본값 8)
maxActive (maxTotal)동시에 사용할 수 있는 최대 커넥션 갯수 (기본값 8)톰캣의 스레드는 200개인데그거에 맞춰서 커넥션도 200개 만드는 것이 좋음
maxWait (maxWaitMillis)할당받을 Connection 객체가 없을 때 스레드를 블록시킬 시간 (1/1000초 단위)

Apache Commons DBCP 커넥션의 검사와 정리용 설정

네트워크가 죽고 다시 살아났을때, 사용하는 옵션들

커넥션풀 설정값설명
validationQuery풀에 커넥션을 반환하기 전이나, 풀을 획득하기 전에 커넥션이 valid한지를 검사, mysql 기준으로 보통 select 1 설정커넥션의 연결(건강)을 확인하기 위해
testOnBorrow커넥션 풀에서 커넥션을 얻어올 때 테스트 실행 (default = true)
testOnReturn커넥션 풀로 커넥션을 반환할 때 테스트 실행 (default = false)
testWhileIdleEvictor 가 실행될 때 커넥션 풀 안에 있는 유휴 상태의 커넥션을 대상으로 테스트 실행 (default = false)
maxConnLifetimeMillis커넥션의 최대 라이프타임을 지정 (default = -1)
logExpiredConnections로그로 maxConnLifetimeMillis를 초과한 경우에 커넥션이 닫혔음을 남김 (default = true)
lifotrue 로 설정하면 최근에 반환한 커넥션을 가장 우선 대여해 줍니다. (기본값)Last in first out

유효성 검사 쿼리 설정

JDBC 커넥션의 유효성은 validationQuery 옵션에 설정된 쿼리를 실행해 확인할 수 있다. Commons DBCP 1.x에서는 다음과 같은 세 가지 테스트 옵션으로 유효성을 검사한다. 유효성을 검사할 때는 validationQuery 옵션에 하나 이상의 결과를 반환하는 쿼리를 설정해야 한다. Commons DBCP 2.x에서는 validationQuery 옵션이 없을 때 Connection.isValid() 메서드를 호출해 유효성을 검사한다.validationQuery는 밴더사 마다 다른 쿼리문을 사용하길 권장한다 그중 MySQL은 "select 1"로 사용한다.

  • testOnBorrow: 커넥션 풀에서 커넥션을 얻어올 때 테스트 실행(기본값: true)
  • testOnReturn: 커넥션 풀로 커넥션을 반환할 때 테스트 실행(기본값: false)
  • testWhileIdle: Evictor 스레드가 실행될 때 (timeBetweenEvictionRunMillis > 0) 커넥션 풀 안에 있는 유휴 상태의 커넥션을 대상으로 테스트 실행(기본값: false)

검증에 지나치게 자원을 소모하지 않게 testOnBorrow 옵션과 testOnReturn 옵션은 false로 설정하고, 오랫동안 대기 상태였던 커넥션이 끊어지는 현상을 막게 testWhileIdle 옵션은 true로 설정하는 것을 추천한다.

Datasource Bean 등록

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Slf4j
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "mysql")
public class DataSourceConfig {

    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private Integer maxIdle;
    private Integer maxTotal;
    private Integer initialSize;
    private Integer minIdle;

    @Bean
    public DataSource dataSource() {
        BasicDataSource basicDataSource = new BasicDataSource();

        basicDataSource.setDriverClassName(driverClassName);
        basicDataSource.setUrl(url);
        basicDataSource.setUsername(username);
        basicDataSource.setPassword(password);
        basicDataSource.setMaxIdle(maxIdle);
        basicDataSource.setMaxTotal(maxTotal);
        basicDataSource.setInitialSize(initialSize);
        basicDataSource.setMinIdle(minIdle);

        basicDataSource.setValidationQuery("SELECT 1");
        basicDataSource.setTestOnReturn(true);
        basicDataSource.setTestOnBorrow(true);
        basicDataSource.setTestWhileIdle(true);

        return basicDataSource;
    }
}

참고하면 좋을 자료

profile
Step by step goes a long way.

0개의 댓글