이전에 적절한 톰캣 쓰레드 개수를 설정하는데 실패하여 Default 설정을 그대로 따르도록 하였었다. 당시에 병목이 DB 쪽에서 발생하는 것 아닐까? 그래서 제대로된 톰캣 쓰레드 개수 설정이 불가능했던 것 아닐까? 하는 의문이 들었고, 따라서 HikariCP 설정을 통해서 DB 쪽 최적화를 먼저 진행한 이후에 톰캣 쓰레드를 다시 한 번 설정해보았고, 그 과정에서 톰캣 쓰레드와 커넥션 풀 간의 관계에 대해서 고민해보았다.
HikariCp는 밀리세컨트를 모든 time value에 사용한다.
이 속성은 클라이언트(사용자)가 풀에서 연결을 대기하는 최대 시간(밀리초)을 제어한다.
connection을 사용할 수 없는 상태에서 이 시간을 초과하면 SQL 예외가 발생한다.
허용 가능한 최소 연결 시간 초과는 250ms이다. (기본값: 30000, 30초)
이 속성은 풀에서 connection이 유휴한 상태로 허용되는 최대 시간을 제어한다.
이 설정은 최소값일 때만 적용되며, maxmumPoolsize 보다 작게 정의된다.
(This setting only applies when minimumIdle is defined to be less than maximumPoolSize)
일단 풀이 최소 유휴 커넥션에 이르면(도달하면) 유휴 커넥션이 retired 되지 않습니다.(폐기되지 않습니다.)
connection이 유휴 상태로 사용 중지되는지 여부는 최대 30초, 평균 15초의 변동에 따라 결정된다. 이 시간 초과 전에는 connection이 유휴 상태로 회수 되지 않는다. (즉 이 정도는 시간이 지난 다음에야 유휴 상태로 판단하고 회수되는 듯)
값이 0이면 유휴 connection이 풀에서 제거되지 않는다. 허용 되는 최소값은 10000ms (10초)이다.
(기본값 : 600000, 10분)
이 속성은 HikariCP가 데이터베이스 또는 네트워크 인프라에 의해 시간 초과되는 것을 방지하기 위해 연결을 계속 유지하려고 시도하는 빈도를 제어한다.
이 값은 maxLifetime 값보다 작아야 한다.
“keepalive” 는 유휴 연결에서만 발생한다. → 유휴 커넥션에 대해서만 적용된다는 느낌인듯
지정된 연결에 대해 “keepalive” 시간이 되면 해당 연결이 풀에서 제거되고 ping된 다음 풀로 돌아간다. ‘ping’은 JDBC4 의 isValid() 메소드를 호출하거나 connectionTestQuery를 실행한다.
일반적으로 풀 외 시간(out-of-the-pool)은 한 자릿수의 밀리초 또는 밀리초 미만으로 측정해야 하므로 성능에 미치는 영향은 거의 없다.
허용되는 최소값은 30,000ms(30초)이지만 분 범위의 값이 가장 좋다.
(기본값: 0 (사용안 함))
이 속성은 풀에서 연결(connection)의 최대 수명을 제어한다. 사용 중인 connection은 사용 중지되지 않으며, 닫힐 때만 제거된다.
connection별 기준으로 풀에서 대량 소멸을 방지하기 위해 사소한 음의 감쇠가 적용된다.
이 값을 세팅하는 것을 강력하게 추천하며 이 값은 데이터베이스 또는 인프라 커넥션 타임 아웃보다 몇초정도 더 짧아야 한다. 값이 0이면 idleTimeout 설정에 따라 최대 수명(무한 수명) 이 없음을 나타낸다.
허용되는 최소값은 30,000ms(30초 이다.)
(기본값: 1800000(30분))
이 속성은 풀에서 유지 관리하려는 최소 유휴 connection 수를 제어한다. 유휴 connection이 이 값 아래로 떨어지고 풀의 총 connection이 최대 풀 크기보다 작으면 HikariCP는 빠르고 효율적으로 추가 connection을 추가하기 위해 최선을 다한다.
그러나 스파이크 요구(spike demand)에 대한 최대 성능과 응답성을 위해 이 값을 설정하지 않고 대신 HikariCP가 고정 크기 커넥션 풀 역할을 하도록 허용하는 것이 좋다.
(기본값: maximumPoolSize와 동일)
이 속성은 유휴 커넥션과 사용중인 커넥션을 모두 포함하여 풀에서 허용하는 최대 크기를 제어한다.
기본적으로 이 값은 DB 백엔드에 대한 실제 최대 연결 수를 결정한다.
적절한 값은 실행 환경에 따라 가장 잘 결정된다.
풀이 이 크기에 도달하고 사용 가능한 유휴한 커넥션이 없으면 getConnection() 호출이 블락된다. (for up to connectionTimeout milliseconds before timing out)
(기본값: 10)
여기서 우리는
maximumPoolSize
와connectionTimeout
에 대한 설정을 진행해보았다.
평균 TPS : 300
Throughput : 286.3 / sec
평균 TPS : 400 ~ 450 사이
Throughput : 386.4 / sec
Throughput : 421.55 / sec (최저는 395.0, 최대는 443.2)
Throughput : 409.9 / sec
Throughput : 415.16 / sec (최저는 394.2, 최대는 438.8)
Throughput : 274.2 / sec
평균 TPS가 훨씬 떨어진 것을 확인할 수 있다.
우리는 결론적으로 HikariCP 커넥션 풀의 개수를 5개로 설정하였다.
커넥션 풀에 대한 개수를 답을 구했다. 우리의 경우 t4g.micro 인스턴스에 Mysql을 설치해 사용하고 있는데 vCPU 가 2개이고, 디스크를 하나 사용하므로 5개가 적정이다. HikariCP Wiki 를 참고해보았을 때에도
connections = ((core_count * 2) + effective_spindle_count)
라는 공식을 발견할 수 있는데, "곱하기 2"를 해주는 이유는 네트워크 및 디스크로 인한 latency 동안 CPU를 최대한으로 활용해주기 위함이고, effective_spindle_count 는 하드디스크 갯수를 의미한다.
이후 HikariPool을 5개로 고정하고, connectionTimeout 을 조정해보았다. 해당 옵션은 클라이언트가 풀에서 연결을 대기하는 최대 시간(밀리초)을 제어하며 허용 가능한 최소 연결 시간은 250ms 이다.
Throughput : 314.7 / sec
Throughput : 392.1 / sec
Throughput : 287.3 / sec
Throughput : 306.4 / sec
Throughput : 314.6 / sec
Error% : 0.16%
큰 의미를 얻지 못했고, 오히려 짧은 1초일 때는 에러도 보였기에 기본설정인 30초를 따르도록 하였다.
결론적으로 tomcat thread 5개, HikcariCP 커넥션 풀을 5개로 하였을 때 가장 높은 응답속도를 보여주었다. 그리고 여기서 왜 이렇게 5개로 같을 때, 가장 높은 성능을 보이는 지에 대해서 고민해보았다. 우리는 OSIV 를 켜두고 있었고, 따라서 사용자 요청이 들어오고 응답이 나가는 주기와 커넥션을 잡고 있는 주기가 비슷하여 같은 값에서 최고의 Throughput을 낸다는 추측을 해볼 수 있었다. 따라서 OSIV 를 끈 후 테스트를 한 번 더 진행해보았다.
위와 같은 과정을 거쳐서 기본 설정에서 Tomcat Thread & HikariCP 설정을 통해서 486.2/sec -> 528.6/sec 로 개선을 이뤄낼 수 있었고, 추가적으로 OSIV 설정과 톰캣 쓰레드 설정을 통해서 Throughput 을 16.3/sec 추가적으로 향상시킬 수 있었다.
아주 멋집니다!!