자바 시스템에서 리소스, 외부 트랜잭션, DB 연결 모니터링

CPU와 메모리 등의 시스템 자원을 모니터링하는 방법을 설명한다. 그리고 외부 애플리케이션과의 연동을 모니터링하는 외부 트랜잭션과 JDBC 모니터링을 설명한다.

CPU 모니터링

제니퍼 에이전트를 통해서 자바 애플리케이션이 동작하는 하드웨어의 시스템 CPU 사용률과 해당 자바 애플리케이션이 사용하는 자바 프로세스 CPU 사용률을 모니터링할 수 있다. 그리고 특정 트랜잭션의 처리 과정에서 사용한 트랜잭션 CPU 점유 시간도 모니터링할 수 있다.

또한 제니퍼 에이전트의 설치와 상관없이 WMOND를 통해서 임의의 하드웨어의 CPU 개수당 CPU 사용률도 모니터링할 수 있다.

시스템 CPU 사용률과 자바 프로세스 CPU 사용률

시스템 CPU 사용률은 시스템의 전체 CPU 사용률을 의미하고, 자바 프로세스 CPU 사용률은 제니퍼 에이전트를 설치한 자바 애플리케이션이 사용하는 CPU 사용률을 의미한다.

시스템 CPU 사용률과 자바 프로세스 CPU 사용률은 제니퍼 에이전트가 수집하는 일반 성능 데이터로 실시간 값을 이퀄라이저 차트와 런타임 라인 차트를 통해서 확인할 수 있다.

실시간 시스템 CPU 사용률 차트

그런데 CPU 사용률은 CPU 사용 영역별로 구분되고, 이퀄라이저 차트는 CPU 사용률을 영역별로 색상을 구분하여 표시한다. 다음은 CPU 사용 영역에 대한 설명이다.

CPU 사용 영역

CPU 사용 영역

설명

WAIT

I/O 대기와 관련한 CPU 사용률을 의미한다.

NICE

NICE 우선 순위가 부여된 사용자 수준(애플리케이션)에서 사용하는 CPU 사용률을 의미한다.

USER

사용자 수준(애플리케이션)에서 사용하는 CPU 사용률을 의미한다.

SYS

시스템 수준(커널)에서 사용하는 CPU 사용률을 의미한다.

이퀄라이저 차트는 자바 프로세스 CPU 사용률과 WMOND가 수집한 CPU 개수당 CPU 사용률도 CPU 사용 영역별로 색상을 구분하여 표시한다.

또한 시스템 CPU 사용률과 자바 프로세스 CPU 사용률에 대한 5분 평균 값은 PERF_X_01~31 테이블의 SYS_CPU 칼럼과 JVM_CPU 칼럼에 저장되고, 특정 날짜의 CPU 사용률 변화 추이는 라인 차트를 통해서 확인할 수 있다.

금일 시스템 CPU 사용률

트랜잭션 CPU Time

트랜잭션 CPU 점유 시간은 트랜잭션이 수행되는 과정에서 사용한 CPU 사용 시간을 의미한다. 이는 애플리케이션 처리 현황 통계 데이터와 X-View 프로파일 데이터로 수집된다.

