진행하던 프로젝트가 끝나고 테스트와 분석용으로 만든 페이지를 사용할 일이 없어졌다.
이걸 가지고 무엇을 더 할 수 있을까, 라고 고민을 하던 중 DB와 관련된 설정들을 기본으로만 해두고 세부적으로 설정하지 않은 것이 기억났다.
실무에서 꼭 설정이 필요한 DB Connection Pool과 DB 암호화에 대해 공부하며 설정을 바꾸었고 현 포스팅은 DB Connection Pool과 스프링 부트에서 사용하는 커넥션 풀 중 한 종류인 Hikari CP에 대해 정리하려 한다.
환경 설정
- Spring Boot 2.7.11
- Gradle 7.6.1
- IntelliJ IDEA 2023.1
DB Connection Pool(DBCP)
일반적인 데이터 연동은 웹 애플리케이션이 데이터를 필요할 때마다 매번 데이터베이스에 연결하여 데이터를 주고 받는다. 하지만 이 방법은 요청이 올 때마다 DB와 연결하고 끊는 과정을 반복하기 때문에 시간이 많이 소요되며 비효율적이다.
이 문제를 해결하기 위해 DB Connection Pool(DBCP)은 웹 애플리케이션이 실행됨과 동시에 연동할 DB와 연결을 미리 설정해 두고 필요에 따라 미리 연결한 connection을 사용하고 반납하여 빠른 연동 작업을 가능하게 한다.
간단하게 정리하면, DBCP는 웹 컨테이너(WAS)가 실행되면서 DB와 미리 연결(connection)을 해놓은 객체들을 pool에 저장해두었다가 클라이언트 요청이 오면 connection을 빌려주고, 처리가 끝나면 다시 connection을 반납받아 pool에 저장하는 방식을 말한다.
DBCP의 특징
- 웹 컨테이너(WAS)가 실행되면서 connection 객체를 미리 pool에 생성
- HTTP 요청에 따라 pool에서 connection 객체를 빌려 쓴 후 반환
- 이와 같은 방식으로 물리적인 데이터베이스 connection(연결) 부하를 줄이고 연결 관리
- pool에 미리 connection이 생성되어 있기 때문에 connection을 생성하는 데 드는 요청마다 연결 시간이 소비되지 않음
- connection을 계속해서 재사용하기 때문에 생성되는 connection 수를 제한적으로 설정
- DB와의 연결을 미리 생성하고, 그것을 관리하는 역할을 하는 오브젝트를 DataSource라고 함
DBCP의 유의사항
1. 동시 접속자가 많을 경우
- 너무많은 DB 접근이 발생하는 경우, 커넥션은 한정되어 있기 때문에 쓸 수 있는 커넥션이 반납될 때까지 기다려야 한다.
- 너무 많은 커넥션을 생성할 시에는 커넥션 또한 객체이므로 많은 메모리를 차지하게 되고, 프로그램의 성능을 떨어뜨리는 원인이 된다.
- 즉, WAS에서 커넥션 풀을 크게 설정하면 메모리 소모가 큰 대신 많은 사용자가 대기 시간이 줄어들고
반대로 커넥션 풀을 작게 설정하면 그만큼 대기 시간이 길어진다. - 따라서 사용량에 따라 적정량의 객체를 생성해 두어야 한다.
2. Connection Pool의 크기
Connection의 주체는 Thread이기 때문에 Connection Pool의 크기는 Thread와 함께 고려해야 한다.
- Thread의 크기 < Connection Pool 크기
- Thread Pool에서 트랜잭션을 처리하는 Thread가 사용하는 Connection 외에 남는 Connection은 실질적으로 메모리 공간만 차지하게 된다.
- Thread Pool 크기와 Connection Pool 모두 크기 증가
- Thread 증가로 인해 더 많은 Context Switching이 발생
- Disk 경합 측면에서 성능 한계 발생
- 데이터베잇스는 하드 디스크 하나 당 하나의 I/O를 처리하므로 블로킹이 발생
- 즉, 특정 시점부터는 성능적인 증가가 Disk 병목으로 인해 마비됨
- Hikari CP의 공식문서에 의하면, 1 connections = ( (core_count) * 2 ) + effective_spindle_count ) 로 정의
- core_count: 현재 사용하는 서버 환경에서의 CPU 개수
- Context Switching으로 인한 오버헤드를 고려하더라도 데이터베이스에서 Disk I/O(혹은 DRAM이 처리하는 속도)보다 CPU 속도가 월등히 빠르다.
- 그러므로, Thread가 Disk와 같은 작업에서 블로킹되는 시간에 다른 Thread의 작업을 처리할 수 있는 여유가 생기고, 여유 정도에 따라 멀티 스레드 작업을 수행할 수 있게 된다. Hikari CP가 제시한 공식에서는 계수를 2로 선정하여 Thread 개수를 지정하였다.
- effective_spindle_count: 기본적으로 DB 서버가 관리할 수 있는 동시 I/O 요청 수
- 하드 디스크 하나는 spindle 하나를 갖는다.
- 디스크가 16개 있는 경우, 시스템은 동시에 16개의 I/O 요청을 처리할 수 있다.
- core_count: 현재 사용하는 서버 환경에서의 CPU 개수
Spring Boot에서 지원하는 DBCP 종류
- Hikari CP(default)
- Tomcat CP
- Apache의 Commons DBCP
이 중 Hikari CP를 사용하였다.
Hikari CP
- Hikarik CP는 Spring Boot 2.0 이후부터 기본 옵션으로 채택하고 있는 DBCP이다.
- 빠르고, 간단하고 믿을 수 있는 Hikari CP는 "오버헤드 제로"의 프로덕션 지원 JDBC connection pool이다. 대략 130KB이며 이 라이브러리는 매우 가볍다.
- 💡 오버헤드(overhead)
: 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간·메모리 등
- 💡 오버헤드(overhead)
- 가볍고 빠르게 처리할 수 있는 장점이 있다.
Hikari CP의 동작 원리
Thread가 커넥션을 요청하면 커넥션 풀의 각자의 방식에 따라 유휴 커넥션을 찾아서 반환한다.
Hikari CP의 경우, 이전에 사용했던 커넥션이 존재하는지 확인하고, 이를 우선적으로 반환하는 특징이 있다.
가능한 커넥션이 존재하지 않으면, HandOffQueue를 Polling하면서 다른 Thread가 커넥션을 반납하기를 기다린다.(지정한 time out 시간까지 대기하다가 시간이 만료되면 예외를 던진다.)
최종적으로 사용한 커넥션을 반납하면 커넥션 풀이 커넥션 사용 내역을 기록하고, HandOffQueue에 반납된 커넥션을 삽입한다.
이를 통해 HandOffQueue를 Polling하던 Thread는 커넥션을 획득하고 작업을 이어나간다.
Hikari CP를 사용하여 DB Connection Pool 설정
1. build.gradle 파일에 라이브러리 추가
Hikari CP는 build.gradle의 설정에 다음과 같이 작성하면 사용가능하다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
}
2. application.properties 파일 수정
application.properties 또는 application.yml 파일을 수정하면 된다.
해당 프로젝트는 DB 정보를 properties 파일에 저장하였다.
spring.config.activate.on-profile=local
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-init-sql=SELECT 1
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
spring.datasource.hikari.schema=
#logging.level.com.zaxxer.hikari=trace
#logging.level.com.zaxxer.hikari.HikariConfig=debug
server.servlet.context-path=
server.port=
mybatis.mapper-locations=
👉 사용한 Hikari CP options
- minimum-idle
: 연결 풀에서 Hikari CP가 유지 관리하는 최소 유휴 연결 수 - maximum-pool-size
: 최대 pool size (default: 10) - connection-init-sql
: DB가 연결되었는지 확인을 위한 초기 쿼리 작성
👉 그외의 Hikari CP options
- autoCommit
: auto-commit 설정 (default: true) - connection-timeout
- 풀에서 커넥션을 얻어오기 전까지 기다리는 최대시간
- 허용가능한 wait time을 초과하면 SQLException을 던짐
- 설정 가능한 가장 작은 시간은 250ms (default: 30000ms, 30s)
- idle-timeout
- 풀에 일을 안하는 커넥션을 유지하는 시간
- minimum-idle이 maximum-pool-size보다 작게 설정되어 있을 때만 설정
- 최소 커넥션 수는 minimum-idle 수 (A connection will never be retired as idle before this timeout.)
- 최소값은 10000ms (default: 600000ms, 10minutes)
- max-lifetime
- 커넥션 풀에서 살아있을 수 있는 커넥션의 최대 수명시간
- 사용 중인 커넥션은 max-lifetime에 상관없이 제거되지 않음
- 사용 중이지 않은 경우에만 제거
- 풀이 아닌 커넥션 별로 적용(∵풀에서 대량으로 커넥션들이 제거되는 것을 방지하기 위해)
- 강력하게 설정해야 하는 설정 값으로 데이터베이스나 인프라의 적용된 connection time limit보다 작아야 함
- 0으로 설정하면 infinite lifetime이 적용됨(idle-timeout 설정 값에 따라 적용, idle-timeout 값이 설정되어 있으면 0으로 설정해도 infinite lifetime이 적용되지 않음)
- default: 1800000ms, 30minutes
- connection-test-query
- JDBC4 드라이버를 지원한다면 해당 옵션을 설정하는 것은 비추천
- JDBC4를 지원하지 않는 드라이버를 위한 옵션(Connection.isValid() API)
- 커넥션 풀에서 커넥션을 획득하기 전에 살아있는 커넥션인지 확인하기 위해 valid 쿼리를 던짐
- default: none
- pool-name
: 사용자가 pool의 이름을 지정 (default: auto-generated) - initialization-fail-timeout
: 풀에서 커넥션을 초기화할 때 성공적으로 수행할 수 없을 경우 빠르게 실패하도록 함 (default: 1) - read-only
- 풀에서 커넥션을 획득할 때 read-only 모드로 가져옴
- 몇몇 데이터베이스는 지원을 하지 않지만, 커넥션이 read-only로 설정되어 있으면 몇몇의 쿼리들이 최정화 됨
- default: false
- driver-class-name
- Hikari CP와 달리 jdbcUrl을 참조하여 자동으로 드라이버를 설정하지 못하는 경우 사용
- 이러한 몇몇의 오래된 드라이버들은 driver-class-name을 명시화해야 함
- 어떤 에러 메시지가 명백하게 표시되지 않는다면 생략 가능
- validation-timeout
: valid 쿼리를 통해 커넥션이 유효한지 검사할 때 사용되는 timeout (default: 5000ms) - leak-detection-threshold
- 커넥션 누수 로그 메시지가 나오기 전에 커넥션을 검사하여 풀에서 커넥션을 내보낼 수 있는 시간
- 0으로 설정하면 leak detection을 이용하지 않음
- 최소값: 2000ms (default: 0)
※ 참고로 Hikari CP는 R2DBC의 설정과 다르게 enabled = true (연결을 항상 유지한다.) 를 설정할 필요가 없다.
DB Connection Pool 상태 확인
위의 코드에서 주석으로 달아놓은 log를 통해서 DBCP가 연결된 상태인지 확인할 수 있다.
logging.level.com.zaxxer.hikari=trace
logging.level.com.zaxxer.hikari.HikariConfig=debug
maximumPoolSize 20으로 최대 connection 개수, minimumIdle 5 설정을 통해 최소 connection 개수 등의 정보를 알 수 있다.
프로젝트를 실행했을 때 기본적으로 연결되는 total connection이 5개 있음을 확인할 수 있다.
Hikari CP의 버전에 따라 로그 메시지의 형식이 조금씩 다를 수 있다.
번외) DB Connection Fail이 발생할 때의 대처
DB Connection이 갑작스럽게 종료되거나 재사용시 연결에 지연이 생기면 서비스를 제공하는 데 큰 장애가 발생한다.
이러한 문제를 방지하기 위해서 DB를 사용하기 전 해당 커넥션이 정상인지 검사하는 DBCP의 validationQuery 기능을 사용한다.
validationQuery 속성은 작성한 쿼리를 주기적으로 호출하여 연결을 유지할 수 있도록 한다.
validationQuery는 위의 Hikari CP의 connection-init-sql 속성과 같이 쿼리를 작성하는 속성이지만 그 의미가 약간 다르다.
- Hikari CP의 connection-init-sql
: 커넥션 풀에서 커넥션을 가져오기 전에 실행 → 데이터베이스가 유효한지 확인 - validationQuery
: 커넥션을 풀에서 가져올 때마다 실행되는 쿼리 → 커넥션이 여전히 유효한지 확인하는 데 사용
spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1
testOnBorrow 설정시 validationQuery가 반드시 필요하다.
📌 DBCP의 validationQuery를 사용할 때 주의 사항
웹 애플리케이션에서의 DB 요청은 빈번하게 발생하기 때문에 그에 따라 connection access도 빈번하게 일어난다.
요청에 대한 응답은 빠른 시간을 필요로 하기 때문에 DB의 부하를 가능한 줄여야 한다.
하지만 DBCP에서 Connection 객체를 받아올 때마다 validationQuery가 실행되면 엄청난 부하를 야기할 수 있다.
그렇다면 validationQuery는 반드시 필요한 것인가?
validationQuery는 선택적인 기능이기 때문에 할당되었을 경우에만 수행된다.
단, 이 기능을 사용하고자 할 때는 해당 쿼리의 실행결과가 반드시 1개 이상의 resultset으로 나와야 한다.
또한 해당 쿼리가 최경량의 SQL문을 사용하고 있는지 확인해야 한다. PostgreSQL의 경우 SELECT 1이 가장 적절한 쿼리이다.
[ 출처 ]
- https://velog.io/@miot2j/Spring-DB커넥션풀과-Hikari-CP-알아보기
- https://linked2ev.github.io/spring/2019/08/14/Spring-3-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80%EC%9D%B4%EB%9E%80/
- https://adjh54.tistory.com/73#2)%20HikariCP%2C%20DBCP(DataBase%20Connection%20Pool)-1
- https://effectivesquid.tistory.com/entry/HikariCP-%EC%84%B8%ED%8C%85%EC%8B%9C-%EC%98%B5%EC%85%98-%EC%84%A4%EB%AA%85
- https://jaehoney.tistory.com/22
- https://haenny.tistory.com/54
- https://docs.spring.io/spring-boot/docs/1.1.2.RELEASE/reference/html/common-application-properties.html
- https://sanghoo.tistory.com/104
[ 이미지 출처 ]
'공부 기록 > Spring' 카테고리의 다른 글
[개인공부] Spring Boot와 R2DBC 사이의 오류 (0) | 2024.02.05 |
---|---|
[Spring Boot] Jasypt를 사용하여 DB 정보 암호화 (1) | 2024.02.01 |
[개인공부] Gradle 8.0에서 발생한 에러와 해결 (2) | 2023.12.05 |
[Spring Boot] 다중 Profile을 사용해서 환경 별 구성 다르게 하기 (0) | 2023.10.23 |
[개인공부] properties 문제 해결 (0) | 2023.09.21 |