
그 여느때와 같이 스프링부트 run을 하고 있었는데, 문득 실행 되기 전 HikariPool이라는 것이 시작되는 것을 보고 이게 뭔데 실행되지 라는 생각에서 비롯된 공부기록이다.
우선 HikariPool은 스프링부트 기본 커넥션 풀인 HikariCP의 별칭으로, JDBC 커넥션 풀 구현체이다.
커넥션 풀에 대해 살펴보자.
DriverManager
JDBC(Java Database Connectivity)에서는 DriverManager를 사용하면 Connection을 아래와 같이 가져올 수 있다.
Connection connection = DriverManager.getConnection(DB_URL, USER, PASSWORD)
하지만 이런식으로 DB에 접근할 때마다 Connection을 생성하는 것은 비효율적이다.
Connection을 생성하는 데 비용이 많이 들기 때문이다.
커넥션을 생성하는 데 비용이 많이 든다는게 무슨 뜻이지?
보통 DB와 연결할 때 TCP 통신을 한다. 바로 이 TCP 통신으로 인해 비용 문제가 발생한다.
TCP 통신은 커넥션의 안정성과 신뢰성 보장을 위해 위해 연결 시 3 Way-handshake, 연결 종료 시 4 Way-handshake 과정을 거친다. 이 과정에서 통신 비용이 많이 소모되므로 커넥션을 계속해서 생성하는 데에 비용이 많이 드는 것이다.
즉, 잦은 연결/해제 과정으로 인해 발생하는 시간 지연과 서버 자원 소모라는 '기술적 오버헤드' 이다.
따라서 사람들은 Connection을 생성하기 위한 비용을 줄이기 위해 Connection Pool을 사용한다.
DataSource
DataSource는 DB나 파일과 같은 물리적 데이터 소스에 연결할 때 사용하는 인터페이스이다.
DataSource를 사용하면 우리는 Connection Pool을 활용할 수 있다.
DriverManager가 아닌 DataSource를 사용하는 이유도 이 때문이다.
이미 우리는 너무나 당연하게 DataSource를 yml에서 이용하고 있다.
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: DB URL
password: DB PASSWORD
hikari:
maximum-pool-size: 15
connection-timeout: 30000
커넥션 풀(Connection Pool)이란?

데이터베이스와의 연결을 미리 여러 개 만들어서 pool에 넣어두는 구조이다.
요청 올 때마다 하나씩 꺼내서 쓰고, 다시 돌려놓는 방식이다.
사실 연결을 매번 새로 만들면 엄청 느리고 비용이 크다. 그래서 커넥션 풀을 통해 미리 만들어둔다.
생각해보자. 트래픽이 많은 서비스에서는 수많은 연결이 발생하는데 이때마다 매 번 연결을 생성하면 시스템이 불안정해질 수 밖에 없다. 이러한 상황에서 커넥션 풀을 이용하면, 응답 속도를 일정하게 유지하고 DB 부하를 줄일 수 있다.
HikariCP
Spring Boot 기본 커넥션 풀이다.
가장 빠르고 락을 최소화해 성능이 뛰어나고 설정이 단순해서 거의 표준이 되었다.
maximum-pool-size
동시에 사용할 수 있는 DB 연결 수를 의미한다.
풀에서 동시에 몇 개의 DB 연결을 유지할지를 결정하는 부분이다.
maximum-pool-size = 10 → 최대 10개의 DB 연결을 동시에 사용
적정 maximum-pool-size 계산식

