DBCP (HikariCP)

오늘 포스팅으로는
Database Connection Pool (DBCP) 에 대해 정리해보겠다.
 

배경

Application 과 Database 간의 통신은 TCP 기반으로 통신한다.
따라서, 본격적으로 데이터를 주고 받기 이전에 연결을 하는 과정을 거친다. (TCP 연결 지향 특성)
-> 3 way-handshaking
또한, 데이터를 주고 받은 다음 연결을 끊어주는 과정도 거친다. (TCP 연결 지향 특성)
-> 4 way-handshaking
 
실제 데이터를 주고 받기 전 후로 연결을 맺고 끊는 과정이 시간이 오래 걸리므로
Backend API 응답 시간에 영향을 주게 되며
이는 처리량에 영향을 주게된다
 

또한, Database Connection 개수의 제한을 두지 않으면 DB 과부하 상태가 될 수 있다.

 

DBCP

따라서, Backend Application 이 띄워질때

"미리 Database 와 연결"을 맺어서 "일정 갯수 제한"을 둔 Connection 들을 Pool 로써

관리할 필요가 생겼고 이를 DBCP 라 부른다.
 

동작 (happy case)

1. Backend Application 에 요청이 들어와서 DB 에 쿼리 보낼 일이 생겼다.
2. 요청 스레드는 Connection Pool 에 connection 을 하나 요청한다. (getConnection Call)
3. Connection Pool 은 idle 상태의 connection 을 할당해준다. (해당 connection 은 in-use 상태가 된다.)
4. 요청 스레드는 할당받은 in-use 상태의 connection 을 가지고 원하는 쿼리를 수행한다.
5. 요청 스레드는 connection 을 Connection Pool 에 반환한다. (closeConnection Call, 다시 idle 상태가 된다.)
 
참고
Connection Pool 에서 getConnection 으로 connection 을 할당 받으면,
connection pool 에서 사라지는게 아니다. 그냥 상태가 변하는 것이다. (idle -> in-use)
 

HikariCP

HikariCP 는 JDBC Connection pool 이다.
DataSource 인터페이스를 구현한 객체는 HikariCPDataSource 이며,
Spring 에서 기본으로 사용된다.
 
HikariCP 공식 문서를 보면서 HikariCP 의 설정 값들에 대해 정리해보겠다.
 

필수 설정

필수 설정들은 기본 값이 존재하지 않는다.
 
[driverClassName]
각 Database 밴더에서 제공하는 JDBC Driver 구현체 이름을 적어준다.
 
ex.
org.h2.Driver
com.mysql.cj.jdbc.Driver
...
 
[username]
Database 접속을 위한 사용자 이름
 
[password]
Database 접속을 위한 사용자 비밀번호
 

자주 사용되는 옵션 설정

[autoCommit]
connection pool 에서 관리하는 connection 의 Auto-commit 동작을 지정해준다.
기본 값 : true
 
참고
대다수의 Database 는 auto-commit 트랜잭션 모드와 명시적 트랜잭션 모드를 지원한다.
JDBC Driver 는 auto-commit 모드가 true 이면 auto-commit 트랜잭션 모드로 실행하고,
auto-commit 모드가 false 이면 명시적 트랜잭션 모드로 실행한다.
-> Connection::setAutoCommit(false) // 이 코드가 트랜잭션을 명시적으로 실행하는 코드이다.
 
[connectionTimeout]
사용자가 Connection pool 에 connection 을 하나 요청했을 때, 할당 될 때 까지의 임계 시간이다.
특징
웹 응답을 기다리고 있는 Client 의 인내심을 고려해야한다.
기본 값 : 30000 (단위는 ms, 최소 허용 값은 250)
 
