Tech
PoolCleaner를 활용한 Connection Pool 최적화
2021. 03. 11
안녕하세요. 화해팀 백엔드 개발자 장영석입니다. Connection Pool 최적화
Tomcat은 효율적인 Connection Pool 관리를 위해 Commons DBCP 보다 개선된 Tomcat DBCP를 사용합니다. Idle Connection 수를 조정하고 Active 상태가 오래 지속 중인 Connection을 정리할 수 있습니다. 또한 validationInterval 기능을 통해 Idle Connection이 DBMS의 wait timeout을 넘겨 재사용되지 못하는 상황을 막기 위해 wait_timeout 초기화를 위한 validation query를 주기적으로 발생시키기도 합니다.
이런 기능들을 수행하는 데 중요한 역할을 하는 클래스가 이번에 소개할 PoolCleaner입니다. 앞에서는 Tomcat의 Resource 설정을 통해 PoolCleaner의 기능을 제어하는 방법을 정리하고, 후반부에는 실제 서비스를 운영하면서 경험했던 예외 상황과 해결 방법을 공유해 보겠습니다.
💡 본론으로 들어가기에 앞서, 이 글은 Tomcat 8.5 버전을 기준으로 작성되었습니다.
Tomcat이 제공하는 몇 가지 Resource 설정은 PoolCleaner의 기능을 제어할 수 있습니다. 실행 시간이 오래 걸리는 Connection을 정리하고 Idle Connection 수를 관리하며 DBMS wait timeout을 초기화하고 체크하기 위한 validation query를 수행합니다.
removeAbandoned 설정이 true라면 PoolCleaner는 오래 실행 중인 Connection을 체크하고 정리합니다. default 설정은 false입니다. 실행 시간이 removeAbandonedTimeout 설정보다 길면 정리 대상으로 판단합니다.
이 때, 정리 대상인 connection에서 쿼리가 수행 중일 수 있습니다. 이런 경우 의도치 않게 진행 중인 쿼리가 종료될 수 있으므로 가능하다면 removeAbandonedTimeout 설정 값은 수행시간보다 큰 값을 사용도록 합니다.
Connection Pool은 최초 InitialSize 만큼 생성되고 이후 Idle 커넥션을 minIdle 보다 작게 유지되도록 조정합니다.
testWhileIdle이 true라면 validationQuery에 설정된 쿼리를 수행하여 Connection의 이상 유무를 판단합니다. 이를 통해 문제가 되는 Connection을 제거하거나 DBMS 상의 Sleep Time을 초기화시켜 wait_timeout에 의해 해당 Connection이 종료되는 것을 방지합니다.
timeBetweenEvictionRunsMillis 설정으로 아래 PoolCleaner 스케쥴의 Period가 결정됩니다. 또한 Idle Connection 체크를 위한 Query는 validationQuery 설정이 사용됩니다.
PoolCleanTimer는 싱글 스레드입니다. 예외 상황으로 인해 PoolCleanTimer가 중지된다면 여러 문제가 발생합니다. 특히 PoolCleanTimer는 한 번 중지되면 재시작할 수 없습니다. 중지된 상황에서 webapp이 reload되면 Timer already cancelled 에러 문구와 함께 IllegalStateException이 발생합니다.
이후 Timer 스케쥴이 동작하지 않게 되고 DBMS의 wait_timeout을 넘어선 Idle Connection은 사용할 수 없게 됩니다.
간혹 JDBC Driver library를 Tomcat webapp의 WEB-INF/lib/에서 관리하는 때도 있습니다. 이 경우 최초 동작 시 문제는 없습니다. 다만 webapp reload 시 새로운 ConnectionPool을 생성하게 되면서 문제가 발생합니다. 이미 생성되었던 ConnectionPool이 close 처리가 되지 않을 경우 종료된 webapp의 ClassLoader를 사용하게 되고 아래와 같은 예외가 발생합니다.
이는 라이브러리 위치에 따른 Tomcat의 Class loading 방식 때문입니다.
tomcat/lib의 라이브러리는 CommonClassloader에 의해 Class loading 됩니다. 반면에 WEB-INF/lib의 라이브러리는 각 webapp에 독립적인 WebaappClassloader를 통해 Class loading 되기 때문입니다.
JDBC Driver Library로 인해 발생하는 PoolCleanTimer 중지 문제를 해결하기 위해서 여러 방법이 존재합니다.
tomcat/li에 JDBC Driver가 위치하게 되면 해당 라이브러리는 webapp에 독립적인 ClassLoader가 아닌 CommonClassLoader에 의해 관리됩니다. 이후 webapp이 reload 될 때에도 종료된 webapp이 사용하는 ConnectionPool의 ClassLoader가 CommonClassLoader이므로 정상적으로 close 되게 됩니다.
GlobalNamingResource를 사용하게 되면 모든 webapp이 ConnectionPool을 공유하게 됩니다. 이 경우 webapp이 reload 되더라도 ConnectionPool을 다시 생성하지 않아 위 문제가 발생하지 않습니다.
Resource에 closeMethod를 설정해주어 종료된 webapp의 ConnectionPool을 clean up 하는 방법이 있습니다.
위 설정을 사용하면 webapp reload 시 ConnectionPool을 clean up 하기 위해 closeMethod의 설정된 메소드를 사용하게 됩니다. 해당 메소드는 벤더사마다 다른 점에 유의합니다.
마치며
Tomcat의 ConnectionPool 관리를 위한 기능 중 PoolCleaner를 알아보았습니다. Datasource 설정들이 실제 동작하는 방식을 확인하는 과정에서 오픈소스인 Tomcat의 장점을 활용할 수 있었습니다. 특히 PoolCleaner가 중지된 원인은 오픈소스가 아니었다면 파악하기 어려웠을 것입니다.
이후에는 다른 오픈소스 DBCP 라이브러리들을 살펴보고 각각의 장단점을 살펴보려 합니다.
참고자료
https://tomcat.apache.org/tomcat-8.5-doc/jdbc-pool.html#Tomcat_JDBC_Enhanced_Attributes
https://tomcat.apache.org/tomcat-8.5-doc/class-loader-howto.html
https://github.com/apache/tomcat
https://d2.naver.com/helloworld/5102792
https://kakaocommerce.tistory.com/45