본문 바로가기

database

HikariCP - maximum pool size와 DB max_connections의 관계

Maximum pool size는 어떻게 조정해야할까?

HikariCP maximum pool size 설정 시 어떤 것을 고려해야 하나 찾아보던 차에 잘 정리된 블로그 글을 발견했다. connection pooling을 해주는 HikariCP가 사용자의 요청(thread)에 대해 어떻게 connection을 관리하는지 알 수 있었고, 할당된 thread 수에 따라 maximum pool size를 어떻게 조정하면 좋은지도 알 수 있었다.

위 글에서 설명된 maximum pool size 설정값은 HikariCP 공식 github의 wiki에 잘 정리되어 있다. 대강 정리하면 다음과 같다.

- connection pool 사이즈를 CPU 코어에 맞게 조정하면 성능 향상을 볼 수 있다.
  (다른 변경 사항 없이 connection pool size만 줄였더니 100ms에서 2ms로 성능이 50배이상 향상되었다. 병렬 처리가 time-slicing의 마법이라는 computer science 101을 떠올려보라고 한다)
- CPU 코어 * 2 보다 조금 많은 connection pool로도 제한된 자원 내에 최대의 성능을 낼 수 있다.
- Pool-locking 가능성이 있기 때문에 어느정도의 공식을 따른다.
   connection pool size = 최대 스레드 수 x (단일 스레드가 보유한 최대 동시 연결 수 - 1) + 1

 

DB 서버의 입장에서 보자면 application에서 관리하는 connection pool의 'connection'과 연결되어 요청을 받아 응답을 해주는 구조다. 여기서 application의 connection pool이 DB 서버의 max_connections와 어떤 관계를 맺을까 하는 궁금증이 생겼다. 고민하다보니 DB서버가 이런 질문을 던지고 있는 것 같았다. "나는 이만큼(max_connections) 연결을 맺어서 처리할 능력이 있는데, 너희(connection pool의 connection)는 얼마나 요청을 할거야?"

수치를 직접 눈으로 확인해보고 싶어서 다음과 같이 테스트를 진행했다. 로컬에서 DB는 MySQL로, client app은 springboot 기반으로 간단한 controller를 구현해 5초 간 sleep 하는 쿼리를 요청하도록 했다. tomcat의 default thread 수 200개는 그대로 두고, connection pooling을 담당하는 HikariCP의 maximum-pool-size와 DB의 max_connections 값을 설정해가며 테스트하였다. thread 수와 connection pool size의 관계는 위 글들에서 충분히 설명되기도 하였고, connection pool size와 DB connection size의 관계를 중점적으로 보기 위함이다.

public class MyController {

    @Autowired
    private final DataSource dataSource;

    @GetMapping("/sleep")
    @Transactional
    public void sleep5secs() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        String sql = "SELECT sleep(5)";
        int res = jdbcTemplate1.queryForObject(sql, Integer.class);
        log.info("{}, transaction complete", Thread.currentThread());
    }
}

 

설정은 다음과 같이 n값을 조정하며 진행하였다.

# application.yml
spring:
  datasource:
    url: {my-db-url}
    username: {my-username}
    password: {my-password}
    hikari:
      maximum-pool-size: n


# mysql sql
SET GLOBAL max_connections = n;

 

APP connection pool size <= DB connection size

HikariCP의 default maximum-pool-size는 10, MySQL의 default max_connections는 151이다(참고로 MySQL은 client의 요청을 처리하기 위한 connection을 항상 별도로 1개 구성해두는데, 실제 app과 connection을 맺고 질의를 요청-응답 받는 connection은 n-1개이다)

default 설정을 건드리지 않고 jmeter를 이용해 connection pool 보다는 조금 큰 20개의 request를 보내고, VisualVM을 통해 tomcat의 Thread와, DB 서버 process를 모니터링하였다. 결과는 아래와 같이 connection pool 10개를 사용해 10건의 request를 5초 간 처리하고, 이후 나머지 10건의 request를 정상 처리하였다.

 

APP connection pool size > DB connection size

HikariCP의 maximum-pool-size를 10으로 두고, MySQL의 default max_connections를 6으로 낮췄다. 동일하게 20개의 request를 보내고 모니터링하였다.

결과는 아래와 같이 20개의 커넥션을 모두 처리하는 데 20초가 걸렸다. app의 connection pool이 10개더라도, db 서버의 connection size가 6이므로 한 번에 5개의 요청만 처리가 가능했다. 이 때 app에서 20개의 요청이 모두 timeout 되지 않고 처리될 수 있었던 것은, HikariCP의 default conneciton timeout이 30초이기 때문이다.

 

HikariCP connection timeout

이외에도 HikariCP의 connection timeout 설정을 4초로 줄이고 테스트를 해보았다. 20개의 요청 중 connection pool의 connection을 4초 안에 획득할 수 있는 최대 5개의 요청만이 처리될 것을 예상하였고, 예상대로 15개의 요청은 다음과 같은 에러를 발생시키며 timeout 되었다. tomcat thread에 할당된 요청이 HikariCP의 connection pool에서 connection을 획득하지 못하고 에러를 반환받은 것이다.

spring:
  datasource:
    hikari:
      connection-timeout: 5000
java.sql.SQLNonTransientConnectionException: 
Data source rejected establishment of connection,  
message from server: "Too many connections"

 

또한 HikariCP 공식 wiki에서 소개하고 있는 공식에 따라 connection pool size를 조정하여 테스트도 해보았다. 이론상 하나의 스레드에서 요청하는 connection 수가 1개이기 때문에, connection pool size도 1개면 충분한 것으로 예상하였다. 실제로 그렇지는 않았는데, 이는 5초간 sleep하는 slow query의 영향이었다. connection을 pool에서 하나 할당 받아 db에 요청하고 응답을 받는 시간이 약 5초가 걸리기 때문에, connection timeout 30초 뒤에는 모든 요청이 실패하였다.

하지만 빠르게 실행되는 단순 쿼리를 요청했을 때는 모든 요청이 성공하였다. 'select 1'을 요청하는 20개의 요청 정도는 10ms 이내에서 처리되었다. query의 성능이 충분히 빠르다면 제한된 connection pool size 내에서도 모든 쿼리를 정상적으로 처리할 수 있는 것이다.

 

마치며

application은 scale out 하기 상대적으로 용이하지만, DB 서버는 상대적으로 그렇지 못하다. 따라서 DB 서버의 리소스에 부하를 주는 application 요청은 제한되어야 한다. connection pool size를 충분히 늘려서 application의 throughput을 최대한 늘리고 싶겠지만, DB 서버의 자원과 처리량을 고려하면 connection pool size를 pool-locking을 고려하여 최소 필요 수 이상만큼만 적절히 적게 조절할 필요가 있다.

 

[참고]

HikariCP Maximum Pool Size 설정 시, 고려해야할 부분 - Carry's 기술블로그

https://github.com/brettwooldridge/HikariCP