[maxLifetime]
Connection pool 이 관리하는 connection 이 최대로 살아있을 수 있는 최대 시간이다.
idle 상태의 connection 중 maxLifetime 에 도달한 connection 은 Connection Pool 에서 삭제한다.
특징
1. maxLifetime 에 도달했지만, in-use 상태의 connection 이면 즉시 삭제 되지 않고 idle 상태가 되어야 삭제된다.
1. 대량 멸종을 방지하고자 커넥션 별로 값이 조금 씩 다르다. (minor negative attenuation)
2. Database 자체 커넥션 시간 제한(MySQL, wait_timeout)보다 몇 초 짧게 둬야한다.
(MySQL, wait_timeout 옵션 : DB 자체적으로 idle 상태의 커넥션에 시간제한을 둔다. idle 상태 벗어나면 시간 reset)
-> maxLifetime 과 wait_timeout 을 동일하게 10 초로 두면..
application 에서 9초에 SQL 을 보냈지만 DB 에는 11 초에 도착한다면 DB 에서는 이미 커넥션이 끊긴 이후다..
3. 값을 0 으로 두면 시간 제한이 없는 무한이다.
기본 값 : 1800000 (단위는 ms, 최소 허용 값은 30000)
 
[idleTimeout]
Connection pool 이 관리하는 connection 이 idle 상태로 존재할 수 있는 최대 시간이다.
특징
1. maximumPoolSize 보다 minimumIdle 이 작을 때 적용된다.
2. Connection Pool 에 존재하는 connection 이 miniumIdle 값 이하라면 connection 은 만료되지 않는다.
3. 값을 0 으로 두면 시간 제한이 없는 무한이다.
기본 값 : 600000 (단위는 ms, 최소 허용 값은 10000)
 
[keepaliveTime]
Database 에도 자체적으로 커넥션에 대해 시간 제한을 둔다. (MySQL, wait_timeout)
따라서, HikariCP 가 관리하는 커넥션 풀에 존재하는 커넥션이
Database 에 의해 의도하지 않게 커넥션이 종료 되는 것을 방지해야한다.
HikariCP 는 연결 유지를 위해 특정 행동(keepalive)을 수행하는데 해당 행동의 빈도에 관한 설정이다.
특징
1. keepaliveTime 은 maxLifetime 보다 작아야한다.
2. 특정행동(keepalive) 은 커넥션이 idle 상태일 때 수행된다.
3. keepalive 시간이 되면, connection pool 에서 빠지고
"pinged"(keepalive) 된 다음 다시 connection pool 에 넣어진다.
4. ping 은 JDBC 버전 4 이상부터 지원되는 Connection::isValid 메서드로 수행하거나
hikariCP 의 connectionTestQuery 로 수행한다.
5. 값을 0 으로 두면 시간 제한이 없는 무한이다.
기본 값 : 0 (단위 ms, 최소 허용 값 30000, "분" 단위의 값으로 할 것을 추천)
 
[connectionTestQuery]
idle 상태의 connection 이 아직 유효한지 확인할 때 수행되는 쿼리이자,
connection 이 사용자로 부터 할당되기 직전에 아직 유효한지 확인할 때 수행되는 쿼리이다.
특징
JDBC 의 Connection::isValid 가 지원되지 않는 드라이버용으로,
JDBC 버전 4 이상이라면, 해당 설정은 하지 않는 것을 추천한다.
기본 값 : none
 
[minimumIdle]
HikariCP 가 Connection Pool 에 idle 상태의 Connection 을 최소한 몇개를 가지고 있으려고 하는 설정이다.
특징
1. Connection Pool 전체 connection 수 (idle + in-use) 가 maximumPoolSize 값 보다 작은 상태에서,
idle connection 수가 minimumIdle 값 보다 작으면 그 때 추가로 connection 을 생성한다.
-> "참고 - minimumIdle vs maximumPoolSize" 에서 좀 더 자세하게 알아보겠다.
2. 갑작스럽게 급증하는 connection 수요가 생길 것을 대비해 이 설정은 하지말고
고정 크기의 connection pool 로 사용할 것을 추천
기본 값 : maximumPoolSize 와 동일
 
[maximumPoolSize]
idle 상태의 connection 과 in-use 상태의 connection 갯수를 모두 합한 값인 connection pool 최대 사이즈 임계 값이다.
즉, 해당 application 와 데이터 베이스간의 최대 연결 수를 설정하는 옵션이다.
특징
1. Application scale out 을 고려하고 Database 자체의 최대 커넥션 수 보다는 작아야한다.
(MySQL 의 max_connections 옵션과 관련)
2. tomcat thread pool 의 thread 최대 갯수(server.tomcat.threads.max 값)를 고려해야한다.
3. 현재 connection pool 사이즈가 해당 값이며 idle 상태의 connection 이 없는 상태에서,
getConnection 요청을 받으면 connectionTimeout 값 동안 요청이 "blocking" 된다.
4. https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
기본 값 : 10
 
