출처:
https://fuirosun.tistory.com/entry/JDBC-Timeout%EA%B3%BC-DBCP
DBCP
데이터베이스와 애플리케이션을 효율적으로 연결하는 커넥션 풀(connection pool) 라이브러리는 웹 애플리케이션에서 필수 요소다.
웹 애플리케이션 서버로 상용 제품을 사용하다면 보통 제조사에서 제공하는 커넥션 풀 구현체를 사용한다.
Apache의 Commons DBCP와 Tomcat-JDBC 등이 있다.
웹 애플리케이션의 요청은 대부분 DBMS(database management system)로 연결되기 때문에 커넥션 풀 라이브러리의 설정은 전체 애플리케이션의 성능과 안정성에 영향을 미치는 핵심이다.
따라서 라이브러리의 내부 구조와 원리, 속성값의 의미를 이해하는 것이 좋다.
JDK(Java development kit)의 버전에 따라서 Connection이나 Statement 같은 JDBC(Java database connectivity)의 인터페이스가 조금씩 다르므로 사용하는 JDK의 버전에 맞게 Commons DBCP 버전을 선택해야 안정된 동작을 기대할 수 있다.
Spring 프레임워크를 사용한다면 다음과 같이 bean 설정으로 속성을 등록한다.
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close"
p:driverClassName="${db.driverClassName }"
p:url="${db.url}"
p:username="${db.username}"
p:password="${db.password}"
p:maxTotal="${db.maxTotal}"
p:maxIdle="${db.maxIdle}"
p:maxWaitMillis="${db.maxWaitMills}""
/>
커넥션 개수를 제대로 설정하려면 Commons DBCP 내부에서 커넥션 풀이 어떤 구조로 저장되는지 이해해야 한다.
Commons DBCP는 commons-pool에서 제공하는 리소스 풀의 기능을 이용한다.
자세한 내용은 Commons DBCP 이해하기를 참고하자.
커넥션을 얻기 전 대기 시간
적절한 maxWait 값을 설정하려면 TPS(transaction per seconds)와 Tomcat에서 처리 가능한 스레드 개수 등을 이해해야 한다.
maxActive = 5과 maxIdle = 5, minIdle = 5로 설정한 상황을 가정한다.
사용자의 요청 A는 요청 하나에 쿼리 10개를 실행한다고 가정하자.
각 쿼리의 평균 실행 시간은 50밀리초라고 하면 전체 10개 쿼리의 실행 시간은 500밀리초다.
결국 요청에 대한 최종 응답 시간은 500밀리초라고 생각할 수 있다.
(요청에 응답하기 위해 다른 컴포넌트도 시간을 소비하지만 무시할 수 있는 정도의 값이라고 생각해 제외했다.)
요청 하나의 응답 시간이 500밀리초이므로 커넥션 풀에 이용 가능한 유휴 상태의 커넥션이 5개일 때는 동시에 5개의 요청을 500밀리초 동안 처리한다.
따라서 1초 동안에는 10개의 요청을 처리할 수 있고 성능 지수는 10TPS라고 볼 수 있다.
처리할 요청 수가 증가하면... 커넥션 풀의 커넥션 개수가 5개이면 10TPS 이상의 성능을 낼 수 없다.
1번부터 5번까지의 요청이 실행되는 동안은 커넥션 풀에 여분의 커넥션이 없기 때문에
6번부터 10번까지의 요청은 대기(wait) 상태가 돼 여분의 커넥션이 생길 때까지 maxWait 값만큼 기다린다.
이를 해결하는 가장 쉬운 방법은 단순히 maxActive 값을 높여서 커넥션 풀의 개수를 늘리는 것이다.
커넥션의 개수를 5에서 10으로 늘리면 전체적인 성능도 10TPS에서 20TPS로 증가한다.
하지만 일반적으로 DBMS의 리소스는 다른 서비스와 공유해 사용하는 경우가 많기 때문에 무조건 커넥션 개수를 크게 설정할 수는 없다.
따라서 예상 접속자 수와 서비스의 실제 부하를 측정해 최적값을 설정하는 것이 중요하다.
대기 시간(wait) 값 조절이 무한히 커넥션 개수를 늘리지 않고 최적의 시스템 환경을 구축하는 데 중요한 역할을 한다.
maxWait 값을 어떻게 설정했는지가 일시적인 과부하 상태에서 드러나는 시스템의 전체적인 견고함을 결정짓는다.
Commons DBCP 외에 Tomcat의 동작 방식도 고려해야 한다.
Tomcat은 스레드 기반으로 동작해 사용자의 요청을 처리한다.
Commons DBCP가 커넥션 풀을 가지고 있는 것처럼 Tomcat도 내부에 스레드 풀(wait set)을 가지고 있다.
중점적으로 살펴볼 부분은 1~5번의 요청이 처리되기 전에 또 다른 요청이 들어올 때 시작된다.
6번 요청은 여분의 커넥션이 없으므로 maxWait 값만큼 기다린다.
중요한 사실은 기다리는 주체가 Tomcat의 스레드라는 점이다.
(Tomcat에서 사용자의 연결을 처리하는 최대의 스레드 개수는 server.xml 파일에서 maxThread 속성으로 지정한다.)
maxWait 속성에 설정한 시간이 10,000밀리초면 처리량을 넘어서는 요청의 스레드는 10초 동안 대기 상태에 있게 된다.
요청이 계속 증가하면 결국 Tomcat 스레드 풀의 모든 스레드가 소진돼 Tomcat은 다음과 같은 오류를 출력하며 응답을 멈출 것이다.
// 심각: All threads (512) are currently busy, waiting. Increase maxThreads (512) or check the servlet status
클릭 후 2~3초 내에 반응이 없으면 페이지를 새로 고치거나 다른 페이지로 이동하는 것이 보통인 인터넷 사용자의 행동을 생각하면 쉽게 이해되리라.
사용자가 인내할 수 있는 시간을 넘어서는 maxWait 값은 아무런 의미가 없다.
너무 작게 설정하면 어떤 문제가 발생할까?
과부하 시 커넥션 풀에 여분의 커넥션이 없을 때마다 오류가 반환될 것이고 사용자는 너무 자주 오류 메시지를 볼 것이다.
이렇듯 maxWait 값도 사용자의 대기 가능한 시간 같은, 애플리케이션의 특성과 다른 주변의 설정, 자원의 상황 등을 고려해 판단해야 한다.
만약 갑작스럽게 사용자가 증가해 maxWait 값 안에 커넥션을 얻지 못하는 빈도가 늘어난다면 maxWait 값을 더 줄여서 시스템에서 사용하는 스레드가 한도에 도달하지 않도록 방어할 수 있다.
전체 시스템 장해는 피하고 '간헐적 오류'가 발생하는 정도로 장애의 영향을 줄이는 것이다.
이런 상황이 자주 있다면 Commons DBCP의 maxActive 값과 Tomcat의 maxThread 값을 동시에 늘이는 것을 고려한다.
그러나 시스템 자원의 한도를 많이 넘는 요청이 있다면 설정을 어떻게 변해도 장애를 피할 수 없다.
애플리케이션 서버의 자원이 설정 변경을 수용할 만큼 충분하지 않다면 시스템을 확충해야 할 것이다.
JDBC 타임아웃
아래는 WAS와 DBMS와 통신 시 타임아웃 계층을 단순화한 것이다.
1. JDBC Connection 획득
2.
트랙잭션 건다.
Statement를 실행.
소켓 연결
JDBC 커넥션 실행
상위 레벨의 타임아웃은 하위 레벨의 타임아웃에 의존성을 가지고 있다.
예를 들어, JDBC Driver SocketTimeout이 정상으로 동작하지 않으면, 그보다 상위 레벨의 타임아웃인 StatementTimeout과 TransactionTimeout도 정상으로 동작하지 않는다.
"StatementTimeout을 설정했는데도 네트워크 장애가 발생하면,
StatementTimeout은 Statement 한 개의 수행 시간을 제한하는 기능만 담당하므로
네트워크 연결 장애에 대한 타임아웃을 담당하는 JDBC Driver SoecketTimeout을 살펴봐야 한다.
JDBC Driver SocketTimeout은 OS의 SocketTimeout 설정에 영향을 받는다.
JDBC Driver SocketTimeout을 설정하지 않아도 네트워크 장애 발생 이후 30분이 지나면 JDBC Connection Hang이 복구되는 것은 OS의 SocketTimeout 설정때문이다.
1번 JDBC Connection 획득을 보면 DBCP는 타임아웃 계층과 분리되어 타임아웃 처리에는 관여하지 않는다는 것을 알 수 있다.
Connection을 애플리케이션이 얻을 때까지의 타임아웃을 지정할 수 있다.
하지만 이것은 JDBC의 ConnectTimeout과는 무관하다.
Timeout 종류 |
설명 |
Timeout 수행 주체 |
설정 |
wait_timeout |
connection pool에서 커넥션을 꺼낼때, 기다리는 시간을 지정한다. |
DBCP |
DBCP maxwait |
TransactionTimeout |
여러 쿼리를 트랜젝션으로 묶고 수행할 때, 지정된 시간을 초과하면 발생한다. |
트랜젝션을 관리하는 프레임워크 |
Spring의 경우 DefaultTransactionDefinition.setTimeout() |
StatementTimeout |
쿼리 하나를 수행할 때, 지정된 시간을 초과하면 발생한다. |
JDBC 드라이버 |
java.sql.Statement.setTimeout() |
SocketTimeout |
소켓을 이용해서 데이터를 주고받을 때, 지정된 시간을 초과하면 발생한다. 네트워크 이슈 or DBMS 구동 중단이 발생한 경우, 이 Timeout으로 인지해야 한다. |
JDBC 드라이버 |
jdbc:mysql://xxx.xxx:3306/database? socketTimeout=10000 |
ConnectionTimeout |
소켓 커넥션을 맺을 때, 지정된 시간을 초과하면 발생한다. |
JDBC 드라이버 |
jdbc:mysql://xxx.xxx:3306/database?connectTimeout=300000 |
'프로그래밍 > Database' 카테고리의 다른 글
mongo + ec2 + springboot (0) | 2020.09.10 |
---|---|
MySQL Locking Reads (0) | 2020.05.26 |
Couchbase cluster with 2 vm (0) | 2019.07.17 |
NoSQL 정리 (0) | 2019.03.21 |
Normalization & Denormalization 요약 (0) | 2019.03.19 |