들어가기 전에
하기 내용은 구글링과 기존에 알고 있던 부분을 토대로 tomcat JDBC connection pool 속성에 대해 정리한 글입니다.
Tomcat JDBC Connection Pool 설정
<Resource name="jdbc/testDatasource"
auth="Container"
driverClassName="oracle.jdbc.OracleDriver"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
initialSize="20"
maxActive="20"
maxIdle="20"
minIdle="20"
testWhileIdle="true"
testOnReturn="false"
timeBetweenEvictionRunsMillis="5000"
minEvictableIdleTimeMillis="30000"
validationQuery="SELECT 1 FROM DUAL"
validationInterval="30000"
testOnBorrow="true"
type="javax.sql.DataSource"
removeAbandoned="false"
removeAbandonedTimeout="60"
logAbandoned="false"
url="jdbc:oracle:thin:@192.168.10.11:1532:testDB"
username="oracle"
password="oracle"/>
아래 표는 tomcat7 기준 tomcat JDBC Connection pool 속성에 대한 설명입니다. 버전에 따라 default 값이 상이할 수 있습니다.
속성 이름 | 설명 |
---|---|
factory | tomcat JDBC를 사용할 경우, org.apache.tomcat.jdbc.pool.DataSourceFactory로 설정(내부적으로 상속 tomcat JDBC를 상속 받은 custom을 설정할 수도 있음) |
initialSize | BasicDataSource 클래스 생성 후 최초로 getConnection() 메소드를 호출할 때 connection pool에 채워 넣을 connection 수 |
maxActive | 동시에 사용할 수 있는 최대 connection 개수 |
maxIdle | connection pool에 반납할 때 최대로 유지될 수 있는 connection 개수 |
minIdle | 최소한으로 유지할 connection 개수 |
maxWait | connection pool 내의 connection이 고갈되었을 때 connection 반납을 대기하는 시간(ms) (tomcat DBCP 사용 시, default 30000ms(30s)) |
testWhileIdle | evictor 스레드가 실행될 때 connection pool 안에 있는 유휴 상태의 connection을 대상으로 테스트 실행(default: false) |
testOnReturn | connection을 반환할 때 테스트(검증) 실행(default: false) |
timeBetweenEvictionRunsMillis | Evictor 스레드가 동작하는 간격이며, 1000ms보다 작은 값을 설정할 수 없음(default: 5000ms) |
minEvictableIdleTimeMillis | Evictor 스레드 동작 시 connection의 유휴 시간을 확인해 설정값 이상일 경우 connection 제거(default: 60000ms) |
validationQuery | 유효성 검사 쿼리로, DBMS 종류에 따라 주로 아래와 같이 사용 [ Oracle ] select 1 from dual [ Microsoft SQL Server ] select 1 [ MySQL ] select 1 |
validationInterval | connection 검증 시, 설정값 이내로 기존에 테스트한 이력이 있으면 다시 검증하지 않음(default: 3000ms) |
testOnBorrow | connection pool에서 connection을 얻어올 때 테스트(검증) 실행(default: false) |
type | javax.sql.DataSource |
removeAbandoned | abandoned connection을 제거할지 여부이며 false 권장(default: false) connection 자원이 제대로 반환되지 않는다고 Tomcat JDBC에서 제거하기보다는 실제 원인을 찾아 어플리케이션단에서 반환할 수 있도록 하는 것이 좋음(근본적 수정 필요) |
removeAbandonedTimeout | abandoned connection이 삭제되기까지 timeout값(default: 60s) |
logAbandoned | abandoned connection에 관한 trace log을 어플리케이션에 찍을지 여부(default: false) |
connection 개수 관련 속성
속성 이름 | 설명 |
---|---|
factory | org.apache.tomcat.jdbc.pool.DataSourceFactory |
initialSize | BasicDataSource 클래스 생성 후 최초로 getConnection() 메소드를 호출할 때 connection pool에 채워 넣을 connection 수 |
maxActive | 동시에 사용할 수 있는 최대 connection 개수 |
maxIdle | connection pool에 반납할 때 최대로 유지될 수 있는 connection 개수 |
minIdle | 최소한으로 유지할 connection 개수 |
maxWait | connection pool 내의 connection이 고갈되었을 때 connection 반납을 대기하는 시간(ms) (tomcat DBCP 사용 시, default 30000ms(30s)) |
connection 개수와 관련된 속성은 아래와 같은 조건을 만족시켜야 합니다.
maxActive
>=initialSize
maxActive
보다initialSize
가 크면 최초 connection 생성 시 최대 connection 개수보다 크게 생성해야 하기 때문에 논리적으로 오류가 있습니다.
maxIdle
>=minIdle
maxIdle
<minIdle
로 설정은 가능하나 논리적으로 오류가 있습니다.
maxActive
=maxIdle
maxActive
값과maxIdle
값이 같은 것이 바람직합니다.maxActive = 10
이고maxIdle = 5
인 상황일 때, 항상 connection을 5개 사용하고 있을 때 1개의 connection을 추가로 요청한다면 1개의 추가 connection을 데이터베이스와 연결한 후 pool은 비즈니스 로직으로 connection을 전달합니다. 이후 비즈니스 로직이 connection을 사용한 후 pool에 반납할 경우, maxIdle = 5의 영향을 받아 connection을 실제로 닫아버리기 때문에 일부 connection을 매번 생성 및 닫는 비용이 발생할 수 있습니다.
사실상 initialSize
, maxActive
, maxIdle
, minIdle
항목은 동일한 값으로 통일해도 무방합니다. connection 개수와 관련된 가장 중요한 성능 요소는 일반적으로 connection 최대 개수입니다. 4가지 항목의 값 차이는 성능을 좌우하는 중요 변수는 아닙니다.
maxActive
값은 DBMS의 설정과 어플리케이션 서버의 개수 및 tomcat에서 동시에 처리할 수 있는 사용자 수 등을 고려해서 설정해야 합니다. DBMS가 수용할 수 있는 connection 개수를 확인한 후에 어플리케이션 서버 인스턴스 1개가 사용하기 적절한 개수를 설정합니다.
connection을 얻기 전 대기 시간
maxWait
속성은 connection pool 안의 connection이 고갈되었을 때 connection 반납을 대기하는 시간(밀리초)입니다. 해당 속성은 평소에는 어떤 값을 설정하던 상관없지만, 사용자가 갑자기 급증하거나 DBMS에 장애가 발생했을 때 장애를 더욱 크게 확산시킬 수 있어 주의해야 합니다.
적절한 maxWait
값을 설정하려면 TPS(Transaction Per Seconds)
와 tomcat에서 처리 가능한 thread 개수 등을 이해해야 합니다.
TPS(Transaction Per Seconds)
아래와 같은 상황을 가정한다면, A 요청에 대한 최종 응답 시간은 500ms(요청 응답에 필요한 다른 컴포넌트 시간 무시)입니다.
- 요청 하나에 쿼리 10개를 실행합니다. 각 쿼리의 평균 실행 시간은 50ms입니다.
위와 같은 요청이 여러개 들어오는 시스템 전체의 TPS를 대략적으로 보면 아래와 같습니다. 요청 하나의 응답시간이 500ms이고, maxActive
가 5이기 때문에 1초동안 10개의 요청을 처리할 수 있고 성능 지수는 10TPS
라고 볼 수 있습니다.
connection의 개수와 TPS가 밀접하게 연관된 이유는, 처리해야 하는 요청 수가 증가해도 connection pool의 connection 개수가 5개이면 10TPS 이상의 성능을 낼 수 없기 때문입니다. 아래 그림과 같이 10개의 요청이 들어오면, connection pool 5개가 이미 사용중이기 때문에 5개의 요청은 connection pool에 여분 connection이 생길 때까지 대기 상태가 되어 maxWait
값만큼 기다립니다.
- 이 때, Thread Dump를 뜨면
TIMED_WAITING
걸린 thread가 존재하는 것을 확인할 수 있습니다.
이러한 대기하는 요청 수를 줄이는 가장 쉬운 방법은 maxActive
값을 높이는 것이지만, 일반적으로 DBMS 리소스를 하나의 어플리케이션에서만 사용하는 것이 아니기 때문에 무작정 connection 개수를 크게 설정할 수 없습니다. 따라서, 예상 접속자 수와 서비스의 실제 부하를 측정해 최적값을 설정하는 것이 중요합니다.
maxWait
값 조절을 통해 무한히 connection 개수를 늘리지 않고 최적의 시스템 환경을 구축하는데 도움을 줄 수 있습니다.
적절한 maxWait 값이란?
Tomcat은 thread 기반으로 동작해 사용자의 요청을 처리합니. Tomcat DBCP가 connection pool을 가지고 있는 것처럼 tomcat도 내부에 thread pool을 가지고 있어 아래와 같이 사용자의 요청이 들어올 때마다 tomcat의 thread pool에서 thread를 하나씩 꺼내 요청을 처리합니다.
위 그림과 같이 Tomcat DBCP의 connection pool을 모두 사용하고 있으면, 그 외의 요청은 maxWait
시간만큼 기다리게 되며 기다리는 주체는 tomcat의 thread입니다. tomcat이 사용자 연결을 처리하는 최대 thread 개수는 server.xml 내 maxThread
속성으로 지정합니다.
만약, maxWait
을 10,000ms로 설정한다면 DBCP connection pool을 얻지 못한 요청의 thread는 10초동안 대기하게 됩니다. 이러한 요청이 계속 증가한다면 결국 tomcat의 thread가 모두 DBCP connection을 얻기 위해 대기하게 될 수 있습니다. 이럴 때는 아래와 같은 오류를 마주하게 됩니다.
심각: All threads (80) are currently busy, waiting. Increase maxThreads (80) or check the servlet status
이 오류 이후, 대기하던 thread가 DBCP connection을 얻어 사용자 요청을 처리하고 응답을 보낸다고 할지라도 이미 사용자는 떠났을 수 있습니다. 일반적으로, 선착순 이벤트 클릭이 아닌 이상 클릭 후 얼마 기다리지 않고 사용자는 페이지 새로 고침을 진행하거나 다른 페이지로 이동합니다. 즉, 기다리는 사람도 없는 요청에 응답하기 위해 리소스를 낭비한 것이 됩니다.
- 사용자가 인내할 수 있는 시간을 넘어서는
maxWait
값은 의미가 없습니다.
그렇다고 maxWait
값을 너무 작게 설정하면 TimeOut이 너무 많이 발생하게 되어 사용자에게 오류 메시지를 자주 보이게 됩니다. 따라서 maxWait 값은 사용자의 대기 가능한 시간과 같은 어플리케이션의 특성과 다른 주변의 설정, 자원의 상황 등을 고려해 판단해야 합니다.
만약 갑작스럽게 사용자가 증가해 maxWait
값 안에 connection을 얻지 못하는 빈도가 늘어난다면 maxWait
값을 줄여 tomcat thread 한도에 도달하지 않도록 방어할 수 있습니다. 이렇게 되면 전체 시스템 장애는 피하고 간헐적 오류가 발생하는 정도로 장애 영향도를 줄일 수 있습니다. 다만, 이러한 상황이 자주 발생한다면 DBCP의 maxActive
와 Tomcat의 maxThread
값을 동시에 늘리는 것을 고려해야 합니다. 하지만, 시스템 자원의 한도를 넘는 요청이라면 설정을 어떻게 하던 장애를 피할 수 없습니다. 이 때에는 시스템을 늘리는 방향으로 진행해야 합니다.
connection의 검사(validate)와 정리(evictor)
validationQuery
설정과 Evictor 스레드
관련 설정을 통해 어플리케이션의 안정성을 높일 수 있습니다.
속성 이름 | 설명 |
---|---|
testWhileIdle | evictor 스레드가 실행될 때 connection pool 안에 있는 유휴 상태의 connection을 대상으로 테스트 실행(default: false) |
testOnReturn | connection을 반환할 때 테스트(검증) 실행(default: false) |
timeBetweenEvictionRunsMillis | Evictor 스레드가 동작하는 간격이며, 1000ms보다 작은 값을 설정할 수 없음(default: 5000ms) |
minEvictableIdleTimeMillis | Evictor 스레드 동작 시 connection의 유휴 시간을 확인해 설정값 이상일 경우 connection 제거(default: 60000ms) |
validationQuery | 유효성 검사 쿼리로, DBMS 종류에 따라 주로 아래와 같이 사용 [ Oracle ] select 1 from dual [ Microsoft SQL Server ] select 1 [ MySQL ] select 1 |
validationInterval | connection 검증 시, 설정값 이내로 기존에 테스트한 이력이 있으면 다시 검증하지 않음(default: 3000ms) |
testOnBorrow | connection pool에서 connection을 얻어올 때 테스트(검증) 실행(default: false) |
검증에 지나치게 자원을 소모하지 않도록 모든 검증 속성을 true
로 하는 것은 권장하지 않습니다. 다만 testWhileIdle
옵션은 true
로 설정하여 오랫동안 대기 상태(유휴 상태)였던 connection이 끊기는 것을 막는 것이 좋습니다.
Evictor 스레드
는 DBCP 내부에서 connection 자원을 정리하는 구성 요소로 별도의 thread로 실행됩니다. Evictor 스레드의 역할은 다음과 같습니다.
- connection pool 내의 유휴 상태의 connection 중 오랫동안 사용되지 않은 connection 제거
testWhileIdle
속성이 true로 되어 있을 경우, connection에 대해 추가로 유효성 검사를 수행하며 문제가 있다면 해당 connection 제거- 앞 2가지 과정 진행 후 connection 수가
minIdle
보다 적다면minIdle
크기만큼 connection을 생성해 유지
Evictor 스레드
는 동작 시 connection pool에 lock
을 걸고 동작하기 때문에 너무 자주 동작하게 되면 서비스에 부담될 수 있습니다. 따라서 connection 유효성 검사를 위한 테스트 옵션(testOnBorrow
, testOnReturn
, testWhileIdle
)과 Evictor 스레드
관련 옵션은 DBA와 상의하여 최적화될 수 있는 옵션을 설정해야 합니다.
참고 자료
'OPEN SOURCE > TOMCAT' 카테고리의 다른 글
[TOMCAT] mySQL 연동 에러(WARNING: Unexpected exception resolving reference) (0) | 2021.02.01 |
---|---|
[TOMCAT] MySQL datasource의 url 내 property 여러개 설정 (0) | 2021.02.01 |