웹 어플리케이션은 정보 저장이 필요할 때 주로 데이터베이스(Database, 이하 DB)를 이용한다. 이를 위해 JEUS와 같은 WAS (Web Application Server)는 어플리케이션이 DB로 접근할 수 있는 통일된 방법을 제공해야 한다. 이러한 통일된 DB 접근을 위해 만든 표준이 바로 JDBC(Java Database Connectivity) 표준이다. JDBC 표준은 어플리케이션이 DB 커넥션을 사용하는 방법에 대해 기술하고 SQL 작업을 하기 위한 API를 제공하고 있다. 표준에 관한 자세한 내용은 Sun JDBC 페이지(http://java.sun.com/javase/technologies/database)를 참고하도록 한다.
JEUS에서는 어플리케이션의 원활한 DB 사용을 위해 커넥션 풀(Connection Pool)과 여러 부가 기능들을 제공한다. 이번 장에서는 JEUS에서 제공하는 커넥션 풀 및 부가 기능들에 대해 설명하고 그 사용법에 대해 알아보도록 한다.
JDBC는 Java 어플리케이션이 DB에 접속하기 위한 Java 표준 API이다. 현재 JEUS는 JDBC 3.0을 지원하고 있다.
JEUS JDBC는 응용 프로그램의 효율적인 DB 사용을 위한 커넥션 풀 및 그 외 기반 기능들을 제공한다. 사용자는 그러한 기능들을 사용하기 위하여 이하 소개되는 내용들을 숙지하고 알맞은 설정을 해야 한다.
JEUS는 JDBC 인증을 받은 드라이버들을 지원한다. 인증된 드라이버의 타입이나 그 종류는 Sun의 'Types of JDBC technology drivers' (http://java.sun.com/products/jdbc/driverdesc.html) 문서에서 찾을 수 있다. JEUS의 JDBC 환경구성은 어떤 벤더의 DB를 사용하느냐에 따라 환경 구성이 서로 다를 수 있다. 이는 각 드라이버마다 각자 요구하는 속성이 다르기 때문이므로 각 벤더의 JDBC 드라이버 매뉴얼을 참고해야 한다.
하나의 javax.sql.DataSource는 어플리케이션과 커넥션 풀 사이의 인터페이스이다. javax.sql.DataSource 객체는 DB 커넥션들의 팩토리로 볼 수 있으며 java.sql.DriverManager보다 많은 이점을 제공한다.
아래는 데이터 소스들의 4가지 타입들을 간략하게 정리하였다.
기본 데이터 소스: 사용자들을 위해 커넥션을 반환한다. 커넥션 풀링이 이루어지지 않기 때문에 커넥션 풀 형식에 비해 추가적인 오버헤드가 있을 수 있다.
커넥션 풀 데이터 소스: 커넥션 풀에 저장된 커넥션을 얻어 응용 프로그램 등에 반환한다. JEUS에서 제공하는 커넥션 풀링 기능을 이용하므로 DB에 매번 접속하여 커넥션을 가져오는 방식보다 오버헤드가 덜하다. auto-commit을 false로 하고 사용할 경우 어플리케이션이 직접 로컬 트랜잭션을 컨트롤 할 수 있다.
XA 데이터 소스: 분산/전역 트랜잭션에 이용되는 커넥션을 관리한다. 이 데이터 소스 형식은 2PC (2 Phase Commit)를 이용할 경우에만 사용하도록 한다. EJB나 Servlet에서 트랜잭션을 시작한 후, XA 데이터 소스로 부터 얻어진 두 개 이상의 커넥션을 이용하여 작업을 시작하면 자동적으로 2PC-protocol (2 Phase Commit protocol)이 시작 되며, 이 커넥션은 트랜잭션이 끝난 이후에는 다시 사용할 수 없다. XADataSource를 사용할 경우에는, 각각의 데이터 소스에 대해 트랜잭션 복구 기능이 지원된다.
로컬 XA 데이터 소스: 커넥션 풀 데이터 소스에서 얻은 커넥션을 auto-commit을 꺼서 로컬 트랜잭션이 항상 켜진 상태로 사용하고 커밋이나 롤백을 트랜잭션 매니저가 처리해준다. 이 때문에 로컬 트랜잭션를 XA에 참여하도록 에뮬레이션 할 수 있다. 참고로 로컬 XA 데이터 소스는 JDBC 드라이버가 XA 데이터 소스를 지원하지 않더라도 XA에는 참여시킬 필요가 있을 때 사용할 수 있다.
참고
로컬 XA 데이터 소스는 기능상의 제약으로 제대로 복구가 되지 않을 경우가 생길 수 있으며, 하나의 글로벌 트랜잭션 내에서 최대 하나의 로컬 XA 데이터 소스만 참여할 수 있다.
참고
로컬 XA 데이터 소스에서 얻은 커넥션은 auto-commit을 끄고 사용하는 로컬 트랜잭션이기 때문에 JDBC 드라이버에서 자체적으로 관리하므로 JEUS 트랜잭션 매니저가 관리하는 XA 트랜잭션과는 엄밀하게 다른 트랜잭션이다. 대신 JEUS에서는 로컬 트랜잭션이 XA에 참여할 수 있도록 에뮬레이션을 해주는 것이다.
지금까지 JEUS 설정에서 제공하는 다양한 데이터 소스 형식을 살펴보았다. 데이터 소스에는 다양한 종류가 있으면서 각각 장단점을 가지고 있다. 그러므로 어플리케이션에서 필요한 것이 무엇인지 확인하고, 최적의 성능과 각각의 상황을 위해 적당한 타입을 사용해야 한다.
JEUS 레벨에서 RAC 인스턴스 간의 Failover & Failback을 제공하기 위해서 클러스터 데이터 소스를 제공한다. RAC는 Real Application Cluster의 약자로 오라클에서 제공하는 DB 클러스터링 기능이다. RAC에 관한 자세한 내용은 오라클의 문서를 참고하기 바란다.
클러스터 데이터 소스는 근본적으로는 하나의 JNDI 이름을 가진 데이터 소스 인스턴스이다. 이 인스턴스는 어플리케이션의 요청을 우선 주 RAC 인스턴스로 전달시켜주는 역할을 한다. 만약 주 인스턴스가 다운되었을 경우, 백업 RAC 인스턴스가 선택되어서 요청 사항을 처리하게 된다. 어플리케이션에서는 단지 하나의 데이터 소스만 보게 되므로 장애복구가 투명하게 처리된다. [그림 9.2]
그리고 오라클 JDBC 드라이버 레벨에서 제공하는 CTF(Connect Time Failover)보다는 JEUS의 클러스터 데이터 소스를 사용하길 권장한다. 오라클 CTF의 경우에는 커넥션 별로 Failover를 하기 때문에 데이터 소스 전체가 문제가 생겼을 경우 풀에 있는 모든 커넥션마다 Failover를 해야 하기 때문에 성능이 떨어진다. 그러나 JEUS 클러스터 데이터 소스에서는 데이터 소스에 문제가 생긴 것을 감지하면 데이터 소스 단위로 Failover를 하므로 더 효율적이며, 자동으로 Failback을 하는 기능도 제공한다.
JEUS6 fix#3부터는 기본적으로 Failback을 지원하므로 이를 위한 설정이 반드시 필요하다. 클러스터 데이터 소스 설정에 관한 내용은 9.3.5절. “클러스터 데이터 소스 설정”를 참고하기 바란다.
참고
JEUS의 클러스터 데이터 소스를 사용하는 방식과 오라클 JDBC 드라이버에 RAC 속성을 설정하는 방식은 서로 동작이 다르다는 점에 주의해야 한다. 전자는 Failover를 JEUS가 처리하는 것이기 때문에 각 RAC 인스턴스의 FAIL 여부를 감지하기 위해서 check-query와 check-query-period를 설정해야 한다. 그러나 후자는 드라이버가 Failover를 담당하게 되므로 check-query가 필수적인 것은 아니다.
JEUS에서 JDBC 드라이버 구성을 시도할 때 JEUS_HOME\lib\datasource\ 디렉토리 안에 JDBC 드라이버 클래스 라이브러리가 있는 지 먼저 확인한 후 다음 절과 같이 JEUSMain.xml
을 구성하거나, WebAdmin를 통해 설정을 해주어야 한다. 대부분의 경우 WebAdmin를 통해 이를 설정할 것을 권장한다.
JEUSMain.xml
에 데이터 소스를 설정할 수 있다. javax.sql.DataSource의 속성들은 각 드라이버별로 다르기 때문에 사용하기 원하는 드라이버의 특성을 파악하고 그 특성에 맞게 설정을 해야 한다. 각 태그들의 전체 리스트는 부록 E. “참고 자료”에 소개된 XML Reference에서 찾을 수 있다. 또한 JDBC 구성 예제들은 부록 B. “주요 벤더의 JDBC Data Source 구성 예”를 보기 바란다.
아래의 XML 태그들은 <resource><data-source>...<database> XML 태그의 하위 태그로 이용할 수 있다.
vendor: DB 벤더의 이름 (“oracle”, “mssql”, “db2”, “sybase”, “tibero”, “others”). 현재 사용하고 있는 DB의 벤더 이름을 사용하고, 나열되지 않은 DBMS는 others로 설정하도록 한다.
data-source-type : “DataSource”, “ConnectionPoolDataSource”, “XADataSource”, “LocalXADataSource” 값 중에 하나. 사용하려는 데이터 소스 타입에 맞게 선택한다.
data-source-name: 데이터 소스의 이름. 드라이버 벤더에 의존적이며 일반적으로 DataSourceClassName 값과 동일하다. (Deprecated)
service-name: Oracle inet 드라이버 사용시만 사용하며 Oracle Database의 SID 값. (Deprecated)
network-protocol: DB에 연결할 때 사용되는 프로토콜(예를 들면 Sybase의 "Tds"). (Deprecated)
password: 사용자의 암호. password를 암호화해서 사용할 경우 {algorithm}ciphertext 형식으로 쓴다. (ex. {DES}FQrLbQ/D801DL28rw==) 허용되는 알고리즘 정보는 JEUS Security 안내서, 3장. “보안 시스템의 설정”에 설명되어 있다.
user: 사용자 이름.
driver-type: Oracle의 경우 드라이버의 타입(ex. thin, oci). (Deprecated)
property: JDBC 커스텀 프로퍼티 지정. 자세한 내용은 다음에 나오는 커스텀 프로퍼티 설정을 참고한다.
auto-commit: 커넥션에 지정될 auto commit 값을 지정한다. true, false로 지정한다. 로컬 XA 데이터 소스나 XA 데이터 소스의 경우에는 커넥션이 트랜잭션에 연동되어 있지 않을 경우에만 적용한다.
action-on-connection-leak: 컴포넌트(주로 Stateless 컴포넌트 - Servlet/JSP, Stateless 세션빈, MDB)에서 사용한 JDBC 커넥션에 대한 로깅이나 반환 액션을 설정한다. 설정하지 않았을 경우 기본 동작은 엔진 컨테이너에 설정한 invocation-manager-action을 따른다. invocation-manager-action에 대한 설명은 2.3.4절. “Invocation Manager”을 참고바란다.
주의
data-source-name, service-name, network-protocol, driver-type 등 특정 JDBC 드라이버에 의존적인 프로퍼티들은 커스텀 프로터티 설정으로 대체하길 권장한다.
예제 (Oracle):
앞서 설명한 기본 설정으로 부족할 때 <database>구성에 커스텀 프로퍼티를 추가하여 해당 프로퍼티를 가진 DB 드라이버를 사용할 수 있다. 각각의 새로운 속성을 추가를 위해 세가지 항목을 정의함으로써 사용할 수 있다. 각각의 DB 마다 요구하는 프로퍼티와 그 값이 다르므로 각 벤더의 JDBC 드라이버 매뉴얼에서 필요한 프로퍼티를 찾도록 한다.
속성의 name
속성의 type
java.lang.String, 원시 타입(Primitive type)의 래퍼 클래스(Ex. java.lang.Integer), java.util.Properties가 올 수 있다.
속성에 문자열을 줌으로써 value를 부여할 수 있다.
예 9.2. <<JEUSMain.xml>>
<jeus-system> . . . <resource> <data-source> <database> . . . <!-- See earlier sub-section --> <property> <name>portNumber</name> <type>java.lang.Integer</type> <value>1099</value> </property> <property> <name>driverType</name> <type>java.lang.String</type> <value>thin</value> </property> </database> . . . </data-source> </resource> </jeus-system>
<property> 태그는 위의 예처럼 추가된다. 위의 예처럼 프로퍼티가 지정이 되면 JEUS는 벤더의 JDBC 드라이버에 setPortNumber라는 메소드를 호출하게 된다. 그러므로 사용자는 각 벤더의 매뉴얼 등을 참고하여 속성을 알맞게 설정하도록 한다.
위에서 설명된 데이터 소스를 적절히 구성해 주었다면, JEUS를 재시작 해주어야 데이터 소스를 어플리케이션에서 사용할 수 있다.
각 데이터 소스는 향상된 성능을 위하여 커넥션 풀 설정을 할 수 있다. 이는 JEUSMain.xml
의 <database> 태그 아래 <connection-pool>을 사용하여 구성된다.
커넥션 풀 옵션들은 <connection-pool> 태그의 하위에서 <pooling> 태그로 묶인다. 해당 옵션들은 다음과 같다.
min: 캐시를 위한 DB 커넥션들의 초기값.
max: 캐시를 위한 DB 커넥션들의 최대값
step: 초기화가 되고 나서, 커넥션 수를 늘릴 상황이 되면, 커넥션이 이 값만큼 증가하게 된다.
period: 여기 설정 된 period 값에 한 번씩 커넥션 수를 조정한다. 여기서 조정 대상이 되는 커넥션 들은 idle 커넥션이 된다. 만약 현재 커넥션이 앞서 지정 된 min 값보다 작을 경우에는 idle 커넥션 수를 min값만큼 늘려주게 되며, 그 반대의 경우 idle 커넥션 수를 줄여 min값에 맞춰주게 된다. 만약 idle 커넥션이 없을 경우에는 min 값만큼 줄이는 작업은 하지 않는다. 단위는 msec 이다. 원활한 DB 작업을 위하여 이 값은 충분히 잡아주도록 한다. (default 30분)
wait-free-connection 설정은 풀안에 idle 커넥션이 있지 않을 경우 사용된다. 이 태그 역시 <connection-pool>의 하위에서 설정된다.
enable-wait: 이 태그는 풀안에 이용 가능한 커넥션이 없고 현재 커넥션들이 이미 최대값에 도달 되었을 때 DB 커넥션 요청을 처리하는 방법을 결정한다. 만약 true라면 시스템은 이용 가능한 커넥션을 얻기 위해 대기한다. 만약 대기 후에도 커넥션을 가져오지 못한다면 exception을 발생시킨다. 값이 false일 경우에는 커넥션을 새로 만들어서 어플에 제공하고 어플이 이를 반납하면 풀링하지 않고 닫아서 버린다. 이렇게 한번 쓰고 버린다는 의미로 JEUS에서 'disposable 커넥션'이라고 한다.
wait-time: 이 태그는 <enable-wait>가 true일 때만 유효하다. 이것은 사용자가 커넥션을 위해 대기하는 시간을 나타낸다. 만약 어떠한 커넥션도 사용자가 이 시간 동안 대기해서 이용할 수 없을 때는 시스템은 사용자에게 Exception을 던져준다. 단위는 msec이다.
delegation-datasource: 이 설정은 현재의 XA DataSource의 전역/지역 트랜잭션을 NonXA-DataSource로 넘길 때 사용한다. 데이터 소스가 XA 또는 Local XA일 때 트랜잭션으로 묶이지 않는 상황에서 커넥션을 얻어올 경우, 여기에 설정 된 데이터 소스에서 커넥션을 가져온다. 그러므로 이 태그에 해당하는 값은 ConnectionPoolDataSource타입으로 설정 된 데이터 소스의 export name이어야 한다. 자세한 내용은 9.3.4.4절. “위임 데이터 소스” 부분을 참고하도록 한다.
max-use-count: 한개의 커넥션에 대해서 설정된 숫자만큼 사용하게 되면 해당 커넥션을 버리고 새로운 커넥션을 맺게 된다. 기본값은 '0'이며 이는 커넥션들을 교체하지 않겠다는 의미이다.
delegation-dba: DBA 권한을 갖는 특별한 데이터 소스를 지정한다. 설정값은 지정 데이터 소스의 export name을 사용한다. 이 기능은 DB에서 기능을 제공해야 한다. (ex. Tibero,Oracle, Sybase) 지원 여부는 각 벤더의 매뉴얼등을 참고하도록 한다. 자세한 내용은 9.3.4.5절. “DB 세션 킬 기능 : DBA 위임 데이터 소스 (DBA Delegation Datasource)” 부분을 참고한다.
dba-timeout(deprecate): ConnectionPool에서 얻은 Connection을 여기에서 지정한 시간이 지난 후에도 반납하지 않으면 delegation-dba에서 설정된 DataSource를 통해 session kill 명령을 날려서 해당 Connection과 DB Session을 강제로 닫아준다. 하지만 이 기능은 꽤 위험하고 대체 기능이 있기 때문에 사용하기를 권장하지 않는다. 자세한 내용은 9.3.4.5절. “DB 세션 킬 기능 : DBA 위임 데이터 소스 (DBA Delegation Datasource)” 부분을 참고한다. 단위는 millisecond이고, 기본값은 무제한(-1)이다.
check-query: 어플리케이션이 getConnection할 때 JDBC 커넥션에 문제가 있는지 체크할 때 사용된다. JDBC 커넥션의 내부적인 에러로 인한 끊김, 방화벽에 의한 소켓 끊김 현상 등을 체크할 때 유용하다. 체크가 실패하면 물리적 커넥션을 새로 만들어서 그에 대한 핸들을 어플리케이션으로 리턴해 준다. 클러스터 데이터 소스를 사용하는 경우, 데이터 소스의 상태 체크에 이용하므로 반드시 설정해야 한다. (e.g. SELECT 1 FROM DUAL)
check-query-timeout: check-query를 했을 때 DB 상황에 따라서 JDBC 드라이버가 응답을 못 받고 계속 기다릴 수 있다. 이런 문제를 방지하기 위하여 JDBC 스펙에 정의된 query timeout 세팅을 사용하여 타임아웃을 줄 수 있다. 만약 타임아웃이 나게 되면 커넥션이 무효한 것으로 판단한다. 참고로 이를 제대로 지원하지 않는 드라이버가 있을 수 있다.
non-validation-interval: check-query를 수행할 때의 시각과 맨 마지막에 점검한 시각과의 차이가 이 인터벌 내에 있다면 check-query를 수행하지 않는다. 커넥션 요청이 빈번할 경우 check-query가 너무 잦아져서 오버헤드를 줄 수 있는데 이 값을 적절하게 설정하면 check 횟수를 줄일 수 있다.
check-query-period: JDBC 커넥션을 일정 시간마다 체크하여 문제가 있는 커넥션을 닫아준다. 이 기능을 사용하려면 check-query가 반드시 설정되어야 한다. 클러스터 데이터 소스를 사용하는 경우, 데이터 소스의 상태 체크에 이용하므로 반드시 설정해야 한다. 단위는 msec이다. (단위 msec) 이 값은 충분히 크게 지정한다.
check-query-retrial-count: check-query는 기본적으로 1번만 수행하지만 이것이 부족하다고 판단될 경우에는 이 설정을 통해서 체크 횟수를 늘릴 수가 있다.
check-query-class: 이 설정은 Check-Query기능의 확장을 위해 존재한다. 이 태그에는 jeus.jdbc.connectionpool.JEUSConnectionChecker를 구현한 class의 full name을 적어주도록 한다. 그렇게 할 경우 JEUS는 사용자가 지정한 class를 이용하여 Check-Query를 실행하게 된다. 자세한 내용은 9.3.4.1절. “커넥션 점검(Connection Validation or Check Query)”를 참고한다.
destroy-policy-on-check-query: JDBC 커넥션 유효성 체크가 실패했을 경우 해당 커넥션 풀에 있는 커넥션들을 어떻게 할지 정책을 결정하는 옵션이다. 정책의 경우 'FailedConnectionOnly'와 'AllConnections'가 있다. 자세한 내용은 9.3.4.1절. “커넥션 점검(Connection Validation or Check Query)” 를 참고한다.
stmt-caching-size: PreparedStatement 객체를 얼마나 캐시할 지 지정한다. 기본값으로는 이 기능을 사용하지 않는다. Oracle 9i 이후 버전에서는 사용하지 않도록 한다. 다른 DBMS에서는 시스템 리소스의 낭비를 줄임으로써 성능 향상을 가져 올 수 있다.
stmt-fetch-size: 여기에 지정 된 값은 직접 하부 DB의 커넥션 객체에 저장 되는 값으로서 커넥션의 row fetch 값을 지정한다. 현재 JEUS에서는 Oracle과 Sybase에서 이 기능을 제공한다.
connection-trace: 커넥션이 보여줄 수 있는 정보들을 만들어 둘 것인지 설정하는 옵션이다. 현재는 invocation manager를 설정했을 때 또는 jeusadmin의 dsconinfo 명령을 통해서 현재 커넥션을 사용하고 있는 어플리케이션을 쓰레드 스택으로 확인할 수 있는 기능을 제공한다.
enabled: 이 태그는 connection trace 기능을 on/off 하는 옵션을 나타낸다.
keep-connection-handle-open: 물리적 커넥션을 풀링하는 동안 커넥션 핸들(또는 논리적 커넥션)을 항상 열어두고 사용하고자 할 때 필요한 옵션이다. DB2 Universal Driver에서 XA 데이터 소스를 사용할 경우에 이 옵션을 true로 하길 권장한다.
경고
IBM DB2에서 제공하는 Universal Driver(JCC) type 4는 2007년 11월 현재 XA 데이터 소스에 문제가 있음을 확인하였다. 여러 쓰레드에서 서로 다른 물리적 커넥션을 가져가서 핸들(또는 논리적 커넥션)을 get(open) & close 하면서 쓰다 보면 드라이버 내부적으로 무한루프에 빠진다. 이때 쓰레드 스택 덤프를 확인하면 내부적으로 같은 Vector(java.util.Vector)를 공유해서 쓰고 있는데 한 쓰레드는 Vector에 대한 락을 잡고 'Runnable'인 상태이고 다른 thread들은 그 락을 기다리게 된다.
DB2 내부 로직을 알 수 없기에 정확한 원인은 알 수 없지만 테스트 결과 커넥션 핸들을 항상 열어두면 문제가 발생하지 않는 것을 확인하였다. 커넥션 핸들을 항상 열어두면 내부 공유 Vector에 접근하는 일이 맨 처음 생성할 때와 물리적 커넥션을 닫을 때 외에는 없기 때문이다. 이를 설정하는 방법은 JEUS6 fix#2부터 추가되는 옵션인 <keep-connection- handle-open>을 true로 하면 된다.
init-sql: Connection이 생성되고 난 뒤 가장 처음으로 수행해야 하는 sql이 있을 때 설정해서 사용한다. keep-connection-handle-open 기능을 사용하면 init-sql을 수행할 때 썼던 connection-handle을 계속 열어두고 사용한다.
use-sql-trace: 어플리케이션이 사용한 SQL문을 로그에 남기거나 현재 커넥션에서 수행되고 있는 SQL을 볼 수 있는 기능이다.
경고
이것을 사용하게 되면 항상 JDBC Statement 객체에 대한 래퍼를 사용하게 되므로 JDBC 드라이버의 Statement 객체를 캐스팅해서 사용할 경우에는 이 기능을 사용할 수 없다.
예 9.3. <<JEUSMain.xml>>
<jeus-system> . . . <resource> <data-source> <database> . . . <!-- See earlier sub-section --> <connection-pool> <pooling> <min>2</min> <max>20</max> <period>500000</period> </pooling> <wait-free-connection> <enable-wait>true</enable-wait> <wait-time>60000</wait-time> </wait-free-connection> <max-use-count>30</max-use-count> <!-- check-query related configurations --> <check-query>SELECT 1 FROM DUAL</check-query> <non-validation-interval>5000</non-validation-interval> <check-query-period>1000000</check-query-period> <destroy-policy-on-check-query> AllConnections </destroy-policy-on-check-query> <stmt-caching-size>10</stmt-caching-size> <connection-trace> <enabled>true</enabled> </connection-trace> <use-sql-trace>true</use-sql-trace> <!-- When using DB2 XA data source --> <keep-connection-handle-open>false</keep-connection-handle-open> </connection-pool> </database> . . . </data-source> </resource> </jeus-system>
어플리케이션이 JDBC 커넥션 요청을 했을 때(getConnection method) 특정 select 쿼리를 보내서 커넥션의 상태를 점검(validation)하는 기능이다. JDBC 커넥션의 내부적인 에러로 인한 끊김, 방화벽에 의한 소켓 끊김 현상 등을 체크할 때 유용하다. 점검이 실패하면 물리적 커넥션을 새로 만들어서 그에 대한 핸들을 어플리케이션으로 리턴해 준다. 만약 RAC를 위한 데이터 소스 클러스터링을 사용하는 경우 이것을 반드시 설정해야 한다.
Check-query 기능은 크게 두 가지로 설정할 수 있다. 첫째로 단순히 설정상의 <check-query> 태그에 쿼리문을 넣는 방법이 있고, <check-query-class> 태그를 이용하여 기능을 확장할 수도 있다.
Check-query를 위한 쿼리문은 DB에 업데이트를 가하는 명령이 아닌 단순히 쿼리만을 위한 명령어를 넣어야 한다. 그렇지 않을 경우 Check-query를 위해 DB 락을 잡기 때문에 이후 업데이트를 위한 작업들이 원활히 수행되지 않을 수도 있다.
Check-query를 수행했을 때 DB 서버가 응답을 안 줘서 드라이버가 계속 기다리는 상황이 발생할 수 있다. 이런 경우를 피하기 위해 Check-query에 대해서 타임아웃을 줄 수 있다. 이것은 JDBC에서 제공하는 'statement query timeout' 기능을 이용하는 것이다. 설정값은 밀리세컨드이며, 1000 밀리세컨드보다 적을 경우 결국 0이 세팅되므로 주의하기 바란다.
만약 Check-query가 너무 잦아져서 오버헤드가 발생한다면 <non-validation-interval> 설정을 고려하길 권한다. 이는 Check-query를 수행할 때의 시각과 맨 마지막에 커넥션을 사용한 시각과의 차이가 설정한 인터벌 내에 있다면 Check-query를 하지 않도록 하는 설정이다. 예를 들어 Non-Validation-Interval을 5초(5000 ms)라고 설정했을 때, 어떤 커넥션을 풀에서 꺼내서 마지막에 사용했던 시간이 아직 5초가 지나지 않았다면 그 커넥션이 유효하다고 가정하고 Check-query를 수행하지 않는 것이다.
사용자는 Check-query가 실패했을 때 해당 커넥션 풀에 있는 나머지 커넥션들에 대한 destroy 정책을 결정할 수 있다. 정책은 아래와 같다.
만약 정책을 'AllConnections'로 했을 경우에는 Check-query가 실패하자마자 바로 커넥션들을 버리는 것이 아니라 한 번 더 커넥션을 풀에서 꺼내서 Check-query를 시도해 본다. 그마저도 실패하면 비로소 풀에 있는 모든 커넥션들을 버린다.
예 9.7. check-query 실패시 풀에 있는 커넥션에 대한 destroy 정책 설정
<destroy-policy-on-check-query>AllConnections</destroy-policy-on-check-query>
그리고 Check-query를 추가적으로 더 하도록 설정하고 싶을 경우에는 <check-query-retrial-count>를 이용해서 설정할 수 있다.
Check-query 클래스는 Check-query 기능을 사용자가 확장할 수 있도록 한다. 응용 프로그램 개발자나 사용자는 위에서 제시한 jeus.jdbc.connectionpool.JEUSConnectionChecker interface를 구현함으로써 기능의 확장을 도모할 수 있다.
예 9.8. jeus.jdbc.connectionpool.JEUSConnectionChecker
public interface JEUSConnectionChecker { void checkConnection(Connection vcon) throws SQLException; void setQueryString(String query); void setQueryTimeout(int timeout); }
checkConnection(): 이 메소드는 실제 Check-query 작업을 수행할 때 불려진다. 그러므로 개발자는 이 method에 자신이 Check-query시 실행하고 싶은 내용들을 구현하여 넣으면 된다.
setQueryString(): 만약 <check-query> 태그에 쿼리문이 지정되어 있는 상태라면, JEUS는 이 메소드를 호출한다. 물론 인자로는 <check-query> 태그에 지정된 쿼리문이 들어가게 된다.
setQueryTimeout(): 만약 <check-query-timeout> 이 지정되어 있는 상태라면, JEUS는 이 메소드를 호출한다.
만약 Check-query class가 지정이 되지 않은 상태라면, JEUS는 지정된 쿼리문만을 이용하여 Check-query를 수행하게 된다. 즉 Check-query class의 사용은 선택을 할 수 있으며, 단순히 쿼리문을 이용하는 것이 아닌 특별한 작업이 필요한 경우에만 사용하도록 한다.
커넥션 별로 사용하고 있는 SQL문을 보여주는 기능이다. 시스템 로그를 통해서 SQL history를 볼 수 있고, jeusadmin을 통해서 커넥션 별로 현재 수행되고 있는 SQL을 확인할 수 있다. 서버 로그 상에는 jeus.jdbc.sql 로거의 레벨을 FINE으로 설정할 경우 보이게 된다. 그리고 이 기능을 사용할 경우 JDBC 드라이버의 Statement 객체를 항상 wrapping하게 되므로 JDBC 드라이버의 Statement 객체를 캐스팅해서 사용하는 어플리케이션은 이 기능을 사용할 수 없다.
.... Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql); .... stmt.close(); conn.close(); ....
PreparedStatement는 미리 SQL을 파싱해놓은 인스턴스를 어플리케이션 입장에서 계속 재활용해서 사용할 수 있으므로 매번 드라이버가 SQL을 파싱하기 위해 들이는 오버헤드를 줄일 수 있다. 그런데 이러한 PreparedStatement 역시 커넥션을 새로 얻을 때마다 SQL 파싱 작업을 해야 하므로 커넥션 요청(getConnection & close)이 빈번하게 이뤄지는 경우에는 PreparedStatement 인스턴스를 생성하는 것 역시 큰 오버헤드가 될 수 있다. 따라서 JEUS에서는 이런 오버헤드를 줄이기 위해 Statement Caching 기능을 제공한다. 이것을 사용하면 물리적 커넥션 별로 SQL 문장을 키로 하여 PreparedStatement 객체를 저장해놓고 어플리케이션이 PreparedStatement를 요청할 경우에 파라미터로 넘어온 SQL을 보고 미리 만들어진 것이 있으면 그것을 리턴해준다.
그런데 Connection 인스턴스에서 PreparedStatement를 만들기 때문에 그 Connection 인스턴스를 닫아버리면(close) 해당 PreparedStatement 인스턴스 역시 무효로(invalid) 된다. 따라서 어플리케이션이 커넥션을 닫았다고 하더라도 실제로 WAS에서는 이 커넥션을 계속 열어둔 채로 재활용하도록 되어 있다. 그래야만 PreparedStatement 인스턴스를 계속 재활용 할 수 있기 때문이다.
주의
어플리케이션은 Statement Caching의 제약사항을 반드시 숙지해야 한다. 즉, 커넥션을 항상 열어둔 채로 사용하기 때문에 커넥션을 닫았을 때 드라이버가 해주는 클리어 작업이 이뤄지지 않는다. 예를 들어 Oracle JDBC 드라이버의 경우, auto-commit을 false로 해놓고 사용하다가 commit이나 rollback을 하지 않고 커넥션을 닫으면 무조건 commit을 하도록 되어 있는데 이런 처리가 되지 않는다는 것이다. keep-connection-handle-open 옵션 또한 같은 제약사항이 있으므로 주의하기 바란다.
일반적으로 XA 데이터 소스가 사용 되면, 대부분 트랜잭션으로 묶여 작업이 이루어진다. 하지만 일부의 경우에는 트랜잭션이 시작되기 전이나, 끝난 후에 커넥션을 가져다 쓰는 경우가 있을 수 있다. 이런 상황에서는 트랜잭션과 무관하게 커넥션이 사용되므로 커넥션 풀 형식의 데이터 소스에서 가져온 커넥션과 동작에 있어 아무런 차이가 없게 된다.
그런데 Oracle, DB2 등의 JDBC 드라이버에서는 XA 커넥션을 트랜잭션 없이 사용도 하고 트랜잭션에 연동도 하면서 사용하다 보면 XA를 시작할 수 없는 예외가 발생한다. 정확한 원인은 알 수 없기 때문에 이를 피해 가기 위해서 위임 데이터 소스를 사용할 수 있다.
위임 데이터 소스(Delegation Datasource)는 XA 형식으로 지정된 데이터 소스가 트랜잭션과 무관한 곳에서 커넥션 요청을 받을 경우 이용하게 되는 데이터 소스이다. 이 상황에서 사용자가 커넥션 요청을 할 경우 XA 데이터 소스는 위임 데이터 소스로 지정된 커넥션 풀 데이터 소스에서 커넥션을 가져와 사용자에게 넘겨주게 된다.
이 기능을 사용하기 위해서는 우선 XA 데이터 소스와 같은 DB에 대한 커넥션 풀 데이터 소스를 지정한다. 그리고 <delegation-datasource> 태그 내부에 설정한 커넥션 풀 데이터 소스의 JNDI 이름을 넣어주면 된다.
앞서 <delegation-datasource>, <dba-timeout>에서 설명한 것처럼 DBA 위임 데이터 소스는 Connection에 대응하는 DB Session을 강제로 정리해줘야 할 필요가 있을 때 사용한다. ConnectionPool에서 Connection을 얻어간 뒤 오랜 시간동안 Pool로 반납되지 않으면 해당 Connection이 DB에 부하를 주거나 장애를 일으킬 것으로 판단하고 강제로 DB를 정리를 하고자 사용하던 기능이다. JEUS6 Fix#6에서는 Webadmin을 통해서 DB Connection 강제 정리 명령을 내릴 때도 DBA 위임 데이터 소스가 설정되어 있다면 이를 이용해서 DB에 Session Kill명령을 내리도록 하였다. 기본적으로 이 기능은 DB에 직접 operation을 가하는만큼 사용시에 주의가 필요하지만 아래에서 dba-timeout을 설정하면 어떻게 작동하는지 간략하게 설명하겠다.
우선 사용하는 데이터 소스 외에 커넥션 풀 형식의 새로운 데이터 소스를 설정한다. 그 데이터 소스 이름이 DBKiller라고 한다면 사용하고 있는 데이터 소스에 다음과 같이 설정해준다.
예 9.9. DBA 설정
..... <connection-pool> <delegation-dba>DBKiller</delegation-dba> <dba-timeout>60000</dba-timeout> </connection-pool> .....
이렇게 설정이 되어있으면, 다음과 같은 동작을 한다. 만약 현재 사용하는 데이터 소스에서 얻어진 커넥션이 <dba-timeout> 태그에 지정 된 60초(단위가 msec이므로) 안에 반납이 되지 않으면, JEUS JDBC는 DBKiller 데이터 소스에 반납되지 않은 커넥션의 세션 id를 죽이라는 kill 명령을 내린다. 그렇게 되면 DBKiller 데이터 소스는 해당 커넥션을 강제로 닫아주게 되게 된다. 하지만 응용 프로그램이 명시적으로 close 명령을 내리지 않으면 해당 Connection은 active인 상태로 남아있게 된다. close를 호출하면 Connection을 버리고 새로 Connection을 맺어서 Pool에 넣어둔다.
이 기능을 사용하기 위해서는 DB가 이 기능을 지원해야 한다. JEUS에서는 단순히 해당 DB에 세션 제거를 요청하는 쿼리를 요청하고, DB가 해당 쿼리를 받아 동작을 수행한다. 자세한 내용은 DB 벤더에서 제공된 매뉴얼을 참고하여 기능의 지원 여부를 따져보도록 한다. 또한 DBA 위임 데이터 소스는 실제 DBA 대상이 되는 데이터 소스와 같은 DB에 설정이 되어있어야 한다.
경고
이 기능은 예전 JDBC 스펙에서는 select query 등이 너무 오래 걸릴 때 그것을 끊어줄 방법이 없어서 사용한 방법인데 현재는 statement query timeout 등이 있으므로 이 기능을 사용할 필요가 없다. 특히 세션 킬 이후에 rollback이 되지 않기 때문에 XA 데이터 소스에는 설정해서 사용하면 안 되고 트랜잭션 타임아웃을 적절하게 세팅해서 사용하길 권장한다.
앞서 보았듯이, JEUS 차원에서 RAC 인스턴스 간의 장애 복구(failover and failback)를 제공하기 위해서 클러스터 데이터 소스를 설정할 수 있다. 이 설정은 <resource> <data-source> <cluster-ds> 태그를 사용해서 설정한다. 이 태그가 포함하는 설정은 다음과 같다.
export-name: 클러스터 데이터 소스의 JNDI 이름.
use-failback: true 혹은 false 값으로, failback 기능을 사용할 것인지 선택하는 옵션이다. 이전에는 failover만을 지원했기 때문에 호환성을 위해서 만든 옵션이다. 기본값은 true.
is-pre-conn: true 혹은 false 값으로, true이면 클러스터 데이터 소스에 참여하는 모든 데이터 소스의 커넥션을 미리 연결시켜 놓는다. 이렇게 될 경우 failover 성능은 향상될 수 있으나, 반대로 시스템 리소스가 많이 이용된다는 단점이 있다. 기본값은 false.
data-source: 클러스터 데이터 소스에 포함되는 데이터 소스의 export name을 적어준다.
리스트의 첫번째가 주 RAC 인스턴스가 되고, 이것이 다운되면 다음 인스턴스가 선택된다. 다음의 설정 예제의 경우 처음에는 RAC1 데이터 소스에서 커넥션을 가져오고 커넥션을 가져오는 도중 Exception이 발생할 경우(Wait timeout인 경우는 제외) RAC2 데이터 소스를 사용하게 된다.
예 9.10. <<JEUSMain.xml>>
<jeus-system> . . . <resource> <data-source> <database> <export-name>RAC1</export-name> . . . </database> <database> <export-name>RAC2</export-name> . . . </database> . . . <cluster-ds> <export-name>RAC</export-name> <data-source>RAC1</data-source> <data-source>RAC2</data-source> </cluster-ds> </data-source> </resource> </jeus-system>
JEUS 6 fix#3부터는 자동으로 Failback을 해주는 기능이 포함된다. 클러스터 데이터 소스에서 Failback을 하기 위해서는, 참여하는 모든 데이터 소스에 반드시 check-query 및 check-query-period를 설정해줘야 한다. 예제에서 주 데이터 소스는 RAC1이고 백업 데이터 소스가 RAC2이다. 만약 RAC1이 죽었거나 문제가 생겼을 경우에는, 어플리케이션이 커넥션을 가져갈 때(getConnection) check-query에 의해 이를 감지하게 되며 Failover를 통해서 RAC2 데이터 소스를 사용하게 된다. 그리고 RAC1 데이터 소스가 살아났는지 check-query-period에 설정한 주기마다 체크한다. RAC1 데이터 소스이 살아난 후부터는 모든 커넥션 요청이 자동으로 RAC1 데이터 소스로 가게 된다.
만약 예전처럼(JEUS 6 fix#2 이하) Failover만을 사용해야 할 경우에는 <cluster-ds><use-failback>을 false로 할 수 있다. 그리고 수동으로 Failback 명령을 내릴 수 있는데 이에 대해서는 JEUS Reference Book에서 controlcds 명령을 참조하기 바란다.
그리고 check-query 실패시 풀에 있는 커넥션에 대한 destroy 정책을 AllConnections로 설정하기 바란다. 그렇지 않으면 만약 RAC1이 죽고 RAC2로 Failover 되었을 경우 RAC1에는 이미 끊어진 커넥션들이 풀에 계속 남아있게 된다. 물론 주기적인 check-query에 의해서 정리가 되지만 Failover가 이뤄지는 시점에 끊어진 커넥션들이 정리가 되는 것이 좀더 바람직하기 때문이다.
예 9.11. <<JEUSMain.xml>>
<jeus-system> . . . <resource> <data-source> <database> <export-name>RAC1</export-name> . . . <check-query>select 1 from dual</check-query> <check-query-period>300000</check-query-period> <destroy-policy-on-check-query> AllConnections </destroy-policy-on-check-query> </database> <database> <export-name>RAC2</export-name> . . . <check-query>select 1 from dual</check-query> <check-query-period>300000</check-query-period> <destroy-policy-on-check-query> AllConnections </destroy-policy-on-check-query> </database> . . . <cluster-ds> <export-name>RAC</export-name> <data-source>RAC1</data-source> <data-source>RAC2</data-source> </cluster-ds> </data-source> </resource> </jeus-system>
사용자는 DB 커넥션 풀을 실시간으로 제어하기 위해서 콘솔 툴인 ‘jeusadmin’이나 WebAdmin를 사용할 수 있다. WebAdmin에 대한 설명은 JEUS WebAdmin 안내서를 참조 하기 바란다.
따르는 코드는 JEUSMain.xml
에 구성된 데이터 소스를 사용하여 어떻게 커넥션을 얻는지를 보여주고 있다.
Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("ds1"); Connection con = ds.getConnection(); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select * from test"); ... ... con.close();
경고
커넥션을 사용한 후에는 반드시 닫아야 한다.
JEUS는 트랜잭션 프로그래밍을 위한 표준 단계를 정의한다. UserTransaction를 사용하는 모든 어플리케이션은 표준단계들을 따라야 한다.
트랜잭션을 시작한다.
DB 커넥션을 얻어 온다.
트랜잭션의 나머지를 코딩한다.
경고
만약 여러분이 다른 트랜잭션 처리를 원한다면 반드시 새로운 커넥션을 다시 얻어야 한다.
JEUS에서는 어플리케이션에 커넥션을 넘겨줄 때 커넥션의 상태 관리를 위하여 래퍼를 사용하게 된다. 그러나 오라클의 CLOB, XMLType 등은 해당 JDBC 드라이버의 커넥션 인스턴스를 직접적으로 필요로 하기 때문에 드라이버 내부적으로 클래스 캐스팅을 하다가 ClassCastException이 발행하게 된다. 이는 JEUS 뿐만 아니라 대부분의 WAS 제품 상에서 발생할 수 있는 문제이며, 어플리케이션에서는 WAS에서 넘어오는 커넥션이 드라이버의 커넥션 인스턴스일 것이라고 가정하고 구현해서는 안 된다. 하지만 이를 우회적으로 피할 수 있는 방법이 있다. 어플리케이션에서는 다음과 같은 방법으로 드라이버의 커넥션 인스턴스를 얻을 수 있다.
import java.sql.Connection; import java.sql.DatabaseMetaData; .... Connection conn = datasource.getConnection(); DatabaseMetaData metaData = conn.getMetaData(); OracleConnection oraConn = (OracleConnection)metaData.getConnection();
Standalone 클라이언트에서 clientcontainer.jar 또는 jclient.jar를 이용해서 JEUS에 등록된 데이터 소스 정보를 이용할 수 있다. 클라이언트에서 JEUSMain.xml에 등록한 JNDI 이름으로 데이터 소스를 찾으면 해당 정보를 클라이언트로 가져와서 로컬 JVM 상에 커넥션 풀을 구성하도록 되어 있다.
경고
JEUS 서버에 구성된 커넥션 풀에서 커넥션을 가져와서 쓰는 것이 아니다. 만약 그렇게 하고 싶다면 커넥션을 사용하는 로직을 EJB로 구현해서 JEUS에 디플로이하고 EJB를 찾아서(lookup) 써야 한다.
커넥션 공유(connection sharing)은 같은 트랜잭션 내에서는 하나의 리소스에 대해 항상 하나의 커넥션만 사용하는 것을 보장한다는 개념으로, JCA 스펙에 언급되어 있다. JDBC 커넥션 풀 입장에서의 리소스는 대부분 데이터베이스(또는 데이터 소스)이다. JEUS의 JDBC 커넥션 풀에서는 특별한 설정이 필요없이 기본적으로 XA 데이터 소스에 대해 커넥션 공유를 제공하며 로컬 XA 데이터 소스도 마찬가지로 제공한다. ProFrame과 같은 어플리케이션 프레임워크에서는 같은 트랜잭션 내에서 하나의 데이터 소스에 여러 번 getConnection과 close를 반복하게 되는 경우가 많으므로 커넥션 공유를 사용하는 것이 바람직하다. 그렇지 않으면 하나의 DB에 대해 여러 개의 물리적 커넥션이 트랜잭션에 참여하기 때문에 DB 입장에서는 불필요하게 많은 트랜잭션 락을 필요로 하게 되고 트랜잭션 성능에 영향을 미칠 수 있다.
만약 커넥션 공유를 사용하고 싶지 않을 때는 XA 데이터 소스를 사용하는 Java EE 컴포넌트 설정(web.xml, ejb-jar.xml 등)에 아래와 같이 설정할 수 있다. 참고로<res-ref-name>에는 보통 JEUSMain.xml에 설정된 데이터 소스의 JNDI 이름을 적어주면 된다.
<resource-ref> <res-ref-name>jdbc/xads</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-sharing-scope>Unshareable</res-sharing-scope> </resource-ref>
참고로 아무 설정을 하지 않으면 기본적으로 <res-sharing-scope>가 Shareable 이기 때문에 Servlet이나 EJB 컴포넌트에서 항상 커넥션 공유를 사용하게 되는 것이다.
단, 로컬 XA 데이터 소스는 항상 커넥션 공유를 해야 하기 때문에 Unshareable로 설정하면 에러(java.sql.SQLException)를 발생하도록 되어 있다.
이번 장에서는 JEUS에 구현된 JDBC 커넥션 풀의 설명 및 관련 설정에 대해 알아보았다.
사용자는 JEUS가 제공하는 DB 커넥션 풀과 JDBC 드라이버를 이용하여 DB에 접속할 수 있다. 그러기 위해서는 각 DB 벤더별로 알맞은 설정을 해주어야 한다. 또한 jeusadmin이나 WebAdmin를 이용하여 현재 커넥션 풀의 상태를 보거나 제어할 수 있다.
'PROGRAMMING > Database' 카테고리의 다른 글
테이블 명세서 추출 쿼리 (0) | 2017.10.03 |
---|---|
MSSQL 실행계획이 없다는 에러 (0) | 2017.10.03 |
charindex를 이용한 revers 추출 예제 (0) | 2015.06.05 |
행을 열로 바꿔주는 쿼리 (0) | 2015.06.03 |
idendity on / off 하기 (0) | 2015.05.10 |
댓글