- Cores : 애플리케이션 서버의 CPU 코어 수
- Wait Time : DB에서 쿼리가 실제로 실행되지 않고 대기(I/O, 락 등)하는 평균 시간
- Service Time : DB가 쿼리를 실제로 실행하는 평균 시간
HikariCP는 자동으로 pool size를 산정해주지 않는다.
Spring Boot 기본값은 10이고, 변경하고 싶으면 내가 직접 yml에 명시해야 한다.
spring.datasource.hikari.maximum-pool-size: 20
우리 서비스에서는 따로 pool size를 지정해주지 않았기 때문에 10을 사용하고 있는 중이다.
커넥션 풀 사이즈를 크게 잡으면 좋은 거 아닌가요?
커넥션을 많이 만든다고 해서 성능이 무조건 좋아지는 건 아니다.
풀 사이즈가 커지면 커넥션을 얻기 위한 경쟁이 줄어들 수 있지만, DB 서버는 더 많은 세션 관리와 자원(메모리, CPU)을 할당해야 하므로 오히려 성능이 저하된다.
실제로 DB의 CPU/메모리 자원은 제한적이기 때문에 커넥션이 많아질수록 context switching과 락 경합으로 인해 오히려 DB가 과부하되어 전체 성능이 떨어지고 장애 위험이 높아진다.
ConnectionTimeOut
풀에서 커넥션을 구하기 위해 대기하는 시간을 의미한다.
최대 커넥션만큼 사용 중일 때에는 대기 큐에 들어가서 대기하게 되는데, 만약 큐에서 지정된 시간 이상 기다렸는데도 커넥션을 확보하지 못하면 요청 스레드는 Timeout Exception을 받고 작업을 실패 처리한다.
기본값은 30초인데 너무 길다.
사용자는 응답없는 상태로 30초를 기다려야한다. 따라서 기본 값 대신에 0.5~3초 이내로 설정하는 것을 권장한다. 응답이 없는 것보다는 빨리 에러 화면이라도 응답해주는게 낫기 때문이다.
보통 웹 서비스는 0.5 ~ 3초 이내로 설정하여 응답 시간을 최소화 한다.
HikariCP가 run 시 커넥션 풀을 생성한다는 뜻
- 스프링부트 run
- HikariCP 초기화
- 설정된 maximum-pool-size만큼의 연결을 DB에 요청
스프링부트가 실행되면 풀 사이즈만큼 DB 연결을 생성해둔다. 이후 HTTP 클라이언트 요청이 들어오면 커넥션 하나를 빌려서 쿼리를 실행하고 쿼리가 끝나면 다시 커넥션은 풀에 반납된다.
만약 풀 내의 모든 커넥션이 사용 중이고 풀 사이즈가 maximum-pool-size에 도달했다면, 새로운 커넥션을 요청한 스레드는 대기 큐에 들어가 순서대로 대기하게 된다. 만약, 커넥션이 반납되면 가장 먼저 큐에 진입하여 대기하고 있던 스레드에게 해당 커넥션이 순서대로 할당되는 구조이다.
우리 프로젝트 DB의 지표 분석 해보기
최대 허용하는 커넥션 수
SHOW VARIABLES LIKE 'max_connections';

내가 담당하고 있는 서비스의 DB는 최대 60개의 커넥션을 허용한다.
동시에 최대 접속(커넥션)했던 수
SHOW STATUS LIKE 'Max_used_connections';

DB 입장에서 열렸던 모든 커넥션의 최대치를 의미한다.
애플리케이션 커넥션 풀 연결 20개 + DB 내부 스레드 + 개발/모니터링 연결이 모두 포함된 숫자이다.
이는 최대 부하 시점에도 약 17개의 여유 연결 공간이 존재했음을 의미한다.
현재까지 사용된 최대 커넥션 수가 설정된 최대 허용치보다 작기 때문에, 아직 우리의 서비스 DB는 아직 접속자 포화 상태에 도달한 적이 없다.
현재 얼마나 연결되어 있는지 확인
SHOW STATUS LIKE 'Threads_connected';

해당 개수는 현재 DB 서버 기준으로 실제 연결된 총 커넥션 수이다.
이때 우리는 메인 컨테이너와 스케줄링 전용 컨테이너가 해당 DB를 공유하고 있기 때문에 총 20(10 + 10)개 이용
- 메인 컨테이너: maximum-pool-size: 10
- 스케줄링 컨테이너: maximum-pool-size: 10
threads_connected에서 보이는 22개 중 20개는 우리 서비스의 기본 커넥션 풀 개수이다.
이번에 커넥션 풀에 대해 공부를 진행해보면서, 커넥션 풀에 대한 정확한 지식 없이 아무렇게나 개발을 진행해왔음을 스스로 깨닫게 되었다.
만약 우리가 불가피하게 스케줄링 컨테이너와 같이 우리의 서비스 DB를 공유하는 컨테이너를 생각없이 늘렸다면?
커넥션 풀을 초과하여 Too many connections 오류와 같은 운영에 치명적인 오류들이 발생하고, 서비스가 마비되었을 것이다.
이제는 커넥션에 대한 경각심을 가지며 개발에 임할 것 같다.
현재 우리가 기본으로 사용하고 있는 각 컨테이너당 10개 수준의 커넥션 풀 크기는 이 정도만으로도 대부분의 요청을 대기 시간 없이 즉시 처리할 수 있다고 판단하여 문제가 없다고 판단했다. 실제로 앞서 보았던 모든 커넥션의 최대치에서도 충분한 여유 공간을 가지고 있음이 확인되었다.
하지만 추후 부하 테스트나 모니터링을 통해 Idle/Wait 시간이 길어지는 현상이 감지될 경우, 15개까지 점진적으로 증가하는 방식으로 진행해야겠다!