그리고 tpmC를 통해서 트랜잭션 CPU 점유 시간을 하드웨어 별로 객관적으로 비교할 수 있다. tmpC는 TPC(Transaction Processing Performance Council, http://www.tpc.org)의 TPC-C 벤치마크 시나리오에 대한 1분당 최대 처리 건수를 나타내는 수치로써, 하드웨어(주로 CPU)의 성능을 측정하는 대표적인 방법이다.

하드웨어의 CPU 처리 용량이 다르기 때문에 트랜잭션 CPU 점유 시간만으로는 해당 트랜잭션이 성능에 미치는 영향을 객관적으로 파악할 수 없다. 예를 들어, CPU 처리 용량이 큰 하드웨어에서 처리된 트랜잭션 A의 CPU 점유 시간이 CPU 처리 용량이 작은 하드웨어에서 처리된 트랜잭션 B의 CPU 점유 시간보다 작은 경우에도 성능에 미치는 절대적인 영향은 트랜잭션 A가 더 높을 수도 있다. 즉 트랜잭션 A를 CPU 처리 용량이 작은 하드웨어에 서 처리하면 트랜잭션 B보다 CPU 점유 시간이 클 수 있다는 것이다.

따라서 tpmC로 하드웨어의 CPU 처리 용량 변수를 통제한 상태에서 트랜잭션이 성능에 미치는 영향을 파악해야 한다.

이를 위해서 제니퍼 에이전트의 approximate_tpmc_on_this_system 옵션으로 제니퍼 에이전트를 설치한 해당 하드웨어의 tpmC 값을 설정하도록 한다.

approximate_tpmc_on_this_system = 30000

WMOND를 통한 CPU 개수당 CPU 사용률

WMOND는 임의의 하드웨어의 CPU 개수당 CPU 사용률 데이터를 수집하는 모듈이다. 이 모듈은 C 언어로 개발되어 있고 사용 방법이 간단한다. 제니퍼 에이전트 설치와는 상관이 없기 때문에 웹 서버나 데이터베이스 서버가 운영중인 하드웨어의 CPU 사용률을 모니터링하는데 적합하다.

WMOND는 하드웨어에 설치된 CPU 개수당 CPU 사용률을 수집하기 때문에 제니퍼 에이전트를 설치한 하드웨어에서도 WMOND를 사용할 필요가 있을 수 있다.

CPU 개수당 CPU 사용률은 이퀄라이저 차트를 통해서 확인할 수 있다. 다음 그림을 보면 WMOND로 모니터링하는 하드웨어에 4개의 CPU가 설치되어 있음을 확인할 수 있다.

CPU 개수당 CPU 사용률

또한 CPU 개수당 CPU 사용률은 PERF_HOST 테이블에 CPU 사용 영역별로 구분되어서 저장된다. 기본 저장 주기는 5분이고, 제니퍼 서버의 perf_host_update_interval 옵션으로 변경할 수 있다. 단위는 밀리 세컨드이다.

perf_host_update_interval = 300000

60000(1분)에서 300000(5분) 사이의 값만 가능하다.

WMOND의 설치와 실행

WMOND는 C 언어로 개발되었기 때문에 운영 체계에 적합한 것을 설치해야 한다. 현재 마이크로소프트 윈도우즈, 선 솔라리스, HP HP-UX, IBM AIX 등과 다양한 리눅스 운영 체계에서 WMOND를 사용할 수 있다.

WMOND를 사용하려면, 우선 JENNIFER_HOME/agent/wmond 디렉토리에 있는 파일 중에서 운영 체계에 적합한 wmond 파일을 CPU 사용률을 모니터링할 하드웨어에 복사한다.

그리고 운영 체계가 유닉스나 리눅스인 경우에는 복사한 wmond 파일에 실행 권한을 부여한다. 예를 들어, wmond 파일을 /usr/bin 디렉토리에 복사하였으면 다음처럼 실행 권한을 부여한다.

chmod 755 /usr/bin/wmond

WMOND를 실행하는 방법은 다음과 같다.

wmond [제니퍼 서버 IP] [포트 번호] [WMOND 아이디]

다음은 WMOND실행의 예이다.

nohup wmond 127.0.0.1 6902 DB1 &

유닉스나 리눅스에서 시스템이 시작할 때 WMOND를 자동으로 구동시키거나, WMOND가 정지되었을 때 자동으로 재시작하려면 /etc/inittab 파일에 다음 내용을 기술한다.

wmond:2:respawn:/usr/bin/wmond 127.0.0.1 6902 DB1 > /dev/null 2>&1

솔라리스에서는 /etc/rc2.d/S97wmond 파일을 추가한다. 파일 이름은 고유해야 한다.

/usr/bin/wmond 127.0.0.1 6902 DB1 > /dev/null 2>&1 &

마지막의 [&]를 생략해서는 안된다.

WMOND의 정지와 경보

제니퍼 서버의 agent_death_detection_time 옵션으로 설정한 기간동안 WMOND로부터 CPU 사용률 데이터가 전송되지 않으면, 제니퍼 서버는 WMOND를 설치한 시스템(머신)이 정지된 것으로 간주하고 ERROR_SYSTEM_DOWN 경보를 발령한다. 기본 값은 8000이고 단위는 밀리 세컨드이다.

CPU 모니터링과 경보 발령

시스템 CPU 사용률과 자바 프로세스 CPU 사용률이 임계치를 초과하면 제니퍼 서버는 경보를 발령한다. 자세한 사항은 경보와 예외 모니터링을 참조한다.

CPU 모니터링과 관련한 주의 사항

실제 CPU 사용률과 제니퍼가 수집한 CPU 사용률의 차이가 큰 경우

제니퍼는 CPU 모니터링을 위해서 운영 체계가 제공하는 커널 API를 사용한다. 그런데 간혹 IBM AIX에서 CPU가 멀티코어인 경우에, 실제 CPU 사용률과 제니퍼가 수집한 CPU 사용률이 50% 이상의 차이를 보이는 현상이 나타날 수 있다. 이 경우에는 tech@jennifersoft.com 메일로 기술 지원을 요청하도록 한다.

제니퍼 에이전트 Native 모듈 테스트

제니퍼 에이전트의 Native 모듈이 CPU 사용률과 메모리 사용량 등의 데이터를 수집한다. 제니퍼 클라이언트에 CPU 사용률과 메모리 사용량 등이 나타나지 않으면 Native 모듈이 올바르게 설치되지 않았음을 의미한다.

Native 모듈을 올바르게 설치했다면 제니퍼 에이전트의 로그 파일과 자바 애플리케이션 표준 출력 파일에 다음 메시지가 기록된다.

libjennifer20.so(sl) shared library loaded successfully.

만약 로그 파일에 앞의 메시지가 기록되지 않으면, Native 모듈이 잘못 설정되었거나 해당 시스템에 맞지 않는 Native 모듈을 사용하고 있음을 의미한다. 이런 경우에는 시스템에 맞는 Native 모듈을 사용해야 한다.

시스템에 적합한 Native 모듈을 찾기 위해서 제니퍼 에이전트와는 별도로 Native 모듈을 테스트할 수 있는 방법을 제공한다.

jennifer@jennifer1:~/agent/jni/linux$ ./test.sh
get_nprocs_conf=2, get_nprocs(ncpu)=2, ncpu=2, _SC_CLK_TCK=100
Jennifer TimeZoneOffset:32400000
Jennifer4.0.0.2(2008-09-22) libjennifer20.so(sl) shared library loaded
successfully.
  USER SYS NICE WAIT IDLE USER SYS NICE WAIT IDLE
get_pid=30449
get_ppid=30447
get_ncpu=2
get_cpucluck=100
get_native_thread_id=-1209939056
get_current_thread_cpu_time=0
get_thread_cpu_time=0
get_total_mem=1059909632
get_free_mem=16814080
get_jvm_mem=217346048
  0.0 0.0 0.0 0.0 100.0 0.0 0.0 0.0 0.0 100.0
total_cpu: 19690350,2889545,42167,0,618107374,
jvm_total_cpu: 7,1,2021553758,0,-1,
get_current_thread_cpu_time=20
  0.0 0.0 0.0 0.0 100.0 0.0 0.0 0.0 0.0 100.0
total_cpu: 19690367,2889546,42167,0,618107561,
jvm_total_cpu: 8,1,2021553858,0,-1,
get_current_thread_cpu_time=30
  4.5 0.5 0.0 0.0 95.0 0.0 0.0 0.0 0.0 100.0
total_cpu: 19690376,2889547,42167,0,618107752,
jvm_total_cpu: 8,1,2021553958,0,-1,
get_current_thread_cpu_time=40

모든 파일을 테스트했는데도 정상적으로 동작하지 않는다면 Native 모듈을 해당 시스템에 맞게 재컴파일해야 한다. 이 경우에는 tech@jennifersoft.com 메일로 기술 지원을 요청하도록 한다.

메모리 모니터링

시스템 메모리 사용량과 자바 프로세스 메모리 사용량

시스템 메모리 사용량은 시스템의 메모리 사용량을 의미하고, 자바 프로세스 메모리 사용량은 제니퍼 에이전트를 설치한 자바 애플리케이션이 사용하는 메모리 사용량을 의미한다. 단위는 MB이다.

시스템 메모리 사용량과 자바 프로세스 메모리 사용량은 제니퍼 에이전트가 수집하는 일반 성능 데이터로 실시간 30초 평균 값을 런타임 라인 차트를 통해서 확인할 수 있다.

또한 시스템 메모리 사용량과 자바 프로세스 메모리 사용량에 대한 5분 평균 값은 PERF_X_01~31 테이블의 SYS_MEM_USED 칼럼과 JVM_NAT_MEM 칼럼에 저장되고, 특정 날짜의 메모리 사용량 변화 추이는 라인 차트를 통해서 확인할 수 있다.

금일 시스템 메모리 사용량

시스템 CPU 사용률과 동일하게 시스템 메모리 사용량과 자바 프로세스 메모리 사용량을 수집하려면 Native 모듈이 올바르게 설치되어 있어야 한다.

자바 힙 메모리 전체와 자바 힙 메모리 사용량

자바 힙 메모리 전체는 다음 자바 코드를 통해서 얻을 수 있는 값을 의미한다.

Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();

그리고 자바 힙 메모리 사용량은 다음 자바 코드를 통해서 얻을 수 있는 값을 의미한다.

Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();

앞의 코드를 통해서 수집한 값의 단위는 바이트이지만, 제니퍼는 이를 MB로 전환해서 사용한다.

자바 힙 메모리 전체와 자바 힙 메모리 사용량은 제니퍼 에이전트가 수집하는 일반 성능 데이터로 실시간 30초 평균 값을 런타임 라인 차트를 통해서 확인할 수 있다.

또한 자바 힙 메모리 전체와 자바 힙 메모리 사용량에 대한 5분 평균 값은 PERF_X_01~31 테이블의 HEAP_TOTAL 칼럼과 HEAP_USED 칼럼에 저장되고, 특정 날짜의 자바 힙 메모리 사용량 변화 추이는 라인 차트를 통해서 확인할 수 있다.

자바 힙 메모리 사용률은 자바 힙 메모리 전체에 대한 자바 힙 메모리 사용량의 비율을 의미한다. 단위는 퍼센트이다. 그런데 자바 힙 메모리 전체가 거의 변하지 않기 때문에 라인 차트와 런타임 라인 차트에서 자바 힙 메모리 사용률과 자바 힙 메모리 사용량을 나타내는 선의 형태는 거의 유사하다.

자바 힙 메모리 전체, 자바 힙 메모리 사용량, 자바 힙 메모리 사용률 등을 나타내는 런타임 라인 차트에서는 특정 제니퍼 에이전트에 대한 가비지 콜렉션을 수행할 수 있다.

자바 힙 메모리 가비지 콜렉션

단, 사용자가 속한 그룹이 gc 권한을 가지고 있어야 컨텍스트 메뉴가 나타난다.

메모리 모니터링과 경보 발령

제니퍼 서버는 임의의 기간 동안의 자바 힙 메모리 사용률이 임계치를 초과하면 WARNING_JVM_HEAP_MEM_HIGH 경보를 발령한다.

메모리-콜렉션 모니터링

자바 애플리케이션에서 자바 힙 메모리 누수는 성능 저하를 유발하는 대표적인 현상이다. 제니퍼는 이 문제를 해결하기 위한 도구로 메모리-콜렉션 모니터링을 제공한다.

메모리-콜렉션 모니터링에 대한 이해

메모리-콜렉션 모니터링은 java.util.Collection과 java.util.Map 인터페이스를 구현한 자바 콜렉션 객체 중에서 임계치 이상의 객체를 담고 있는 것을 추적하고 스텍트레이스를 기록하는 기능이다. 임계치는 제니퍼 에이전트의 lwst_collection_minimum_monitoring_size 옵션으로 설정한다. 기본 값은 3000이다.

예를 들어, 특정 트랜잭션에 의해 다음 자바 코드가 실행된다고 가정한다.

java.util.List books = new java.util.ArrayList();
for (int i = 0; i < 4000; i++) {
      books.add(new Book(i));
}

앞의 코드에 의하면 books 자바 콜렉션 객체에 4000개의 객체가 담겨지기 때문에, 제니퍼 에이전트는 books 자바 콜렉션 객체를 추적한다.

사용자는 이렇게 추적된 콜렉션 객체 목록을 [장애 진단 | 메모리-콜렉션] 메뉴에서 확인할 수 있다.

메모리-콜렉션 모니터링 활성화

기본적으로 메모리-콜렉션 모니터링은 동작하지 않는다. 이 기능을 사용하려면 build_collection 패치 옵션을 true로 설정해서 LWST 빌드를 다시 해야 한다. 자세한 사항은 LWST빌드와 설치를 참조한다.

기본으로 메모리-콜렉션 모니터링을 사용하지 않는 이유는, 다른 모니터링 기능에 비해서 이 모니터링이 야기하는 성능 저하가 클 수 있기 때문이다. 따라서 자바 힙 메모리 누수와 관련한 문제를 해결할 때만 사용하고, 그 이후에는 build_collection 패치 옵션을 false로 설정해서 LWST 빌드를 다시 하도록 한다.

메모리-콜렉션 모니터링 활용

모든 메모리-콜렉션 모니터링은 [장애 진단 | 메모리-콜렉션] 메뉴에서 활용한다. 이 메뉴에서 자바 콜렉션 객체에 담겨 있는 객체 개수의 변화량을 확인하고, 자바 콜렉션 객체를 사용하는 트랜잭션의 스택트레이스를 추출할 수 있다.

먼저 제니퍼 에이전트 선택 영역에서 특정 제니퍼 에이전트를 선택하면, 제니퍼 에이전트 의 lwst_collection_minimum_monitoring_size 옵션으로 설정한 크기보다 많은 객체를 담고 있는 자바 콜렉션 객체 목록이 나타난다. 기본 값은 3000이다.

lwst_collection_minimum_monitoring_size = 3000

그리고 lwst_collection_minimum_monitoring_size 옵션을 직접 수정하지 않고, [콜렉션 최소 모니터링 크기] 검색 조건에서 임계치를 임시적으로 수정할 수 있다. [콜렉션 최소 모니터링 크기] 검색 조건에 임의의 값을 입력한 후에 [검색] 버튼을 클릭하면 된다.

메모리-콜렉션 모니터링 화면

다음은 메모리-콜렉션 모니터링 항목에 대한 설명이다.

메모리 - 콜렉션 모니터링 항목

항목

설명

번호

일련 번호

생성 시간

자바 콜렉션 객체의 생성 시간

전체

자바 콜렉션 객체에 담겨 있는 객체의 개수로, java.util.Collection이나 java.util.Map 인터페이스의 size 메소드가 반환하는 값이다.

델타

자바 콜렉션 객체에 담겨 있는 객체 개수의 변화량

콜렉션 이름

자바 콜렉션 클래스의 이름

해시 코드

자바 콜렉션 객체의 해시 코드

스택트레이스

해시 코드

애플리케이션

자바 콜렉션 객체를 사용하는 트랜잭션의 애플리케이션 이름

특정 자바 콜렉션 객체에 담겨 있는 객체 개수의 변화량을 확인하려면 다음과 같이 한다.

그리고 자바 콜렉션 객체를 사용하는 트랜잭션의 스택트레이스를 추출할 수 있다. 하나의 자바 콜렉션 객체에 대한 스택트레이스를 추출하려면 다음 사항을 참고한다.

특정 트랜잭션에 의해서 스택트레이스가 추출되면, 이후에 다른 트랜잭션이 해당 자바 콜렉션 객체를 사용해도 스택트레이스가 다시 추출되지는 않는다. 따라서 새로운 스택트레이스를 기록하려면 [다시 받기] 링크를 클릭한다.

모든 자바 콜렉션 객체에 대한 스택트레이스를 추출하려면 다음 사항을 참고한다.

추출된 스택트레이스는 제니퍼 에이전트를 설치한 자바 애플리케이션의 자바 힙 메모리에 상주한다. 따라서 분석을 완료한 후에는 모든 스택트레이스를 삭제하는 것을 권장한다.

그런데 사용자가 조작을 하지 않아도, 자바 콜렉션 객체에 담겨 있는 객체의 개수가 임계치를 초과하면 자동으로 스택트레이스를 추출한다. 임계치는 제니퍼 에이전트의 lwst_collection_auto_stacktrace_size 옵션으로 설정한다. 기본 값은 100000이다.

lwst_collection_auto_stacktrace_size = 100000

그리고 lwst_collection_auto_stacktrace_size 옵션을 직접 수정하지 않고, [콜렉션 자동 스택트레이스 크기] 검색 조건에서 임계치를 임시적으로 수정할 수 있다. [콜렉션 자동 스택트레이스 크기] 검색 조건에 임의의 값을 입력한 후에 [검색] 버튼을 클릭하면 된다.

파일/소켓 모니터링

자바 애플리케이션에서 IO는 성능 저하를 유발하는 원인이 될 수 있다. 제니퍼는 이 문제를 해결하기 위한 도구의 하나로 파일/소켓 모니터링을 제공한다.

파일 모니터링

[장애 진단 | 파일/소켓 ] 메뉴를 통해서 액티브한 자바 애플리케이션의 파일 IO 현황을 모니터링할 수 있다. 파일 IO 현황을 모니터링하려면 build_file 패치 옵션을 true로 설정해서 LWST 빌드를 해야 한다. 기본이 true이다. 자세한 사항은 LWST빌트와 설치를 참조한다.

파일 IO 목록

다음은 파일 IO 목록 항목에 대한 설명이다.

파일 IO 목록 항목

칼럼 이름

설명

번호

일련 번호

마지막 수정 시간

파일의 마지막 수정 시간

파일명

파일의 절대 경로

크기

파일의 크기로 단위는 바이트이다.

모드

작업 상태를 나타내는 코드로, READ는 읽기를, WRITE는 쓰기를 의미한다.

동시 접근 수

파일의 동시 접근 수

파일 IO 모니터링을 통해서 자바 애플리케이션이 불필요한 파일 IO를 수행하고 있는지를 확인 할 수 있다.

파일 IO 모니터링은 java.io.FileInputStream과 java.io.FileOutputStream 클래스로 접근한 파일만을 감지한다. 따라서 JNI를 이용하여 C 프로그램으로 파일을 열면, 해당 파일은 IO 목록에 나타나지 않는다.

소켓 모니터링

소케 모니터링은 자바 애플리케이션의 java.net.Socket 클래스를 통한 소켓 IO 현황을 제공한다. 소켓 IO 현황을 모니터링하려면 build_socket 패치 옵션을 true로 설정해서 LWST 빌드를 해야 한다. 기본이 true이다. 자세한 사항은 LWST빌드와 설치를 참조한다.

기본적으로 특정 트랜잭션이 수행한 소켓 IO 작업 정보가 X-View 프로파일 데이터에 나타난다.

X-View 프로파일 - 소켓

그리고 제니퍼 에이전트의 socket_simple_trace 옵션을 false로 하면 [장애 진단 | 파일/소켓 ] 메뉴를 통해서 액티브한 자바 애플리케이션의 소켓 IO 현황을 모니터링할 수 있다. 기본 값은 true이다.

socket_simple_trace = false

소켓 IO 목록

다음은 소켓 IO 목록 항목에 대한 설명이다.

소켓 IO 목록 항목

항목

설명

번호

일련 번호

소켓이 열린 시간

소켓이 열린 시간

스택트레이스

소켓을 사용하는 트랜잭션에서 추출한 스택트레이스

로컬 포트

소켓의 로컬 포트 번호

원격 IP 주소

소켓의 원격 서버 IP 주소

원격 포트

소켓의 원격 서버 포트 번호

읽기(활성/전체)

소켓을 통해서 읽은 데이터의 크기를 표시한다. 단위는 바이트이다. 활성은 최근에 읽은 데이터의 크기를, 전체는 소켓을 통해서 읽은 누적된 모든 데이터의 크기를 의미한다.

쓰기(활성/전체)

소켓을 통해서 쓴 데이터의 크기를 표시한다. 단위는 바이트이다. 활성은 최근에 쓴 데이터의 크기를, 전체는 소켓을 통해서 쓴 누적된 모든 데이터의 크기를 의미한다.

소켓 IO 목록에 표시된 소켓 IO 현황이 많은 경우에는 [로컬 포트], [원격 IP 주소] 그리고 [원격 포트] 등의 조건으로 검색을 할 수 있다. [검색] 버튼 옆의 [초기화] 버튼을 클릭하면 검색 조건이 초기화된다.

그리고 소켓을 사용하는 트랜잭션의 스택트레이스를 추출할 수 있다. 하나의 소켓에 대한 스택트레이스를 추출하려면 다음 사항을 참고한다.

특정 트랜잭션에 의해서 스택트레이스가 추출되면, 이후에 다른 트랜잭션이 해당 소켓을 사용해도 스택트레이스가 다시 추출되지는 않는다. 따라서 새로운 스택트레이스를 기록하려면 [다시 받기] 링크를 클릭한다.

소켓 모니터링을 제니퍼 에이전트의 JDBC 커넥션 추적용 설정정보를 획득하기 위한 수단으로도 사용할 수 있다. JDBC 모니터링을 하려면 java.sql.Connection 객체를 제공하는 클래스를 찾아야 한다. 그런데 이를 명확하게 알 수 없다면 데이터베이스 서버의 원격 IP 주소와 원격 포트 번호를 이용해서 데이터베이스 연결을 담당하는 소켓을 찾아내서, 해당 소켓을 사용하는 트랜잭션의 스택트레이스를 추출하면 어느 클래스에서 java.sql.Connection 객체를 반환하는지를 파악할 수 있다.

사용자가 조작을 하지 않아도, 제니퍼 에이전트의 lwst_trace_remote_port 옵션으로 설정한 원격 포트에 대해서는 자동으로 스택트레이스를 추출한다. 예를 들어, 외부 EAI 솔루션의 2300 포트와 소켓 통신을 하는 자바 애플리케이션의 소켓에 대한 스택트레이스를 자동으로 기록하려면 다음과 같이 설정한다.

lwst_trace_remote_port = 2300

이 옵션으로 특정 원격 포트 번호를 설정했다면 socket_simple_trace 옵션을 true로 설정해도 해당 원격 포트를 사용하는 소켓에 대해서는 스택트레이스가 추출된다.

제니퍼 에이전트의 lwst_trace_remote_port 옵션을 변경해도 자바 애플리케이션을 재시작할 필요는 없다. 그러나 이 옵션으로 설정한 원격 포트 번호를 사용하는 소켓 객체가 이미 만들어져서 풀링된 상태라면 해당 소켓 객체에 대한 스택트레이스를 추출할 수는 없다. 따라서 이 경우에는 자바 애플리케이션을 재시작해야 한다.

라이브 오브젝트 모니터링

라이브 오브젝트 모니터링은 특정 클래스의 객체 개수를 파악하는 기능이다.

라이브 오브젝트 모니터링 설정

불필요한 성능 저하를 방지하기 위해서 모든 클래스의 객체 개수를 파악하지는 않고, 제니퍼 에이전트의 liveobject로 시작하는 옵션을 통해서 설정한 클래스들에 대해서만 라이브 오브젝트 모니터링을 한다. 수정한 liveobject로 시작하는 옵션을 반영하려면 제니퍼 에이전트를 설치한 자바 애플리케이션을 재시작해야 한다.

예를 들어, java.util.ArrayList 클래스의 객체 개수를 파악하려면 다음과 같이 설정한다.

liveobject_class = java.util.ArrayList

두 개 이상의 클래스는 세미콜론[;]을 구분자로 구분한다. liveobject로 시작하는 모든 옵션도 마찬가지 이다. 따라서 java.util.HashMap 클래스의 객체 개수도 파악하려면 다음과 같이 설정한다.

liveobject_class = java.util.ArrayList;java.util.HashMap

특정 클래스를 상속한 모든 클래스의 객체 개수를 파악하려면 제니퍼 에이전트의 liveobject_super 옵션을 사용한다. 예를 들어, java.lang.Thread 클래스의 하위 클래스의 객체 개수를 파악하려면 다음과 같이 설정한다.

liveobject_super = java.lang.Thread

그러나 이 경우에 직접적으로 상속받은 클래스의 객체 개수만을 모니터링한다. 예를 들어, A 클래스가 java.lang.Thread 클래스를 상속하고 B 클래스가 A 클래스를 상속했다면, A 클래스의 객체 개수는 모니터링하지만 B 클래스의 객체 개수는 모니터링하지 않는다.

특정 인터페이스를 구현한 모든 클래스의 객체 개수를 파악하려면 제니퍼 에이전트의 liveobject_interface 옵션을 사용한다. 예를 들어, java.lang.Runnable과 java.sql.Connection 인터페이스를 구현한 클래스의 객체 개수를 파악하려면 다음과 같이 설정한다.

liveobject_interface = java.lang.Runnable;java.sql.Connection

특정 패키지로 시작하는 클래스들의 객체 개수만을 모니터링 할 수 있다. 예를들어 java.util 로 시작하는 모든 클래스의 객체 개수를 파악하려면 다음과 같이 설정한다.

liveobject_prefix = java.util

특정 이름으로 끝나는 클래스들의 객체 개수만을 모니터링할 수 있다. 예를 들어, Factory로 끝나는 모든 클래스의 객체 개수를 파악하려면 다음과 같이 설정한다.

liveobject_postfix = Factory

HTTP 세션 모니터링

HTTP 세션은 웹 애플리케이션에서 사용자 상태를 유지하기 위한 방법이다. 자바에서 HTTP 세션은 javax.servlet.http.HttpSession 인터페이스에 대한 구현 클래스로 WAS 마다 상이하다.

Dummy HTTP 세션 추적

제니퍼 에이전트는 page 속성 session을 설정하지 않았거나 true로 설정한 JSP가 수행되는 과정에서 HTTP 세션 객체가 만들어지면 WARNING_DUMMY_HTTPSESSION_CREATED 예외를 발생시킨다. 대부분의 환경에서는 이 예외가 적합하지 않기 때문에 기본적으로 이 예외는 발생하지 않는다. 이 예외를 발생시키려면 제니퍼 에이전트의 enable_dummy_httpsession_trace 옵션을 true로 설정한다. 기본 값은 false이다.

enable_dummy_httpsession_trace = true

HTTP 세션 덤프

[장애 진단 | HTTP 세션] 메뉴에서 WAS의 액티브한 HTTP 세션 객체를 확인할 수 있다. 이를 HTTP 세션 덤프라고 한다. HTTP 세션 덤프를 기록하려면 WAS 별로 별도의 설정을 해야 한다. 이는 WAS 별로 javax.servlet.http.HttpSession 인터페이스를 다르게 구현하기 때문이다.

현재 제니퍼가 HTTP 세션 덤프를 지원하는 WAS는 다음과 같다.

각 WAS 별로 HTTP 세션 덤프를 설정하는 방법은 다음과 같다. 설정을 완료한 후에 WAS를 재시작하여야 한다.

아파치 톰켓 4.x, 5.x에서는 다음과 같이 설정한다.

아파치 톰켓 6.x에서는 jennifer_session.jar 파일을 TOMCAT_HOME/server/lib 디렉토리에 복사한다.

session_class = tomcat

정상적으로 설정된 경우에는 제니퍼 에이전트 로그 파일에 다음과 같은 메시지가 기록된다.

Catalina session-trace ok

오라클 웹로직 8.x, 9,x, 10,x에서는 다음과 같이 설정한다.

session_class = weblogic

정상적으로 설정된 경우에는 제니퍼 에이전트 로그 파일에 다음과 같은 메시지가 기록된다.

Weblogic session-trace ok

IBM 웹스피어 5.x, 6,0에서는 다음과 같이 설정한다.

session_class = websphere

정상적으로 설정된 경우에는 제니퍼 에이전트 로그 파일에 다음과 같은 메시지가 기록된다.

WebSphere session-trace ok

IBM 웹스피어 6.1에서는 다음과 같이 설정한다.

IBM 웹스피어 6.1에서 HTTP 세션 덤프를 하려면 해당 라이브러리 자체를 변경해야 한다. 따라서 반드시 필요한 경우가 아니라면 이 기능을 사용하지 않도록 한다.

session_class = websphere

정상적으로 설정된 경우에는 제니퍼 에이전트 로그 파일에 다음과 같은 메시지가 기록된다.

WebSphere session-trace ok

IBM 웹스피어 7.x에서는 다음과 같이 설정한다.

IBM 웹스피어 7.x에서 HTTP 세션 덤프를 하려면 해당 라이브러리 자체를 변경해야 한다. 따라서 반드시 필요한 경우가 아니라면 이 기능을 사용하지 않도록 한다.

session_class = websphere7

정상적으로 설정된 경우에는 제니퍼 에이전트 로그 파일에 다음과 같은 메시지가 기록된다.

WebSphere session-trace ok

HTTP 세션 덤프를 설정했으면 [장애 진단 | HTTP 세션] 메뉴에서 액티브한 HTTP 세션객체 현황을 확인할 수 있다.

HTTP 세션 목록

다음은 아파치 톰켓을 기준으로 HTTP 세션 덤프의 주요 정보에 대한 설명이다. HTTP 세션 덤프 정보는 WAS에 따라 다르다.

HTTP 세션 주요 정보 ( 톰켓 )

항목

설명

Pathname

WAS가 정지되는 순간에 액티브한 HTTP 세션 객체를 파일에 저장한 후에 WAS가 재시작할 때 해당 파일에서 액티브한 HTTP 세션 객체를 로딩하는 경우가 있다. Pathname은 이 파일의 경로를 의미한다.

상대 경로일 경우에는 자바 javax.servlet.context.tempdir 속성으로 설정한 임시 디렉토리를 기준으로 한다.

ActiveSessions

액티브한 HTTP 세션 객체 개수

ExpiredSessions

만료된 HTTP 세션 객체 개수

RejectedSessions

WAS가 생성할 수 있는 최대 액티브한 HTTP 세션 객체 개수가 초과되어서 HTTP 세션 객체를 생성하지 못한 횟수

CheckInterval

HTTP 세션 객체가 만료되었는지를 체크하는 주기로 단위는 초이다.

MaxActiveSessions

WAS가 생성할 수 있는 최대 액티브한 HTTP 세션 객체 개수로 -1은 제한이 없음을 의미한다.

MaxInactiveInterval

사용자가 요청을 하지 않으면 WAS가 HTTP 세션 객체를 소멸시키는 최대 시간 주기로 단위는 초이다. 0보다 작은 값은 HTTP 세션 객체가 소멸하지 않음을 의미한다.

SessionCounter

WAS가 생성한 HTTP 세션 객체 수

Duplicates

HTTP 세션 아이디가 중복된 개수로, 0 보다 크면 문제가 있다는 의미이다.

Algorithm

HTTP 세션 아이디를 생성하는 알고리즘으로, java.security.MessageDigest 클래스가 지원해야 한다.

Distributable

분산 환경에서 HTTP 세션 객체를 복제하는지에 대한 여부로, true인 경우에는 HTTP 세션 객체에 추가되는 모든 객체는 java.io.Serializable 인터페이스를 구현해야 한다.

개별 애플리케이션의 HTTP 세션 정보

create time, last accessed, max inactive interval 등은 HttpSession 객체의 getCreationTime, getLastAccessedTime, getMaxInactiveInterval 등의 메소드가 반환하는 값을 의미한다.

외부 트랜잭션 모니터링

외부 트랜잭션은 자바 애플리케이션이 다른 애플리케이션과 연동하는 것을 의미한다. 외부 트랜잭션 처리 현황과 응답 시간은 전체 자바 애플리케이션 성능에 중요한 영향을 미친다.

따라서 제니퍼는 외부 트랜잭션을 모니터링하여 외부 트랜잭션 처리 현황 통계 데이터와 외부 트랜잭션 처리 내용을 포함한 X-View 프로파일 데이터 등을 수집한다.

일반적으로 외부 트랜잭션은 데이터베이스, TP(Transaction Processing) 모니터, LDAP(Lightweight Directory Access Protocol) 서버 등 모든 외부 애플리케이션과의 연계를 의미하지만, 데이터베이스는 그 중요성과 특수성으로 인하여 별도의 JDBC 모니터링으로 취급한다. 즉, 제니퍼에서 외부 트랜잭션 모니터링은 데이터베이스를 제외한 모든 외부 애플리케이션과의 연동을 모니터링하는 것이다.

외부 트랜잭션 시작점 설정

외부 트랜잭션 모니터링은 다른 애플리케이션과의 연동을 담당하는 자바 애플리케이션 내부 모듈(특정 클래스의 특정 메소드)을 모니터링하는 것이다. 예를 들어, 메일 서버와 연동하는 클래스가 example.MailManager이고 메일을 보내는 메소드는 sendMail이라면, 외부 트랜잭션 모니터링은 example.MailManager 클래스의 sendMail 메소드의 호출 건수와 응답 시간 등의 데이터를 수집하는 것을 의미한다.

이를 위해서는 어떤 클래스의 어떤 메소드가 외부 트랜잭션 시작점인지를 설정해야 한다. 이를 외부 트랜잭션 시작점 설정이라고 한다.

제니퍼는 아래의 외부 애플리케이션과의 연동은 별도의 설정없이 외부 트랜잭션 시작점으로 자동 인식한다.

IBM CICS(Customer Information Control System)에서 CTG 5.x를 사용하는 경우, JENNIFER_HOME/agent/3rdparty/ctg51_jennifer40.jar파일을 CTG(CICS Transaction Gateway)를 위한 JAR 파일보다 앞쪽으로 클래스 패스에 추가하면, 외부 트랜잭션 시작점으로 자동 인식된다. CTG 5.x이외의 버전에서는 외부 트랜잭션 시작점으로 자동인식이 되지 않는다.

CTG 5.x 버전이 아닌 경우에는 tx_client 옵션으로 외부 트랜잭션 시작점을 설정해야 한다.

위에 기술된 것들 이외의 외부 트랜잭션의 경우, 제니퍼 에이전트의 tx_client로 시작하는 옵션을 통해서 외부 트랜잭션 시작점을 설정한다. 수정한 옵션을 반영하려면 제니퍼 에이전트를 설치한 자바 애플리케이션을 재시작해야 한다.

다음은 메일 서버와 연동하는 클래스가 example.MailManager일 경우의 외부 트랜잭션 시작점 설정의 예이다.

tx_client_class = example.MailManager

추가로 LDAP서버와 연동하는 클래스 example.LdapManager를 외부 트랜잭션 시작점으로 설정하는 경우에는 세미콜론[;]을 구분자로 해서 tx_client_class옵션에 설정한다. tx_client_class 옵션뿐만 아니라 외부 트랜잭션 시작점과 관련한 모든 옵션에 2개 이상을 설정하려면 세미콜론[;]을 구분자로 사용한다.

tx_client_class = example.MailManager;example.LdapManager

외부 트랜잭션 시작점은 특정 클래스의 특정 메소드를 의미한다. 즉, 앞에서와 같이 클래스만 설정하면 example.MailManager 클래스의 모든 메소드가 외부 트랜잭션 시작점으로 인식된다.

따라서 특정 메소드만을 외부 트랜잭션 시작점으로 설정하려면 제니퍼 에이전트의 tx_client_target_method 옵션으로 해당 메소드를 설정한다.

tx_client_target_method = sendMail

그런데 외부 트랜잭션 시작점으로 설정한 클래스들 중에서 이름이 동일한 메소드가 있고, 이중 특정 클래스의 특정 메소드만을 외부 트랜잭션 시작점으로 설정하려면 다음과 같이 구체적으로 설정한다.

tx_client_target_method = example.MailManager.sendMail

반대로 특정 메소드만을 외부 트랜잭션 시작점에서 제외하려면 제니퍼 에이전트의 tx_client_ignore_method 옵션으로 설정한다.

tx_client_ignore_method = example.MailManager.someMethod

메소드의 접근자를 통해서도 외부 트랜잭션 시작점을 설정할 수 있다. 예를 들어 public 접근자와 접근자가 없는 메소드를 외부 트랜잭션 시작점으로 등록하려면 다음과 같이 설정한다.

tx_client_access_method = public;none

tx_client_access_method 옵션에 설정이 가능한 값은 다음과 같다.

그리고 특정 클래스를 상속한 모든 클래스가 외부 트랜잭션 시작점인 경우에는 제니퍼 에이전트의 tx_client_super 옵션을 사용한다. 예를 들어, example.ejb.BaseSessionBean을 상속한 모든 클래스를 외부 트랜잭션으로 설정하려면 다음과 같이 설정한다.

tx_client_super = example.ejb.BaseSessionBean

그러나 이 경우에 직접적으로 상속받은 클래스만이 외부 트랜잭션 시작점이 된다. A 클래스가 example.ejb.BaseSessionBean 클래스를 상속하고 B 클래스가 A 클래스를 상속했다면, A 클래스는 외부 트랜잭션 시작점으로 인식하지만 B 클래스는 외부 트랜잭션 시작점으로 인식하지 않는다.

그리고 특정 인터페이스를 구현한 모든 클래스가 외부 트랜잭션 시적점인 경우에는 제니퍼 에이전트의 tx_client_interface 옵션을 사용한다. 예를 들어, example.eai.ITransaction-Manager 인터페이스를 구현한 모든 클래스를 외부 트랜잭션으로 설정하려면 다음과 같이 설정한다.

tx_client_interface = example.eai.ITransactionManager

그러나 이 경우에 직접적으로 구현한 클래스만이 외부 트랜잭션 시작점이 된다. A 클래스가 example.eai.ITransactionManager 인터페이스를 구현하고 B 클래스가 A 클래스를 상속했다면, A 클래스는 외부 트랜잭션 시작점으로 인식하지만 B 클래스는 외부 트랜잭션 시작점으로 인식하지 않는다.

그리고 클래스의 이름을 이용해서도 외부 트랜잭션 시작점을 설정할 수 있다. 이 경우에는 제니퍼 에이전트의 tx_client_prefix, tx_client_postfix, tx_client_ignore_prefix 옵션을 사용한다. 이름이 example.eai로 시작하는 모든 클래스를 외부 트랜잭션 시작점으로 하기 위해서는 다음과 같이 설정한다.

tx_client_prefix = example.eai

또한 이름이 TpMonitor로 끝나는 모든 클래스를 외부 트랜잭션 시작점으로 하기 위해서는 다음과 같이 설정한다.

tx_client_postfix = TpMonitor

그리고 특정 이름으로 시작하는 클래스를 외부 트랜잭션 시작점에서 제외하려면 제니퍼 에이전트의 tx_client_ignore_prefix 옵션을 사용한다. 이 옵션은 다른 모든 옵션에 우선한다.

tx_client_ignore_prefix =

제니퍼 에이전트의 tx_client로 시작하는 옵션을 통해서 동일한 내용을 다양한 방법으로 설정할 수 있다. 외부 트랜잭션 시작점을 설정하는데 있어서 tx_client_target_method 옵션을 통해서 실제로 외부 트랜잭션 시작점인 메소드만을 설정하도록 한다.

예를 들어, pkg.ClassA와 pkg.ClassB 클래스가 있다. 여기서 ClassA 클래스의 run 메소드와 ClassB 클래스의 process 메소드가 외부 트랜잭션 시작점이라고 가정한다. 그런데 ClassB 클래스에 run 메소드가 존재하고 이 메소드는 외부 트랜잭션과는 상관이 없다면 다음과 같이 설정한다.

tx_client_class = pkg.ClassA;pkg.ClassB
tx_client_target_method = run;process
tx_client_ignore_method = pkg.ClassB.run

또는 다음과 같이 설정할 수도 있다.

tx_client_class = pkg.ClassA;pkg.ClassB
tx_client_target_method = pkg.ClassA.run;pkg.ClassB.process

외부 트랜잭션 네이밍

외부 트랜잭션 이름은 TXNAMES 테이블에 저장된다. 기본적으로 외부 트랜잭션 이름은 클래스 이름과 메소드 이름으로 구성된다. 그런데 BEA 턱시도를 호출하는 경우에는 턱시도 서비스 이름이, IBM 웹스피어 MQ를 호출하는 경우에는 큐 이름이 외부 트랜잭션 이름에 포함되어야 좀더 정확한 모니터링이 가능하다.

이를 위해서 외부 트랜잭션 이름을 다양한 방식으로 설정하는 것을 외부 트랜잭션 네이밍이라고 한다. 수정한 외부 트랜잭션 네이밍과 관련한 옵션을 반영하려면 제니퍼 에이전트를 설치한 자바 애플리케이션을 재시작해야 한다.

기본적으로 외부 트랜잭션 이름은 클래스 이름과 메소드 이름으로 구성된다. 제니퍼 에이전트의 tx_client_ntype 옵션을 통해서 이를 다양하게 설정할 수 있다. 기본 값은 FULL이고, SIMPLE, CLASS, METHOD 등으로 설정할 수 있다.

tx_client_ntype = FULL

예를 들어, pkg.ClassB 클래스의 process 메소드를 외부 트랜잭션 시작점이라고 할 때 tx_client_ntype 옵션 설정에 따라서 외부 트랜잭션 이름은 다음과 같이 결정된다.

그리고 외부 트랜잭션 시작점인 메소드의 파라미터 값을 외부 트랜잭션 이름으로 사용할 수도 있다. 이를 위해서는 제니퍼 에이전트의 tx_client_using_param 옵션을 true로 설정한다.

tx_client_using_param = true

tx_client_using_param 옵션을 true로 설정하면, 외부 트랜잭션 시작점인 메소드의 여러개의 파라미터 중에서 java.lang.String 타입의 파라미터 중에서 첫번째 파라미터가 외부 트랜잭션 이름이 된다. 해당 사항이 없는 경우에는 tx_client_ntype 옵션에 따라서 외부 트랜잭션 이름이 결정된다.

그리고 외부 트랜잭션 이름으로 외부 트랜잭션 시작 메소드 내부에서 호출되는 특정 클래스의 특정 메소드의 파라미터나 반환 값을 사용할 수도 있다.

외부 트랜잭션 시작 메소드 내부에서 호출되는 특정 클래스의 특정 메소드의 파라미터를 외부 트랜잭션 이름으로 사용하려면 다음과 같이 설정한다.

lwst_txclient_method_using_param = pkg.ClassC.f1(String)

이 경우에는 여러 개의 파라미터 중에서 java.lang.String 타입의 파라미터 중에서 첫번째 파라미터가 외부 트랜잭션 이름이 된다.

그리고 외부 트랜잭션 시작 메소드 내부에서 호출되는 특정 클래스의 특정 메소드의 반환값을 외부 트랜잭션 이름으로 사용하려면 다음과 같이 설정한다.

lwst_txclient_method_using_return = pkg.ClassC.f2(String)

단, 이 경우에는 반환되는 객체의 유형이 java.lang.String이어야 한다.

lwst_txclient_method_using_param과 lwst_txclient_method_using_return 옵션에도 2개 이상은 세미콜론[;]을 구분자로 해서 설정한다.

여러 옵션이 설정된 경우에는 다음 옵션 순서대로 외부 트랜잭션 이름이 결정된다.

lwst_txclient_method_using_param과 lwst_txclient_method_using_return 옵션의 경우에는 실제 트랜잭션이 처리되는 과정에서 마지막으로 수행되는 메소드의 파리미터 혹은 반환 값이 외부 트랜잭션 이름이 된다.

외부 트랜잭션 모니터링과 예외 발생

외부 트랜잭션의 응답 속도가 임계치를 초과하면 WARNING_TX_BAD_RESPONSE 예외가 발생한다. 임계치는 제니퍼 에이전트의 tx_bad_responsetime 옵션으로 설정한다. 기본 값은 10000이고 단위는 밀리 세컨드이다.

tx_bad_responsetime = 10000

DB 연결 모니터링

데이터베이스 연동은 엔터프라이즈 애플리케이션 성능에 많은 영향을 미친다. 따라서 제니퍼는 이에 대한 성능 분석 및 조치를 위한 DB 모니터링을 제공한다. DB 모니터링으로 수집하는 데이터는 다음과 같다.

DB 모니터링을 위한 기본 설정

DB 사용을 모니터링을 하려면 build_jdbc 패치 옵션을 true로 설정해서 LWST 빌드를 해야 한다. 기본 값은 true이다. 자세한 사항은 [LWST 빌드와 설치(61 페이지)]를 참조한다.

그리고 제니퍼 에이전트의 enable_jdbc_sql_trace 옵션을 true로 설정해야 한다. 기본 값은 true이다. 이 옵션을 false로 설정하면 JDBC 모니터링이 이루어지지 않는다.

enable_jdbc_sql_trace = true

또한 세부 항목별로 JDBC 모니터링을 비활성화할 수 있다. 기본 값은 모두 true이다.

enable_jdbc_callablestatement_trace = true
enable_jdbc_preparedstatement_trace = true
enable_jdbc_statement_trace = true
enable_jdbc_resultset_trace = true
enable_jdbc_databasemetadata_trace = true

JDBC 모니터링 세부 항목을 비활성화하면 관련 데이터가 수집되지 않는다. 예를 들어, enable_jdbc_resultset_trace 옵션을 false로 하면 Fetch 건수가 수집되지 않고, enable_jdbc_preparedstatement_trace 옵션을 false로 하면 java.sql.PreparedStatement 객체로 수행한 SQL의 응답 시간이 수집되지 않는다.

비표준 방식으로 데이터베이스와 연동하는 자바 애플리케이션에 제니퍼를 설치하면 충돌이 발생할 수 있다. 이런 경우에는 세부 항목별로 JDBC 모니터링을 비활성화하여 원인을 파악하도록 한다.

자바 DB 커넥션 추적 설정

자바 애플리케이션이 JDBC API를 통해서 데이터베이스와 연동하는 방법은 다음과 같다.

JDBC API 구조

그리고 JDBC 모니터링을 위해서 사용하는 방법은 Class Wrapping이다.

JDBC 모니터링을 위한 구조

이를 위해서 자바 애플리케이션이 java.sql.Connection 객체를 획득하는 시점에 해당 객체를 JenniferConnection 객체로 Wrapping해야 한다. java.sql.Statement, java.sql.Result-Set 등의 객체는 JenniferConnection 객체에 의해서 자동으로 Wrapping된다.

그래서 java.sql.Connection 객체를 JenniferConnection 객체로 Wrapping하는 것을 JDBC 커넥션 추적 설정이라고 한다. 일반적으로 애플리케이션이 java.sql.Connection 객체를 획득하는데에는 3가지 유형이 있고 제니퍼는 이 유형들에 맞는 JDBC 커넥션 추적 설정 방법을 제공한다.

그런데 java.sql.Connection 객체가 만들어지는 시점의 스택트레이스를 제니퍼 에이전트 로그 파일에 기록할 수 있다. 이를 위해서는 제니퍼 에이전트의 debug_connection_open 옵션을 true로 설정한다. 기본 값은 false이다.

debug_connection_open = true

이를 통해서 JDBC 커넥션 추적 설정에 필요한 정보를 확보할 수 있다. 그러나 java.sql.Connection 객체를 생성할 때 마다 java.lang.Throwable 객체의 printStackTrace 메소드로 스택트레이스를 기록함으로 부하가 발생한다. 따라서 디버깅을 위한 목적으로만 사용하고 일반적인 경우에는 false로 설정한다.

유형 1 - JNDI & DataSource

자바 애플리케이션에서 JNDI(Java Naming and Directory Interface)를 이용해서 javax.sql.DataSource 객체를 획득하고, 이 객체로부터 java.sql.Connection 객체를 획득하는 경우를 유형 1이라고 한다.

javax.naming.Context jndiContext = new javax.naming.InitialContext();
javax.sql.DataSource ds = (javax.sql.DataSource)
                         jndiContext.lookup( “java:comp/env/jdbc/AppDS” );
java.sql.Connection con = ds.getConnection();

유형 1은 추가적인 설정없이 JDBC 모니터링이 가능하다. 단, 제니퍼 에이전트의 enable_jdbc_datasource_trace 옵션을 true로 설정해야 한다. 기본 값은 true이다.

enable_jdbc_datasource_trace = true

그런데 javax.naming.InitialContext 객체로부터 javax.sql.DataSource 객체를 직접적으로 획득하지 않고, 다른 javax.naming.Context 객체를 획득한 후에 그 객체에서 javax.sql.DataSource 객체를 획득하는 경우에는 별도의 설정이 필요하다.

javax.naming.Context jndiContext = new javax.naming.InitialContext();
javax.naming.Context jdbcContext = (javax.naming.Context)
              jndiContext.lookup( “java:comp/env/jdbc” );
javax.sql.DataSource ds = (javax.sql.DataSource)
               jdbcContext.lookup( “AppDS” );
java.sql.Connection con = ds.getConnection();

이런 경우에는 제니퍼 에이전트의 enable_wrap_context_jdbc_trace 옵션을 true로 설정해야 한다.

enable_wrap_context_jdbc_trace = true

단, enable_wrap_context_jdbc_trace 옵션을 true로 설정하면 EJB를 사용하는 경우에 java.lang.ClassCastException이 발생할 수 있다. 이 경우에는 유형 3으로 JDBC 커넥션을 추적해야 한다.

그리고 유형 1로 JDBC 커넥션을 추적하는 경우에 트랜잭션을 처리하는 자바 쓰레드가 다른 자바 쓰레드를 만들어서 JDBC 처리를 하면 해당 자바 쓰레드가 작업한 JDBC 내용이 모니터링되지 않는다. 이 경우에는 제니퍼 에이전트의 enable_non_servlet_thread_jdbc_trace 옵션을 true로 설정한다.

enable_non_servlet_thread_jdbc_trace = true

그리고 유형 1로 JDBC 커넥션을 추적하는 경우에 자바 애플리케이션이 데이터소스를 백그라운드 자바 쓰레드에서 초기화하면 JDBC 모니터링이 수행되지 않는다. 이 경우에도 제니퍼 에이전트의 enable_non_servlet_thread_jdbc_trace 옵션을 true로 설정한다. EJB 엔티티 빈을 사용하는 경우에 장애가 발생할 수도 있다. 이 경우에는 유형 1이 아닌 유형 3으로 JDBC 커넥션을 추적해야 한다.

유형 2 - DriverManager

자바 애플리케이션에서 java.sql.DriverManager 클래스를 통해서 java.sql.Connection 객체를 획득하는 경우를 유형 2라고 한다. 이 경우에는 JDBC 커넥션을 풀링하지 않기 때문에 JDBC 커넥션 개수를 체크하지 못한다.

한 트랜잭션이 처리하는 SQL 개수가 많거나 커넥션에 대한 획득과 반환이 매우 빈번한 경우에 제니퍼를 설치하면 자원 사용량이 높아질 수 있다.

유형 2는 제니퍼 에이전트의 user_defined_jdbc_connectionpool_prefixes 옵션을 통해서 JDBC 커넥션 추적 설정을 한다. 사용하는 데이터베이스와 JDBC 드라이버에 따라서 설정이 달라진다.

다음 코드는 오라클 데이터베이스를 사용하는 경우이다.

Class.forName("oracle.jdbc.driver.OracleDriver");
java.sql.Connection con = java.sql.DriverManager.getConnection
( "jdbc:oracle:thin:@127.0.0.1:1521:ORACL","scott", "tiget");

제니퍼 에이전트의 user_defined_jdbc_connectionpool_prefixes 옵션에 java.sql.DriverManager 클래스의 getConnection 메소드의 첫번째 파라미터 값의 일부를 시작 부분을 포함해서 설정한다.

user_defined_jdbc_connectionpool_prefixes = jdbc

그런데 여러 개의 데이터베이스를 사용하고 이 중 특정 데이터베이스와의 연동만을 모니터링하려면 user_defined_jdbc_connectionpool_prefixes 옵션을 좀더 구체적으로 설정한다.

user_defined_jdbc_connectionpool_prefixes = jdbc:oracle:thin:@local

이 옵션을 사용하는 경우에 JDBC 커넥션 객체의 미반환 여부를 체크하려면 제니퍼 에이전트의 user_defined_jdbc_ignore_close 옵션을 추가한 후, false로 설정해야 한다.

user_defined_jdbc_ignore_close = false

유형 3 - 임의의 클래스

자바 애플리케이션이 커넥션 풀의 역할을 담당하는 임의의 클래스를 통해서 java.sql.Connection 객체를 획득하는 경우를 유형 3이라고 한다. 아파치 DBCP와 같은 커넥션 풀 라이브러리를 사용하거나 레드햇 Hibernate 혹은 아파치 iBATIS 등의 프레임워크를 사용하는 경우가 이에 해당한다. 엄밀하게는 JDBC 커넥션 풀링 여부와는 상관없이 특정 클래스의 메소드를 통해서 java.sql.Connection 객체를 획득하고 반환하는 경우에 유형 3으로 설정한다. 따라서 유형 3으로 설정하려면 애플리케이션 소스 코드를 이해하고 있어야 한다.

예를 들어, 자바 애플리케이션에서 example.ConnectionPool 클래스의 getConnection 메소드를 통해서 java.sql.Connection 객체를 획득한다면 다음과 같이 설정한다.

jdbc_connection_get = example.ConnectionPool.getConnection()

만약 해당 메소드에 파라미터가 있다면 파라미터 유형까지 기술해야 한다.

jdbc_connection_get = example.ConnectionPool.getConnection(String)

그리고 example.ConnectionPool 클래스의 releaseConnection 메소드로 java.sql.Connection 객체를 커넥션 풀에 반환하는 경우에는 다음과 같이 설정한다.

jdbc_connection_close = example.ConnectionPool.releaseConnection(Connection)

이 경우에는 메소드의 파라미터 중에 java.sql.Connection 유형이 꼭 존재해야 한다. 그런데 java.sql.Connnection 객체의 close 메소드로 반환을 한다면 제니퍼 에이전트의 jdbc_connection_close 옵션을 설정할 필요가 없다. 그리고 java.sql.Connection 객체를 반환하는 방법을 알수 없는 경우에는 제니퍼 에이전트의 jdbc_connection_justget 옵션으로 설정한다.

jdbc_connection_juestget = example.ConnectionPool.getConnection(String)

제니퍼 에이전트의 jdbc_connection_juestget 옵션을 사용하면 JDBC 커넥션 개수와 JDBC 커넥션 객체의 미반환 여부를 체크하지 못한다.

유형 3으로 설정할 때 다음을 주의한다.

레드햇 Hibernate를 사용하는 경우에는 다음과 같이 설정한다.

jdbc_connection_justget = org.hibernate.jdbc.ConnectionManager.getConnection()

아파치 iBATIS를 사용하는 경우에는 다음과 같이 설정한다.

jdbc_connection_get = com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransaction.getConnection()

아파치 DBCP를 사용하는 경우에는 다음과 같이 설정한다.

jdbc_connection_get = org.apache.commons.dbcp.PoolingDataSource.getConnection()

DB 커넥션 개수 모니터링

유형 1 혹은 유형 3으로 DB 커넥션을 추적하는 경우에는 DB 커넥션 개수를 모니터링할 수 있다. DB 커넥션 유형은 다음과 같다.

DB 커넥션 유형

유형

설명

대기 중인 JDBC 커넥션개수(IDLE)

DB 커넥션 풀에서 대기 중인 DB 커넥션 개수

할당된 DB 커넥션 개수(ALLOCATED)

자바 쓰레드가 DB 커넥션 풀로부터 할당받은DB 커넥션 중에서 SQL 쿼리를 수행하고 있지 않은 DB 커넥션 개수

사용 중인 DB 커넥션 개수(ACTIVE)

자바 쓰레드가 DB커넥션 풀로부터 할당받은 DB 커넥션 중에서 SQL 쿼리를 수행하고 있는 DB 커넥션 개수

DB 커넥션 개수는 제니퍼 에이전트가 수집하는 일반 성능 데이터로 해당 시점의 값을 이퀄라이저 차트와 런타임 라인 차트를 통해서 확인할 수 있다.

실시간 JDBC 커넥션 개수

또한 DB 커넥션 개수에 대한 5분 평균 값은 PERF_X_01~31 테이블의 JDBC_IDLE, JDBC_ALLOC, JDBC_ACTIVE 칼럼에 저장된다.

1자바 애플리케이션에서 DB 커넥션 개수가 모니터링되지 않는 경우

제니퍼 내부에는 각 데이터베이스와 JDBC 드라이버별로 키가 되는 커넥션 목록이 등록되어 있다. 만약 제니퍼에 등록되지 않은 JDBC 드라이버가 사용되면 JDBC 커넥션 개수가 모니터링 되지 않는다.

이 경우 해당 JDBC 드라이버의 키 커넥션을 찾아 제니퍼 에이전트의 connection_trace_class 옵션에 설정해주어야 한다. 2개 이상은 세미콜론[;]을 구분자로 구분한다.

connection_trace_class = com.ibm.db2.jcc.b.bb;com.ibm.db2.jcc.b.o

특히 DB2 JDBC 드라이버는 버전에 따라 키 커넥션 클래스가 변경되기 때문에 이 옵션으로 설정해주어야 한다. 키 커넥션은 데이터베이스 연결을 위한 TCP 포트 번호에 대한 스택트레이스를 추적하는 방법으로 찾을 수 있다.

SQL 데이터 수집 방법

JDBC 모니터링은 다른 자원을 모니터링하는 것에 비해 상당히 많은 데이터를 수집해야 한다. 자바 애플리케이션이 수행하는 모든 SQL과 파라미터를 수집해야지만 문제가 있는 SQL에 대해서 튜닝 등의 작업을 할 수 있기 때문이다.

그래서 데이터 전송량을 줄이기 위해서 제니퍼 에이전트가 제니퍼 서버에 보내는 데이터에서는 SQL이 아닌 해당 SQL에 대한 해시 값을 사용한다. 그리고 별도의 방법을 통해서 해시 값에 대한 SQL을 수집해서 제니퍼 서버의 SQLS 테이블에 저장한다.

그리고 SQLS 테이블에 저장되는 SQL의 개수를 줄이기 위해서, SQL에 있는 상수를 파라미터로 처리하여 저장한다. 예를 들어, 자바 애플리케이션이 처리한 다음 SQL은 별도의 SQL이다.

SELECT * FROM TABLE WHERE ID = ? AND AGE = 32 AND NAME = ‘KIM’
SELECT * FROM TABLE WHERE ID = ? AND AGE = 27 AND NAME = ‘LEE’

그러나 앞의 코드는 상수를 제외한 기본적인 구조는 동일하다. 이를 그대로 SQLS 테이블에 저장하면 데이터의 양이 많아진다. 예를 들어, 다음 코드가 수행된다면 10,000개의 SQL이 SQLS 테이블에 저장되어야 한다.

Statement stmt = ...;
ResultSet rs = null;
for (int i = 0; i < 10000; i++) {
   rs = st.executeQuery("SELECT * FROM USERS WHERE ID = " + i);
   ...
}

이를 해결하기 위해서 상수 중에서 문자는 [$]로, 숫자는 [#]으로 변경하여 SQLS 테이블에 저장한다.

따라서 SQLS 테이블에는 다음 SQL 만이 저장된다.

SELECT * FROM TABLE WHERE ID = ? AND AGE = # AND NAME = ‘$’

그리고 X-View 프로파일 데이터 등에서는 상수 파라미터는 파라미터 1로 나타나고, 바인딩 파라미터는 파라미터 2로 표시된다. 바인딩 파라미터는 java.sql.PreparedStatement 객체로 수행하는 SQL에서 [?]로 표시되는 파라미터를 의미한다.

그런데 제니퍼 서버가 수집한 바인딩 파라미터가 정상적으로 보이지 않는 경우가 있다. 이 경우에는 제니퍼 에이전트의 sqlparam_encoding 옵션을 추가하여 java.sql.PreparedStatement 객체의 SQL 바인딩 파라미터에 대한 인코딩을 설정한다.

sqlparam_encoding = 8859_1

예를 들어, 데이터베이스 인코딩이 애플리케이션이 사용하는 인코딩과 달라서, 다른 프로파일 데이터는 정상적으로 보이는데 SQL 바인딩 파라미터만 정상적으로 보이지 않는 경우가 있다. 이런 경우는 애플리케이션이 SQL 파라미터를 설정하기 전에 명시적으로 인코딩을 변경한 것이며, 이 때 이 옵션을 사용하여 문제를 해결하도록 한다.

오라클 Dependency

오라클 데이터베이스를 사용하는 자바 애플리케이션을 모니터링할 때, 표준이 아닌 오라클 의존적인 코드를 사용하면 java.lang.ClassCastException이 발생할 수 있다. 예를 들어, CLOB, BLOB 등을 다음과 같이 처리한 경우가 이에 해당한다.

Statement stmt = ...;
ResultSet rs =
stmt.executeQuery("SELECT TEXT FROM TEST_CLOB WHERE ID =1 FOR UPDATE");
if (rs. next()) {
   oracle.sql.CLOB cl =
      ((oracle.jdbc.OracleResultSet) rs).getCLOB("TEXT");
   ....
}

이 경우에는 제니퍼 에이전트의 enable_jdbc_oracle_dependency_used 옵션을 true로 설정한다.

enable_jdbc_oracle_dependency_used = true

오라클 데이터베이스가 아닌 다른 데이터베이스를 사용하는 경우에 이 옵션을 true로 설정해도 문제가 되지 않는다.

그런데 enable_jdbc_oracle_dependency_used 옵션을 true로 설정한 후에 JDBC 모니터링이 되지 않고 X-View 프로파일 데이터에 다음과 같은 메세지가 나타날 수 있다.

enable_jdbc_oracle_dependency_used = true, but Oracle Driver not found

이 경우는 오라클 JDBC 드라이버가 자바 애플리케이션 클래스 패스에 없음에도 해당 옵션을 true로 설정하였거나, jennifer.jar 파일에서 오라클 JDBC 드라이버 파일을 참조할 수 없음을 의미한다.

enable_jdbc_oracle_dependency_used 옵션을 true로 설정한 상태에서 내부 객체가 오라클 JDBC 객체가 아닌 경우 내부 객체를 unwrap할 수 있다.

WAS가 JDBC 커넥션 풀을 구현할 때 Wrapper 클래스를 사용한다. 따라서 unwrap이란 이 Wrapper 객체로 부터 원래의 JDBC 객체를 꺼내는 것을 말한다.

unwrap_method로 끝나는 제니퍼 에이전트의 옵션으로 unwrap을 위한 메소드명을 설정한다.

jdbc_connection_unwrap_method = getVendorObj
jdbc_statement_unwrap_method = getVendorObj
jdbc_preparedstatement_unwrap_method = getVendorObj
jdbc_callablestatement_unwrap_method = getVendorObj
jdbc_resultset_unwrap_method = getVendorObj

각 JDBC 객체 별로 unwrap 메소드를 설정할 수 있다. 만약 모든 JDBC 객체를 위한 unwrap 메소드 이름이 동일하다면 제니퍼 에이전트의 jdbc_unwrap_method 옵션만으로 설정할 수 있다.

jdbc_unwrap_method = getVendorObj

unwrap은 특별한 경우에만 사용한다. 웹로직 JDBC 커넥션 풀 Wrapper 클래스들은 오라클 JDBC 드라이버 API를 구현하고 있다. 따라서 웹로직을 사용하는 경우에는 unwrap이 필요하지 않다.

JDBC Vendor Dependency

WAS에 의존적인 코드를 사용하는 자바 애플리케이션에 제니퍼를 설치하면 에러가 발생할 수 있다. 왜냐하면 제니퍼가 JDBC 모니터링을 위해서 사용하는 Wrapper 클래스와 WAS가 JDBC 커넥션 풀 구현을 위해서 사용하는 Wrapper 클래스가 호환되지 않기 때문이다.

이 문제를 해결하려면 JDBC 모니터링을 위해 사용하는 Wrapper 클래스가 WAS가 JDBC 커넥션 풀 구현을 위해서 사용하는 Wrapper 클래스와 호환되도록 해야 한다.

우선 제니퍼 에이전트의 enable_jdbc_vendor_wrap 옵션을 true로 설정한다.

enable_jdbc_vendor_wrap = true

이 옵션을 설정하면 enable_jdbc_oracle_dependency_used 옵션은 무시된다.

WAS 종류와 버전, 그리고 사용하는 데이터베이스에 따라서 JDBC 커넥션 풀 구현을 위해서 사용하는 Wrapper 클래스가 상이하다. 따라서 Wrapper 클래스를 명시적으로 설정해 주어야 한다. 제니퍼가 생성하는 Wrapper 클래스는 Wrapped 클래스 이름에 JWP가 붙는다. 다음은 주요 환경을 위한 설정 예제이다.

웹로직 9.x + 데이터소스 + 오라클
weblogic.jdbc.wrapper.PoolConnection_oracle_jdbc_driver_T4CConnectionJWP(S) =
   weblogic.jdbc.wrapper.PoolConnection_oracle_jdbc_driver_T4CConnection
weblogic.jdbc.wrapper.Statement_oracle_jdbc_driver_T4CStatementJWP(S) =
   weblogic.jdbc.wrapper.Statement_oracle_jdbc_driver_T4CStatement
weblogic.jdbc.wrapper.PreparedStatement_oracle_jdbc_driver_T4CPreparedStatementJWP(S) =
   weblogic.jdbc.wrapper.PreparedStatement_oracle_jdbc_driver_T4CPreparedStatement
weblogic.jdbc.wrapper.CallableStatement_oracle_jdbc_driver_T4CCallableStatementJWP(S) =
   weblogic.jdbc.wrapper.CallableStatement_oracle_jdbc_driver_T4CCallableStatement
weblogic.jdbc.wrapper.ResultSet_oracle_jdbc_driver_OracleResultSetImplJWP(S) =
   weblogic.jdbc.wrapper.ResultSet_oracle_jdbc_driver_OracleResultSetImpl
Notice: 재시작 필요
웹로직 6.x + 데이터소스
weblogic.jdbc.pool.ConnectionJWP(S) = weblogic.jdbc.pool.Connection
weblogic.jdbc.pool.StatementJWP(S) = weblogic.jdbc.pool.Statement
weblogic.jdbc.pool.PreparedStatementJWP(S) = weblogic.jdbc.pool.PreparedStatement
weblogic.jdbc.pool.CallableStatementJWP(S) = weblogic.jdbc.pool.CallableStatement
weblogic.jdbc.pool.ResultSetJWP(S) = weblogic.jdbc.pool.ResultSet
Notice: 재시작 필요
DriverManager + 오라클
oracle.jdbc.driver.T4CConnectionJWP(S) = oracle.jdbc.driver.OracleConnection
oracle.jdbc.driver.T4CStatementJWP(S) = oracle.jdbc.driver.T4CStatement
oracle.jdbc.driver.T4CPreparedStatementJWP(S) = oracle.jdbc.driver.T4CPreparedStatement
oracle.jdbc.driver.T4CCallableStatementJWP(S) = oracle.jdbc.driver.T4CCallableStatement
oracle.jdbc.driver.OracleResultSetImplJWP(S) = oracle.jdbc.driver.OracleResultSetImpl
Notice: 재시작 필요
톰켓 6.x + 데이터소스
org.apache.tomcat.dbcp.dbcp.PoolingDataSource$PoolGuardConnectionWrapperJWP(S) =
   org.apache.tomcat.dbcp.dbcp.DelegatingConnection
org.apache.tomcat.dbcp.dbcp.DelegatingStatementJWP(S) =
   org.apache.tomcat.dbcp.dbcp.DelegatingStatement
org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatementJWP(S) =
   org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement
org.apache.tomcat.dbcp.dbcp.DelegatingCallableStatementJWP(S) =
   org.apache.tomcat.dbcp.dbcp.DelegatingCallableStatement
org.apache.tomcat.dbcp.dbcp.DelegatingResultSetJWP(S) =
   org.apache.tomcat.dbcp.dbcp.DelegatingResultSet
Notice: 재시작 필요
웹스피어 5.x + 데이터소스
com.ibm.ws.rsadapter.jdbc.WSJdbcConnectionJWP(S) =
   com.ibm.ws.rsadapter.jdbc.WSJdbcConnection
com.ibm.ws.rsadapter.jdbc.WSJdbcStatementJWP(S) =
   com.ibm.ws.rsadapter.jdbc.WSJdbcStatement
com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatementJWP(S) =
   com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement
com.ibm.ws.rsadapter.jdbc.WSJdbcCallableStatementJWP(S) =
   com.ibm.ws.rsadapter.jdbc.WSJdbcCallableStatement
com.ibm.ws.rsadapter.jdbc.WSJdbcResultSetJWP(S) =
   com.ibm.ws.rsadapter.jdbc.WSJdbcResultSet
오직 오라클

그런데 만약 애플리케이션의 JDBC드라이버에 대한 의존성이 명확한 경우에는 다음과 같이 간단하게 설정할 수 있다.

java.sql.Connection(S) = oracle.jdbc.driver.OracleConnection
java.sql.Statement(S) = oracle.jdbc.driver.OracleStatement
java.sql.PreparedStatement(S) =
oracle.jdbc.driver.OraclePreparedStatement
java.sql.CallableStatement(S) =
oracle.jdbc.driver.OracleCallableStatement
java.sql.ResultSet(S) = oracle.jdbc.driver.OracleResultSet

위의 설정은 enable_jdbc_vendor_wrap = false하고 enable_jdbc_oracle_dependency_used=true로 설정한 것과 동일한 기능을 한다.

오라클 SID 확인 기능

[실시간 모니터링 | 애플리케이션] 메뉴의 JDBC 탭에서 오라클 SID를 확인할 수 있다. 오라클 SID를 확인하기 위해서는이를 위한 설정이 필요하며 그 방법은 데이터베이스 버전에 따라서 상이하다.

오라클 9i 데이터베이스는 제니퍼 에이전트의 dbsession_query 옵션에 다음과 같이 설정한다.

dbsession_query = \
oracle.jdbc.driver.OracleConnection: \
select SYS_CONTEXT ('USERENV','SESSIONID') sid from dual;

오라클 10g 데이터베이스는 제니퍼 에이전트의 dbsession_query 옵션에 다음과 같이 설정한다.

dbsession_query = \
oracle.jdbc.driver.PhysicalConnection: \
select SYS_CONTEXT ('USERENV','SESSIONID') sid from dual;

SQL 예외 로그 기록

자바 애플리케이션에서 java.sql.SQLException이 발생하면, 관련 내용을 제니퍼 에이전트 로그 파일에 기록할 수 있다. 이 내용을 기록하려면 제니퍼 에이전트의 enable_sql_error_trace 옵션을 true로 설정한다. 기본 값은 false 이다.

enable_sql_error_trace = true

단, 정상적인 상황에서도 비즈니스 로직에 종속적으로 java.sql.SQLException이 발생할 수 있다. 따라서 java.sql.SQLException을 오류 혹은 장애로 단정지을 수는 없다. 또한 반복적인 java.sql.SQLException의 발생으로 로그를 빈번하게 기록하면 성능 저하가 발생할 수 있으므로, 필요시에만 이 옵션을 true로 지정하고 평상시에는 false로 지정하는 것을 권장한다.

SQL 실행 계획

X-View 프로파일의 SQL 탭에서 오른쪽 마우스를 클릭하면 나타나는 컨텍스트 메뉴에서 [쿼리 빌드] 메뉴를 선택하면, SQL 실행 계획을 확인할 수 있는 팝업 창이 나타난다.

SQL 실행 계획

  1. 데이터베이스 정보 - SQL 실행 계획을 확인할 데이터베이스 정보를 입력한다.

  2. SQL - X-View 프로파일 탭에서 선택한 SQL이 자동으로 입력된다. 파라미터 1의 값은자동으로 치환되고, 파라미터 2로 표시되는 바인딩 파라미터는 하단에 별도로 나타난다.

  3. 파라미터 - 파라미터 2에 해당하는 바인딩 파라미터가 나타난다. 파라미터 값을 직접 수정할 수 있다.

  4. 실행 - 해당 버튼을 누르면 SQL 실행 계획이 하단 결과 영역에 나타난다.

  5. 쿼리 빌드 - 해당 버튼을 누르면 바인딩 파라미터를 바인딩한 SQL이 하단 결과 영역에 나타난다.

  6. 파라미터 추가 -해당 버튼을 누르면 파라미터가 추가된다.

SQL 실행 계획을 확인하려면 해당 데이터베이스에 대한 JDBC 드라이버를 JENNIFER_HOME/server/common/lib 디렉토리에 복사한 후에 제니퍼 서버를 재시작해야 한다.

SQL에 대한 세션별 데이터베이스 자원 사용량 확인

제니퍼 에이전트의 enable_dbstat 옵션을 true로 설정하면, 해당 SQL에 대한 세션별 데이터베이스 자원 사용량을 X-View 프로파일 데이터의 CLOSE-CONNECTION 메시지에서 확인할 수 있다.

enable_dbstat = true

SQL에 대한 세션별 데이터베이스 자원 사용량

여기서 ora는 오라클을 지칭하고, c는 CPU 사용량(1/100초)을 의미하고, b는 오라클 read block(Logical/Physical IO Block)을 의미한다.

DB 모니터링과 예외 발생

DB 모니터링과 관련해서 발생하는 예외는 다음과 같다.

SQL 응답 속도의 지연

SQL 응답 속도가 임계치를 초과하면 WARNING_DB_BAD_RESPONSE 예외가 발생한다. 임계치는 제니퍼 에이전트의 sql_bad_responsetime 옵션으로 설정한다. 기본 값은 20000이고 단위는 밀리 세컨드이다.

sql_bad_responsetime = 20000

Fetch 건수 초과

Fetch는 java.sql.ResultSet 객체의 next 메소드를 호출하는 것을 의미한다. 따라서 Fetch 건수는 next 메소드의 호출 건수를 의미한다. 트랜잭션이 수행되는 과정에서 동일한 java.sql.ResultSet 객체에 대한 Fetch 건수가 임계치를 초과하면 WARNING_JDBC_TOOMANY_RS_NEXT 예외가 발생한다. 임계치는 제니퍼 에이전트의 jdbc_resultset_warning_fetch_count 옵션으로 설정한다. 기본 값은 10000 이다.

jdbc_resultset_warning_fetch_count = 10000

DB 리소스 미반환 추적

java.sql.Connection, java.sql.Statement, java.sql.ResultSet 객체 등을 DB 연결 자원이라고 한다. 이 객체들을 사용한 후에 해당 객체의 close 메소드를 호출하지 않으면 DB 연결 자원 미반환 예외가 발생한다.

자바 애플리케이션에서 DB 연결 자원 미반환을 감지하는 시점의 차이가 존재한다. 제니퍼는 DB 연결 자원이 가비지 콜렉션되는 순간에 close 메소드의 수행 여부를 체크하여 DB 연결 자원 미반환을 감지한다. 따라서 가비지 콜렉션이 수행되지 않으면 DB 연결 자원 미반환 예외가 발생하지 않을 수 있고, 가비지 콜렉션이 수행되어도 트랜잭션 종료 시점과 DB 연결 자원 미반환 예외 발생 사이에 시차가 존재하게 된다. 즉 예외가 실제보다 지연되서 발생할 수 있다.

JDBC 자원 미반환 추적

Java Application에서는 트랜잭션이 종료되는 시점에는 DB 연결 자원 미반환 여부를 알수 없기 때문에 X-View 트랜잭션 데이터나 애플리케이션 처리 현황 통계 데이터에는 DB 연결 자원과 관련한 예외가 발생하지 않은 것으로 나타난다.

DB 연결 자원 미반환과 관련한 예외는 다음과 같다.

WARNING_DB_CONN_UNCLOSED

애플리케이션에서 DB Connection 객체를 사용한 후에 close 메소드를 호출하지 않으면 WARNING_DB_CONN_UNCLOSED 예외가 발생한다.

WARNING_JDBC_STMT_UNCLOSED

자바 애플리케이션에서 java.sql.Statement 객체를 사용한 후에 close 메소드를 호출하지않으면 WARNING_JDBC_STMT_UNCLOSED 예외가 발생한다.

WARNING_JDBC_PSTMT_UNCLOSED

자바 애플리케이션에서 java.sql.PreparedStatement 객체를 사용한 후에 close 메소드를 호출하지 않으면 WARNING_JDBC_PSTMT_UNCLOSED 예외가 발생한다.

WARNING_JDBC_CSTMT_UNCLOSED

자바 애플리케이션에서 java.sql.CallableStatement 객체를 사용한 후에 close 메소드를 호출하지 않으면 WARNING_JDBC_CSTMT_UNCLOSED 예외가 발생한다.

WARNING_JDBC_RS_UNCLOSED

자바 애플리케이션에서 java.sql.ResultSet 객체를 사용한 후에 close 메소드를 호출하지않으면 WARNING_JDBC_RS_UNCLOSED 예외가 발생한다.

JDBC 자원 미반환 문제를 해결하기 위한 스택트레이스를 제공한다. 일반적으로 이것만으로 충분하지만, 좀더 상세한 스택트레이스가 필요할 수도 있다. 이 경우에는 제니퍼 에이전트의 enable_jdbc_XXXX_fullstack_trace 옵션을 true로 설정한다. 기본 값은 모두 false이다.

enable_jdbc_connection_fullstack_trace = true
enable_jdbc_callablestatement_fullstack_trace = true
enable_jdbc_preparedstatement_fullstack_trace = true
enable_jdbc_statement_fullstack_trace = true
enable_jdbc_resultset_fullstack_trace = true

상세한 스택트레이스를 기록하는 것은 부하가 클 수 있다. 따라서 문제를 해결하는데 필요한 기간에만 일시적으로 적용하는 것을 권장한다.

미반환 JDBC 자원을 제니퍼 에이전트가 임의적으로 반환하도록 설정할 수 있다. 제니퍼 에이전트의 enable_auto_XXXX_close 옵션을 통해서 이를 설정할 수 있다. 기본 값은 모두 false이다.

enable_auto_connection_close = true
enable_auto_statement_close = true
enable_auto_preparedstatement_close = true
enable_auto_callablestatement_close = true
enable_auto_resultset_close = true

그러나 ApplicationServer 혹은 커넥션 풀 라이브러리에서 JDBC자원을 관리하고 있는 경우, java.sql.Connection 객체를 자동으로 반환하는 것은 위험할 수 있다.

오라클 XML DB XMLType 누수 추적

오라클 XML DB를 사용하는 경우에 oracle.xdb.XMLType 객체를 사용한 후에 close 메소드를 호출하지 않으면 WARNING_ORACLE_XMLTYPE_UNCLOSED 예외가 발생한다. 단, 이 예외는 제니퍼 에이전트의 enable_oracle_xdb_xmltype_trace 옵션을 true로 설정한 경우에만 발생한다. 기본 값은 true이다.

enable_oracle_xdb_xmltype_trace = true

SQL수행 후 Uncommit/Rollback 추적

애플리케이션에서 DB 트랜잭션을 시작(false를 파라미터로 java.sql.Connection 객체의 setAutoCommit 메소드를 호출)하고 java.sql.Statement 객체의 execute, executeBatch, executeUpdate 등의 메소드를 호출한 후에, 명시적으로 DB 트랜잭션을 종료(rollback 혹은 commit 메소드를 호출)하지 않으면 WARNING_DB_UN_COMMIT_ROLLBACK 예외가 발생한다. 이런 경우에 제니퍼 에이전트의 enable_rollback_uncommited_close 옵션을 통해서 관련 java.sql.Connection 객체를 자동으로 rollback할 수 있다. 기본 값은 false이다.

enable_rollback_uncommited_close = true

검증된 시스템이 아닌 경우에 이 옵션을 true로 설정하는 것을 권장하지 않는다. java.sql.Connection 객체의 close 메소드가 호출되는 시점의 자바 쓰레드와는 다른 자바 쓰레드에서 rollback 메소드가 호출되면 장애가 발생할 수 있다.

ignore_rollback_uncommited_error = true

장기적으로는 SELECT 문이 exeucteQuery 메소드로 처리되도록 해당 소스 코드를 수정하는 것을 권장한다.

DB 커넥션 객체의 중복 할당

동일한 DB Connection 객체가 여러 개의 서비스 쓰레드에 중복으로 할당되면 애플리케이션이 정지되는 것과 같은 치명적인 문제를 야기할 수 있다. 이런 현상이 나타나면 제니퍼는 WARNING_DB_CONN_ILLEGAL_ACCESS 예외를 발생시킨다. 이런 중복 할당이 일어나는 원인은 다음과 같다.

DB 중복 풀링 구조로 인한 중복 할당 예외

그런데 중복 할당이 아님에도 불구하고 애플리케이션 내부 구조로 인해서 이 경보가 발생할 수도 있다. 이런 현상을 유발하는 애플리케이션 내부 구조를 중복 풀링 구조라고 한다.

예를 들어, 제니퍼가 DataSource(유형1)나 DriverManager(유형2)로 JDBC 모니터링을 하는데 애플리케이션 내부에 또다른 커넥션 풀이 존재하면, 제니퍼는 하나의 커넥션이 반환되지 않고 여러 쓰레드에서 사용되고 있다고 인식하고 WARNING_JDBC_CONN_ILLEGAL_ACCESS 예외를 발생시킨다.

따라서 WARNING_JDBC_CONN_ILLEGAL_ACCESS 예외가 발생하면 먼저 중복 풀링 구조인지를 확인하고 JDBC 커넥션 추적 설정을 변경하도록 한다.

중복 풀링 구조 때문에 WARNING_JDBC_CONN_ILLEGAL_ACCESS 예외가 발생하면 그 빈도가 매우 높고 거의 대부분의 트랜잭션에서 발생하는 특징이 있으므로 프로그램 오류에 의한 중복 할당과 쉽게 구분할 수 있다. 또한 중복 풀링 구조는 불필요한 이중 작업이 수행되기 때문에 애플리케이션 구조 개선을 검토할 필요도 있다.

사용자 정의형 리소스 모니터링

JDBC 자원뿐만 아니라 임의의 리소스에 대한 누수 현상도 모니터링할 수 있다. 예를 들어 , 네트워크 성능 향상을 위해서 java.net.Socket 객체를 풀링하는 경우에 누수 현상을 모니터링할 수 있다.

그러나 모든 리소스 누수 현상을 모니터링할 수 있는 것은 아니고, 리소스를 획득하는 메소드와 반환하는 메소드가 명확한 경우에만 이 기능을 사용할 수 있다. 트랜잭션을 처리하는 과정에서 리소스를 획득하는 메소드의 호출 건수와 리소스를 반환하는 메소드의 호출 건수가 동일하지 않으면 WARNING_RESOURCE_LEAK 예외가 발생한다.

사용자 정의형 자원 모니터링은 제니퍼 에이전트의 leakcheck_XXXX_get과 leakcheck_XXXX_close 옵션을 통해서 설정한다.

예를 들어, mypool과 yourpool이라는 리소스 풀이 있다면 제니퍼 에이전트의 leakcheck_XXXX_get 옵션을 통해서 리소스 획득 메소드를 설정한다. XXXX 대신에 리소스 풀 이름을 사용한다.

leakcheck_mypool_get = example.Leak.get();
leakcheck_yourpool_get = example.Leak2.get();

그리고 제니퍼 에이전트의 leakcheck_XXXX_close 옵션을 통해서 리소스 반환 메소드를 설정한다. XXXX 대신에 리소스 풀 이름을 사용한다.

leakcheck_mypool_close = example.Leak.release(Object);
leakcheck_yourpool_close = example.Leak2.release(Object);

사용자 정의 리소스 풀 사용 내역을 X-View 프로파일 데이터에서 확인할 수 있다.

사용자 정의 리소스 풀에 대한 X-View 프로파일

쓰레드 모니터링(CPU튜닝을 위한 HOW TO)

제니퍼는 Java1.5환경에서 JMX를 이용한 쓰레드 상세 모니터링 기능을 제공한다. 제니퍼는 서비스 중심적인 모니터링 솔루션이다. 즉 사용자의 요청을 처리하는 쓰레드를 중심으로 서비스 성능을 추적한다.

그런데 간혹 프로세스의 CPU사용량을 튜닝해야하는 상황이 있다. 이런 경우에는 쓰레드 중심적인 모니터링을 수행해야 한다. 즉 어떤 쓰레드가 CPU를 많이 사용하는지를 판별하고 해당 쓰레드를 튜닝해야 하는데 이 쓰레드는 크게 서비스 쓰레드와 백그라운드 쓰레드로 구분할 수 있다.

웹 애플리케이션 서버에서 서비스 쓰레드는 일반적으로 정형화된 쓰레드 이름을 가지고 있다. 따라서 우리는 쓰레드 이름을 통해 서비스쓰레드와 백그라운드 쓰레드를 구분할 수 있다.

이렇게 구분하여 서비스 쓰레드가 CPU를 많이 사용하는지 백그라운드 쓰레드가 CPU를 많이 사용하는지를 확인고 튜닝해야 한다.

쓰레드 모니터링

제니퍼는 프로세스 내부의 쓰레드를 이름으로 구분하고 그들의 CPU사용량을 모니터링함으로써 CPU튜닝을 위한 기반정보를 제공한다.

설정 및 환경

JENNIFER AGENT 설정

쓰레드 상세 모니터링은 제니퍼 에이전트 환경이 Java 1.5 이상에서만 사용할 수 있다.

extra_enable=true
extra_agent_class=jennifer.extra.ThreadMon
extra_agent_classpath=/jennifer/agent/lwst40.custom.jar
extra_data_interval=3000
xtmon_fg_name=http-8080
xtmon_topn=20
xtmon_file_save=true

제니퍼에이전트의 모니터링 확장 기능인 ExtraAgent기능을 사용한다.

JENNIFER SERVER 설정

제니퍼 서버에는 메모리 상세 정보를 모니터링 하기 위한 화면을 등록해야 한다. 제니퍼 4.2 서버를 처음 기동했다면 자동으로 [ 실시간 모니터링| 쓰레드 ]에 등록되어 있고 권한만 빠져 있는 상태가 된다. 만약 다른 버전이거나 메뉴가 존재하지 않으면 다음 URL을 메뉴에 등록한다.

real_thread.jsp

쓰레드 대시보드와 해석

쓰레드 상세 모니터링의 대시보드는 아래와 같다.

쓰레드 대시보드

화면 상단에서 에이전트를 선택해야만 그래프가 보인다.

중요 그래프

주요 그래프 내용은 다음과 같다

Forground/Background Threads

서비스(Forground)쓰레드와 백그라운드 쓰레드들 중에서 단위시간 (기본 3000ms) 동안 사용한 CPU시간이 긴 순서데로 리스트로 보여줌 어떤 특정 쓰레드의 CPU사용량이 지속적으로 많으면 원인을 찾아봐야 한다.

Forground/Background Threads

서비스(Forground)쓰레드와 백그라운드 쓰레드들 중에서 단위시간 (기본 3000ms) 동안 사용한 CPU시간이 긴 순서데로 리스트로 보여줌 어떤 특정 쓰레드의 CPU사용량이 지속적으로 많으면 원인을 찾아봐야 한다.

Thread Count

Thread Count

대시보드의 하단 그래프 중에서 왼쪽은 몇개의 쓰레드가 실행되었는지를 보여준다.

두번에 F:99, B:99를 표시하는 그래프는 현재 수행중이 쓰레드중에서 Forground 쓰레드 수와 Background 쓰레드 수의 비율을 나타낸다.

CPU 사용량 비율

백그라운드 쓰레드가 CPU를 많이 사용하는지 포그라운드 쓰레드사 CPU를 많이 사용하는지를 나타내는 그래프이다.

시스템 CPU사용량을 보고 CPU사용비율을 확인한후 어떻게 CPU사용을 튜닝할지를 연구해야 한다.

CPU 사용량 비율

백그라운드 쓰레드가 CPU를 많이 사용하는지 포그라운드 쓰레드사 CPU를 많이 사용하는지를 나타내는 그래프이다.

시스템 CPU사용량을 보고 CPU사용비율을 확인한후 어떻게 CPU사용을 튜닝할지를 연구해야 한다.

실시간 상세 조회

수행중인 쓰레드의 Stack 정보를 상세하게 조회할 수있다.

실시간 상세 조회

수행중인 쓰레드의 Stack 정보를 상세하게 조회할 수있다.

언제 시작되었고 지금까지 얼마의 CPU를 사용했는지 등을 알 수있다. 또한 해당 쓰레드의 콜 스택을 조회할 수 있다.

각 쓰레드별 CPU Time은 쓰레드가 실행되어 지금까지 사용한 CPU점유시간이다. 이 값을 보고 어느쓰레드가 CPU를 많이 사용하는지를 판별할 수 있다.

자바 메모리 상세 모니터링

자바 프로세스의 성능을 튜닝할때 메모리 튜닝을 빼놓을 수는 없다. 강력한 힙 메모리 관리 모델을 제시함으로써 이전보다 편리하고안정적인 프로그램이 가능해 졌지만 여전히 메모리는 관리영역중에 1순위이다. 힙메모리를 통한 엔진레벨의 관리는 메모리를 안정화 시켰지만 CPU 과도한 사용을 야기했다. 더이상 메모리 부족은 메모리의 문제로 나타나는 것이 아니라 필요이상의 CPU사용으로 나타나기 때문에 여전히 메모리 관리와 튜닝은 쉽지 않은 영역이다.

그런데 Java5 부터는 이전보다 강력한 메모리 모니터링 수단을 제공한다. 이것을 이용하여 힙메모리 혹은 NON-HEAP 상태를 모니터링 하거나 메모리를 해제하기 위한 GC의 움직임이나 오버헤드를 실시간 관찰할 수 있다.

제니퍼에서는 이것을 이용하여 보다 운영자 직관적으로 메모리 상태를 모니터링 할 수 있도록 기능을 제공하고 있다.

설정 및 환경

JENNIFER AGENT 설정

메모리 상세 모니터링은 제니퍼 에이전트 환경이 Java 1.5 이상에서만 사용할 수 있다.

extra_enable=true
extra_agent_class=jennifer.extra.MemoryMon
extra_agent_classpath=/jennifer/agent/lwst40.custom.jar
extra_data_interval=3000
xmmon_file_save=true

제니퍼에이전트의 모니터링 확장 기능인 ExtraAgent기능을 사용한다.

JENNIFER SERVER 설정

제니퍼 서버에는 메모리 상세 정보를 모니터링 하기 위한 화면을 등록해야 한다. 제니퍼 4.2 서버를 처음 기동했다면 자동으로 [ 실시간 모니터링| 메모리 ]에 등록되어 있고 권한만 빠져 있는 상태가 된다. 만약 다른 버전이거나 메뉴가 존재하지 않으면 다음 URL을 메뉴에 등록한다.

real_memory.jsp

메모리 대시보드와 해석

메모리 상세 모니터링의 대시보드는 아래와 같다

메모리 대시보드

화면 상단에서 에이전트를 선택해야만 그래프가 보인다.

중요 그래프

주요 그래프 내용은 다음과 같다

GC Delta

단위 시간(기본 3000ms) 동안 GC가 발생한 횟수와 그것을 위해 사용한 시간을 라인 그래프로 보여진다. GC가 발생할때 걸린 시간을 실시간으로 모니터링 할 수있다.

GC Delta

영역별 HEAP 사용량

힙메모리 사용량의 변화를 모니터링 한다.

영역별 HEAP 사용량

영역별 NON-HEAP 사용량

NON-HEAP 메모리 사용량의 변화를 모니터링 한다.

영역별 NON-HEAP 사용량

Object Pending Finalization Count

finalize()메소드가 호출되기를 기다리를 객체 수 로써 만약 이값이 높다면 finalze()메소드에 많은 로직이 존재할 수 있음으로 분석이 필요할 수 있다.

Object Pending Finalization Count

GC Total

프로세스가 기동되고 수행된 GC의 총수와 그로인해 사용되었던 시간정보를 나타내기 위한 화면이다.

GC Total

기타 시스템 그래프

모니터링중인 에이전트의 액티브서비스, 시스템 CPU, TPS, 응답시간은 GC로 영향받는 에이전트의 상황을 비교하기 위해 포함되어있다.

데이터 조회

메모리 상세 모니터링은 메모리 현황을 모니터링하고 튜닝하여 최적의 실행환경을 만들기 위한 목적이다. 따라서 다른 성능 데이터들 처럼 장시간 보관하거나 관리할 필요가 없다.

이에 따라 제니퍼는 현재의 프로세스 메모리 현황을 조회하거나 간단한 제어를 할 수있는 기능과 상대적으로 단기간의 메모리 상세데이터를 엑셀로 다운받아 이차가공하는 기능을 제공한다.

실시간 데이터 조회

GC Delta(왼쪽 상단) 그래프의 좌측 버튼을 클릭하면 현재 에이전트의 메모리 현황을 상세하게 조회할 수 있는 기능이 제공된다. 사용할 수있는 중요 기능은 아래와 같다.

실시간 데이터 조회

메모리 상세 조회시 각 속성의 의미는 JVM 설명서를 참조해야 한다.

저장 데이터 조회

에이전트에 메모리 모니터링 기능을 설정할때 제니퍼 서버에서 주요 데이터를 저장하도록 할 수 있다.

xmmon_file_save = true

그러면 메모리 관련 상세 정보가 REMON포멧으로 저장된다. 이데이터는 화면 우측 하단의 [검색]버튼을 클릭하여 조회할 수 있다.

저장 데이터 조회