다른글에서 JDBC가 무엇인지 살펴 보았지만 해당 글에서도

JDBC, DB 커넥션풀이 무엇인지 왜 필요한지 알아보고

Hikari CP 에 대해서 알아보고자 한다

 

1. JDBC 란?

Java DataBase Connectivity 의 약자로

자바에서 데이터베이스에 연결할 수 있도록

도와주는 자바 API 라고 보면 된다

 

JDBC는 DB 회사에 맞춰서 드라이버만 설치하면

해당 DB에 연결될 수 있게 인터페이스를 제공한 것이다

 

2. DB 커넥션 풀 이란?

 

커넥션 풀은 데이터베이스와 연결된 커넥션을 미리 만들어 놓고

이를 pool로 관리하는 것이다

즉 필요할 때마다 커넥션 풀의 커넥션을 이용하고 반환하는 기법이다

 

연결때 마다 커넥션을 만드는것이 아닌

미리 만들어 놓고 커넥션이 필요할때 만들어둔걸로

DB에 빠르게 접속할 수 있으니 비용을 줄일 수 있다

 

커넥션 풀을 이용하면 커넥션 수를 제한할 수 있고

과도한 접속으로 인한 서버 자원 고갈을 방지할 수 있다

또한 DB 접속 모듈을 공통화 해서 DB 서버의 환경이 바뀌어도

유지보수를 쉽게할 수 있는 장점이 있다

 

3. HikariCP 란?

자바에서는 기본적으로 DataSource 인터페이스를 이용해

커넥션 풀을 관리하게 된다

 

Spring에서는 사용자가 직접 커넥션을 관리할 필요없이

자동화된 기법들을 제공하는데

 

SpringBoot 2.0 이전에는 tomcat-jdbc 를 사용했고

현재 2.0 이후 부터는 HikariCP를 기본옵션으로 채택 하고 있다

 

 

HikariCP는 가벼운 용량과 빠른 속도를 가지는

JDBC의 커넥션 풀 프레임워크이다

 

다른 커넥션 풀 관리 프레임워크보다 빠른 성능을 보이는 이유는

커넥션풀의 관리 방법에 있다

 

 

Hikari는 Connection 객체를 한번 Wrapping 한 PoolEntry로 Connection을 관리하고

이를 관리하는 ConcurrentBag 이라는 구조체를 사용하고 있다

 

ConcurrentBag은 HikariPool.getConnection() -> ConcurrentBag.borrow()라는

메서드를 통해 사용 가능한(idle) Connection을 리턴하도록 되어있다

 

이 과정에서 커넥션생성을 요청한 스레드의 정보를 저장해두고

다음에 접근시 저장된 정보를 이용해 빠르게 반환을 해준다

 

 

Thread가 커넥션을 요청했을 때 유휴 커넥션이 존재한다면 해당 커넥션을 반환해준다

하지만 유휴 커넥션이 존재하지 않는다면

HandOffQueue를 Polling 하면서 다른 Thread가 커넥션을 반납하기를 기다린다

다른 Thread가 커넥션 풀에 커넥션을 반납하면

커넥션 풀은 HandOffQueue에 반납된 커넥션을 삽입하고

HandOffQueue를 Polling 하던 Thread는 커넥션을 획득하게 된다

 

아래에 우아한 기술블로그를 참고하면 더 이해가 잘 될 것이다

https://techblog.woowahan.com/2664/

 

HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그

{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할

techblog.woowahan.com

 

4. HikariCP 사용법

스프링부트에 spring-boot-starter-jdbc 에 기본적으로 들어있다

이후 hikari 설절을 해주면 된다

 

1) application.yml 통해 설정하기

spring:
 datasource:
   url: jdbc:mysql://localhost:3306/world?serverTimeZone=UTC&CharacterEncoding=UTF-8
   username: root
   password: your_password
   hikari:
     maximum-pool-size: 10
     connection-timeout: 5000
     connection-init-sql: SELECT 1
     validation-timeout: 2000
     minimum-idle: 10
     idle-timeout: 600000
     max-lifetime: 1800000

server:
 port: 8000
 
