[Java] Hikari CP에 대해 알아보자. (1)
17 Dec 2022최근 회사에서 HikariCP로 인해 커넥션 누수 현상이 발생하고 있어 해당 원일을 찾고 수정 중에 있다. 먼저 원인 분석 및 해결을 위해서는 HikariCP에 대해 알고있어야 할 것 같아. HikariCP에 대해 공부해보고 간략하게 정리해보려고 한다.
HikariCP
HikariCP
공식 Github에서는 HikariCP를 이렇게 소개하고 있다.
Fast, simple, reliable. HikariCP is a “zero-overhead” production ready JDBC connection pool. At roughly 130Kb, the library is very light.
간단하게 번역해 보자면 빠르고 신뢰할 수 있으며, “오버 헤드가 없는” 가벼운 JDBC Connection Pool 라이브러리 로 볼 수 있다.
현재 HikariCP는 이 성능을 인정받아 Spring boot 2.0 버전에서 Tomcat DBCP => HikariCP로 전환이 되었다.
Connection Pool
HikariCP에 정리하기 전에 HikariCP가 관리하는 Connection Pool에 관하여 간략하게 설명 해보고자 한다.
Connection이란 요청이 들어왔을 때 애플리케이션과 데이터베이스간에 연결하여 쿼리를 실행해 데이터를 가져오고, 결과를 가져올시 종료하는 과정을 말한다.
Connection 과정에서 연결 및 종료하는데 자원이 많이 소모되므로, DB에서는 일정 수 Connection을 미리 만들어 제공해 주는 기능이 있다. 이렇게 미리 만들어 놓은 Connection의 자원을 관리하는 서비스를 Connection Pool이라고 한다.
간단하게 도식화하자면 아래와 같다.
- Client에서 WAS로 Request 요청을 한다.
- Apllication에서는 WAS 내부에서 실행시 미리 보유해놓은 Connection Pool의 인스턴스중 유휴 상태의 Connection이 있는지 조회한다.
- 만약 유휴 Connection 이 없다면, 유휴 상태의 Connection이 생길 때 까지 대기한다.
- 유휴 상태의 Connection이 있다면, 해당 Connection을 획득한다.
- 획득한 Connection을 이용하여 DB에서 쿼리를 조회 한다.
- DB에서 쿼리를 통해 조회한 결과를 반환한다.
- 사용한 Connection은 다시 재사용을 해야하기 때문에 종료하지 않고, Connection Pool에 반환한다.
- Response로 응답을 내보낸다.
HikariCP의 장점?
HikariCP의 벤치마크에 대한 정보가 담긴 Github Repository이다.
해당 통계를 보면 HikariCP의 성능이 우월하다는 것을 확인 할 수 있다.
또한 HikariCP의 장점을 리스트업 해보자면 아래와 같다.
- 교착(Deadlock)상태가 없도록 설계가 되었다.
- 자체적으로 Connection leak (커넥션 누수)를 감지 할 수있다.
- 모든 구성 속성에 적합한 기본값을 제공
- 초경량의 라이브러리 (130kb)
- 훌륭한 벤치마크 성능
- 빠른 버그 Fix
HikariCP 에서 Connection Pool
그렇다면 HikariCP에서는 어떻게 Connection Pool을 관리할까?
HikariCP는 아래의 Url에서 확인 할 수있듯이, ConcurrentBag 구조체를 이용하여 풀을 관리하고 있으며, 아래의 링크에서 자세한 내용은 확인 할 수 있다.
ConcurrentBag 구조체는 C#, .NET ConcurrentBag 클래스에서 차용하고 내부구현을 달리해 HikariCP에 적용했다고 한다.
특징으로는
- 교착상태가 없는 디자인 (A lock-free design)
- ThreadLocal 캐싱 (ThreadLocal caching)
- 큐 훔치기 (Queue-stealing)
- 직접적인 hand-off 최적화 (Direct hand-off optimizations)
가 있다고 한다..만 어려운말 들이라 참고만 하도록 하겠다.
Connection 획득
HikariCP에서 Connection Pool을 가져오는 부분은 HikariPool.java 의 getConnection()에서 확인할 수 있다.
아래의 소스는 Connection을 가져오는 전문 소스이다.
HikariPool.java_getConnection()
이중에서 눈여겨볼 부분은 아래 소스 중 PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
해당 부분을 살펴보면 된다.
PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
if (poolEntry == null) {
break; // We timed out... break and throw exception
}
connectionBag.borrow(timeout, MILLISECONDS)
부분의 메소드에서 유휴 상태의 커넥션을 가져온다.
해당 메소드에서 Connection을 가져오는 방법을 확인해 볼 수 있다.
간략하게 로직을 설명해 보자면,
- 현재 실행중인 Thread에서 사용한 이력이 있는 Connection 정보가 있는지 조회하고, 해당 정보가 있을 경우 유휴 상태(STATE_NOT_IN_USE)의 Connection을 가져와 return 한다.
- 1.에서 Connetion을 획득하지 못했을 경우 전체 Connection Pool에서 유휴 상태(STATE_NOT_IN_USE)의 Connection이 있는지 조회하고 Connection을 return한다.
- 2.에서도 Connection을 획득하지 못했을 경우 handoffQueue에서 기존 HikariCP의 설정중
connection-timeout
의 정보(기본값 30000ms)를 가져와 해당 설정의 시간만큼 대기한다. - 최종적으로 3.에서도 획득하지 못할경우 null을 반환하고,
SQLException
를 발생시킨다.
Connection 반납
Connection 반환 방법 중 HikariCP의 로직을 설명해보도록 하자.
Connection을 가진 Application이 DB와의 연결이 끝난경우 ProxyConnection.java close() => PoolEntry.java의 recycle() => HikariPool.java의 recycle()의 순서로 HikariCP의 Connection 반환 로직을 실행한다.
/**
* Recycle PoolEntry (add back to the pool)
*
* @param poolEntry the PoolEntry to recycle
*/
@Override
void recycle(final PoolEntry poolEntry)
{
metricsTracker.recordConnectionUsage(poolEntry);
connectionBag.requite(poolEntry);
}
Connection을 획득할 때와 마찬가지로 connectionBag 객체를 호출하지만 이번엔 connectionBag.requite(poolEntry);
을 호출하여 Connection을 반환한다.
해당 메소드를 통해 Connection을 반환하는 로직을 확인 할 수 있다.
해당 로직도 간략하게 확인 해보도록 하자.
- 반환한 Connection을 유휴 상태(STATE_NOT_IN_USE)로 만든다
- 만약 handoffQueue에서 대기중인 Thread가 있다면, 해당 Connection을 제공한다.
- Thread의 사용 이력에 추가한다.
이렇게 확인 해볼 수 있다.
정리
이를 통해 HikariCP는 기존에 사용한 적 있는 Connection을 우선적으로 넘기고, 빠르게 재활용 및 다른 대기중인 Thread에게 Connection을 전달 할 수 있도록 구성되었다는 것을 알 수 있다.
다음번에는 HikariCP의 설정 및 설정값에 대해 알아보도록 하자.