참고 - minimumIdle vs maximumPoolSize
Connection Pool 에 idle 상태의 Connection 수가
minimumIdle 수 보다 작으면 추가 Connection 을 맺는다고 했는데
maximumPoolSize 을 초과한다면 어떻게 될까?
-> maximumPoolSizeminimumIdle 보다 우선순위가 높게 동작한다.
(포스팅 아래에 동작 예시를 보면 쉽게 알아볼 수 있다.)
 
[metricRegistry]
connection pool 에서 다양한 "metric" 을 기록하는데 사용할
인스턴스(Codahale/Dropwizard MetricRegistry)를 지정하는 옵션이다.
특징
1. IoC container 에서 사용 가능
2. https://github.com/brettwooldridge/HikariCP/wiki/Dropwizard-Metrics
기본 값 : none
 
[healthCheckRegistry]
connection pool 의 현재 상태를 기록하는데 사용할
인스턴스(Codahale/Dropwizard HealthCheckRegistry)를 지정하는 옵션이다.
특징
1. IoC container 에서 사용 가능
2. https://github.com/brettwooldridge/HikariCP/wiki/Dropwizard-HealthChecks
기본 값 : none
 
[poolName]
Connection pool 의 사용자 정의 이름을 지정한다.
기본 값 : auto-generated
 

자주 사용되지 않는 옵션 설정

[initializationFailTimeout]
[isolateInternalQueries]
[allowPoolSuspension]
[readOnly]
[registerMbeans]
[catalog]
[connectionInitSql]
[driverClassName]
[transactionIsolation]
[validationTimeout]
[leakDetectionThreshold]
[datasource]
[schema]
[threadFactory]
[scheduledExecutor]
 
 

동작 예시

설정은 minimumIdle : 3, maximumPoolSize : 4 인 상태라 가정한다.
connection pool 에 idle, in-use 상태의 connection 갯수를 같이 보면서 동작을 보자
 
1. 초기 상태  (connection pool : idle(3), in-use(0))
2. idle connection 하나가 in-use 로 되었다. (connection pool : idle(2), in-use(1))
3. HikariCP 는 idle connection 을 생성한다. (connection pool : idle(3), in-use(1))
-> minimumIdle 조건 따름
4. idle connection 하나가 in-use 로 되었다. (connection pool : idle(2), in-use(2))
-> minimumIdle 조건을 따라 idle connection 을 생성해야하지만,
maximumPoolSize 조건에 의해 생성하지 않는다. (maximumPoolSizeminimumIdle 보다 우선)
5. idle connection 두개가 in-use 로 되었다. (connection pool : idle(0), in-use(4))
6. 이 상태에서 idle connection 한개를 얻어보면 connectionTimeout 값 동안 요청이 "blocking" 된다.
7. in-use connection 4개가 idle 로 반환 되었다. (connection pool : idle(4), in-use(0))
8. HikariCP 는 각 connection 을 maxLifetime, idleTimeout 에 따라 idle connection 을 종료하고 
minimumIdle 에 따라 다시 생성한다. 이 때, 초과분으로 존재하던 idle connection 갯수를 유지할 필요는 없다.
(connection pool : idle(3), in-use(0))
 
하지만, HikariCP 에서는 minimumIdlemaximumPoolSize 를 동일한 값으로 사용하여
고정크기의 Connection Pool 을 사용하기를 권장한다. 기본 값도 그렇게 되어있으며,
이유는 DBCP 자체가 미리 connection 을 맺어두는 것인데 minimumIdlemaximumPoolSize 보다
작은 값으로 설정한 상황에서 갑자기 idle connection 수 보다 많은 요청이 생기면
connection 을 그때그때 생성하여 반환하고 해야하기 때문에 DBCP 의 존재 의의가 퇴색되기 때문이다.
 
 
참고
HikariCP
https://github.com/brettwooldridge/HikariCP
https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing
https://github.com/brettwooldridge/HikariCP/wiki/Dropwizard-Metrics
MySQL
https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html
Commons DBCP
https://d2.naver.com/helloworld/5102792