// maximum-pool-size: 최대 pool size (defailt 10)
// connection-timeout: (말 그대로)
// connection-init-sql: SELECT 1
// validation-timeout: 2000
// minimum-idle: 연결 풀에서 HikariCP가 유지 관리하는 최소 유휴 연결 수
// idle-timeout: 연결을위한 최대 유휴 시간
// max-lifetime: 닫힌 후 pool 에있는 connection의 최대 수명 (ms)입니다.
// auto-commit: auto commit 여부 (default true)

 

2) 클래스에서 HikariConfig 통해 설정하기

public static HikariDataSource createDataSource(String url, String id, String pwd) throws IllegalArgumentException {

   // RDB 종류 확인
   Pattern pattern = Pattern.compile("^jdbc:(postgresql|sqlserver|oracle|mysql|mariadb):.*$");
   Matcher matcher = pattern.matcher(url);

   String rdbKind = null;
   if (matcher.matches()) {
       rdbKind = matcher.group(1);
   }

   // RDB 종류에 따른 JDBC 드라이버 클래스명 선택
   RdbDriverEnum rdbDriverEnum = RdbDriverEnum.findByRdbKind(rdbKind);
   String rdbDriver = rdbDriverEnum.getDriverClass();

   // 빈 값인지 확인
   if(StringUtil.isEmpty(rdbDriver) == true) {
      throw new IllegalArgumentException("dbDriver cannot be null or empty");
   }

   // 설정값 구성
   HikariConfig config = new HikariConfig();
   config.setJdbcUrl(url);
   config.setUsername(id);
   config.setPassword(pwd);
   config.setDriverClassName(rdbDriver);

   // 커넥션풀 설정
   config.setMaximumPoolSize(5);            // 최대 pool size
   config.setMinimumIdle(2);                // idle 상태에서 유지할 최소 커넥션 수, 이하로 떨어지면 maximumPoolSize 만큼 커넥션 생성 가능
   config.setConnectionTimeout(30000);      // 풀에서 커넥션을 가져올 때 소요되는 최대 대기시간 (30초, ms)
   config.setIdleTimeout(600000);           // 풀에 존재하는 커넥션 중에서 사용되지 않는 커넥션 객체를 커넥션 풀에서 제거하는 시간 (10분)
   config.setMaxLifetime(1800000);          // 풀에서 관리되는 커넥션의 최대 생명주기, 이 값을 초과하면 해당 커넥션은 커넥션풀에서 제거됨 (30분)
   config.setAutoCommit(false);             // (default: false)
   config.setLeakDetectionThreshold(60000); // HikariCP가 커넥션 누수를 감지하기 위한 임계값 설정 (이 임계값을 초과하면 누수로 간주, 0ms는 비활성화)

   return new HikariDataSource(config);
}

 

5. DeadLock 피하기

이론적으로 필요한 최소한의 커넥션 풀 사이즈를 알아보면 다음과 같다

PoolSize = Tn × ( Cm -1 ) + 1

Tn : 전체 Thread 갯수
Cm : 하나의 Task에서 동시에 필요한 Connection 수

 

위와 같은 식으로 설정을 한다면 데드락을 피할 수는 있겠지만

여유 커넥션풀이 하나 뿐이라 성능상 좋지 못하다
따라서 커넥션풀의 여유를 주기위해 아래와 같은 식을 사용하는것을 권장한다

PoolSize = Tn × ( Cm - 1 ) + ( Tn / 2 )

thread count : 16
simultaneous connection count : 2
pool size : 16 * ( 2 – 1 ) + (16 / 2) = 24

 

추가적으로 더 자세한 사용법을 알고싶다면

아래 링크를 통해 알아보면 좋을것 같다

https://techblog.woowahan.com/2663/

 

HikariCP Dead lock에서 벗어나기 (실전편) | 우아한형제들 기술블로그

{{item.name}} 1부 HikariCP Dead lock에서 벗어나기 (이론편)은 잘 보셨나요? 2부 HikariCP Dead lock에서 벗어나기 (실전편)에서는 실제 장애 사례를 기반으로 장애 원인을 설명하고 해결 사례를 공유하고자

techblog.woowahan.com