커넥션 객체를 미리 만들어 풀에 담아두고 빌려주는 방식. WAS가 뜰 때 일정 수를 만들어두고, 요청이 오면 빌려주고, 끝나면 돌려받아 다시 담는다.

커넥션을 새로 만드는 비용

요청이 적으면 상관없지만 요청마다 커넥션을 새로 만드는 것은 리소스 낭비가 심하다. DriverManager는 사용자가 요청할 때마다 드라이버를 로드하고 커넥션을 만들어 연결했다가 종료한다. 그 비용을 한 번만 치르고 재사용하자는 것이 커넥션 풀이다.

만들고 없애는 작업이 사라지므로 접속이 빠르고, DB 접속 설정이 한곳에 모여 서버 환경이 바뀔 때 고치기 쉽다. 커넥션 수를 제한할 수 있으니 과도한 접속으로 서버 자원이 고갈되는 것도 막는다.

빌려주고 돌려받기

1. WAS 시작 시 풀에 커넥션을 미리 생성한다 (기본 10개)
2. 요청이 오면 풀에서 하나 꺼내 쓴다
3. 다 쓰면 풀에 반납한다

반납이 중요하다. JDBC에서 커넥션을 닫지 않으면 자원이 바닥나는 것과 같은 문제인데, 풀을 쓰면 프로세스가 죽는 대신 풀이 빈다. 그러면 모두가 대기하게 된다.

HikariCP가 커넥션을 고르는 순서

HikariCP는 커넥션을 내줄 때 직전에 쓰던 커넥션이 있는지 먼저 본다. 있으면 그것을 우선 주고, 없으면 이전에 썼던 목록에서 쓸 수 있는 것을 찾고, 그래도 없으면 전체 목록을 뒤진다. 이 세 단계는 순서대로 실행되다가 찾는 즉시 멈춘다.

쓸 수 있는 커넥션이 하나도 없으면 HandOffQueue를 폴링하면서 다른 스레드가 반납하기를 기다린다. 지정한 TimeOut까지 못 받으면 예외를 던진다. 반납된 커넥션은 사용 내역이 기록된 뒤 HandOffQueue에 들어가고, 기다리던 스레드가 그것을 받아 작업을 시작한다.

구현체 계보

아파치가 제공하는 commons-dbcp가 있고, 톰캣에 내장되어 commons-dbcp를 바탕으로 만들어진 tomcat-jdbc-pool이 스프링 부트 2.0.0 이전의 기본값이었다. 2.0.0부터는 HikariCP가 기본 커넥션 풀이다. 벤치마크가 잘 나오는 이유는 오버헤드가 거의 없게 만들어졌기 때문이다. 오버헤드란 실제 처리 외에 부수적으로 드는 시간과 메모리를 말한다.

풀 크기와 대기 시간

커넥션 수가 제한되어 있으니 동시 접속자가 많으면 반납까지 기다려야 한다. 풀을 크게 잡으면 메모리를 더 쓰는 대신 대기 시간이 줄고, 작게 잡으면 그 반대다.

스레드 풀과의 관계

커넥션을 쓰는 주체가 스레드이므로 스레드 풀과 함께 봐야 한다. 지침은 Thread Pool 크기 < Connection Pool 크기다. 트랜잭션을 처리하는 스레드가 쓰고 남은 커넥션은 실질적으로 메모리 공간만 차지한다.

둘을 함께 키우면 다른 벽에 부딪힌다. 스레드가 늘어난 만큼 컨텍스트 스위칭이 잦아져 오버헤드가 커지고, DB는 하드디스크 하나당 하나의 I/O를 처리하므로 디스크 경합에서 블로킹이 생긴다. 특정 시점부터는 디스크 병목 때문에 성능이 더 오르지 않는다.

오래 쥐고 있는 코드

커넥션을 오래 쥐고 있는 코드가 있으면 풀이 커도 소용없다. OSIV가 그 예다.

관련

출처