[12] Jmeter + 톰캣 + UCP를 이용한 RLB (Runtime Load Balancing) 동작 방식 검증

이번 시간에는 톰캣 서버 + UCP + JSP + Jmeter를 이용해서 RLB 동작 방식을 파악해보도록 하겠습니다.

준비 사항


다음 사항을 준비합니다.

1. Jmeter 설치


Jmeter 설치는 여기를 참고하세요.

2. UCP 구성


지난 포스팅을 참고하세요.

3. 샘플 JSP 작성 (load.jsp)


load.jsp의 핵심은 노드에 따라서 쿼리 성능의 편차를 크게 하는 것입니다. 이를 위해, 인스턴스 명을 기준으로 쿼리를 분기해서 처리했습니다.

<%@ page language=”java” contentType=”text/html; charset=UTF-8″  pageEncoding=”UTF-8″%>
<%@ page import=”java.sql.*”%>
<%@ page import=”javax.naming.*”%>
<%@ page import=”javax.sql.*”%>
<%@ page import=”oracle.ucp.jdbc.*”%>
<%@ page import=”oracle.ucp.admin.*”%>
<html>
<head>
<body >
<%
String   v1 = null;
Integer  v2 = null;
Connection conn=null;
Statement st=null;
ResultSet rs=null;
try {
Context ctx = new InitialContext();
Context envContext  = (Context) ctx.lookup(“java:/comp/env”);
javax.sql.DataSource ds = (javax.sql.DataSource) envContext.lookup(“jdbc/online_ucp”);
PoolDataSource pds = (PoolDataSource) ds;
conn = pds.getConnection();
st=conn.createStatement();
String sql = “select instance_name from v$instance”;
rs = st.executeQuery(sql);
rs.next();
v1 = rs.getString(1);
%>
<%=v1 %>
<%
if (v1.equals(“ORA12C1″))
sql=”select count(*) from (select level from dual connect by level<=2000) a, (select level from dual connect by level<=10000)”;
else if (v1.equals(“ORA12C2″))
sql=”select count(*) from (select level from dual connect by level<=2000) a, (select level from dual connect by level<=10)”;
                rs = st.executeQuery(sql);
rs.next();
v2 = rs.getInt(1);
%>
<%=v2 %>

<%
} catch (Exception e){
e.printStackTrace(System.out);
}
finally {
st.close();
rs.close();
conn.close();
}
%>
</body>
</html>

4. 성능 정보를 수집할 스크립트 생성 (save_load.sql)


save_table을 생성한 후, save_load.sql을 이용해서 서비스의 로드 및 시스템 상황을 저장합니다.

desc save_load
 Name                                      Null?    Type
 --------------------- -------- ----------------------------
 LOG_TIME                                           DATE
 INST_ID                                            NUMBER
 SERVICE_NAME                                       VARCHAR2(64)
 CALLS                                              NUMBER
 DBTIMEPERCALL                                      NUMBER
 DBTIMEPERSEC                                       NUMBER
 GOODNESS                                           NUMBER
 DELTA                                              NUMBER
 ACTIVE_CNT                                         NUMBER
 TOTAL_CNT                                          NUMBER
 BUSY_TIME                                          NUMBER
 IDLE_TIME                                          NUMBER
 LOAD                                               NUMBER

insert into save_load
select sysdate,
       a.inst_id,
       a.service_name,
       round(a.callspersec*a.intsize_csec/100,1) calls,
       round(a.dbtimepercall/1000000,4) dbtimepercall,
       round(a.dbtimepersec/100,4)      dbtimepersec,
       a.goodness,
       a.delta,
       b.active_cnt,
       f.total_cnt,
       c.value busy_time,
       d.value idle_time,
       round(e.value,2) load
from   gv$servicemetric a,
       (select inst_id, count(*) active_cnt from gv$session where status='ACTIVE' and service_name='ONLINE_UCP' group by inst_id) b,
       (select inst_id, count(*) total_cnt  from gv$session where service_name='ONLINE_UCP' group by inst_id) f,
       (select inst_id, value from gv$osstat c where stat_name='BUSY_TIME') c,
       (select inst_id, value from gv$osstat c where stat_name='IDLE_TIME') d,
       (select inst_id, value from gv$osstat c where stat_name='LOAD') e
where  a.inst_id = b.inst_id (+)
and    a.inst_id = c.inst_id
and    a.inst_id = d.inst_id
and    a.inst_id = e.inst_id
and    a.service_name='ONLINE_UCP'
and    a.inst_id = f.inst_id (+)
and    a.intsize_csec < 1000;
commit;
exit

이제 모든 준비가 끝났습니다. 그럼 테스트를 시작합니다.

12-1. UCP + CLB_GOAL=SHORT / RLB_GOAL=SERVICE_TIME 인 서비스 (ONLINE_UCP)를 이용한 부하 테스트


아래 순서대로 테스트를 진행합니다.

1. 톰캣 서버 기동


[root@ap1 ~]# service tomcat start
Using CATALINA_BASE:   /usr/local/tomcat
Using CATALINA_HOME:   /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME:        /usr/local/java
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar

2. 커넥션 풀 연결 확인


UCP는 톰캣 기동 시에 커넥션을 연결하지 않고, 첫 번째 요청이 들어오는 시점에 커넥션을 연결합니다. 따라서, 현재 시점에는 ONLINE_UCP 서비스로 연결된 세션은 없습니다.

select 'INST_1' as inst_id, count(*) total_cnt,
       sum(decode(service_name,'ONLINE_DBCP',1)) online_dbcp,
       sum(decode(service_name,'ONLINE_UCP' ,1)) online_ucp,
       sum(decode(service_name,'SHORT_SRV'  ,1)) short_srv,
       sum(decode(service_name,'LONG_SRV'   ,1)) long_srv
from gv$session where inst_id=1
union all
select 'INST_2' as inst_id, count(*) total_cnt,
       sum(decode(service_name,'ONLINE_DBCP',1)),
       sum(decode(service_name,'ONLINE_UCP' ,1)),
       sum(decode(service_name,'SHORT_SRV'  ,1)),
       sum(decode(service_name,'LONG_SRV'   ,1))
from gv$session where inst_id=2;

INST_I  TOTAL_CNT ONLINE_DBCP <b>ONLINE_UCP</b>  SHORT_SRV   LONG_SRV
------ ---------- ----------- ---------- ---------- ----------
INST_1         74           6                     6          6
INST_2         67           4                     4          4

3. UCP 연결


UCP는 최초 요청 직후에 커넥션을 연결합니다. 이전 포스팅에서 작성한 test.jsp를 수행한 후, 커넥션 연결 여부를 확인합니다.

INST_I  TOTAL_CNT ONLINE_DBCP ONLINE_UCP  SHORT_SRV   LONG_SRV
------ ---------- ----------- ---------- ---------- ----------
INST_1         87           6         13          6          6
INST_2         74           4          7          4          4

4. 로드 저장용 스크립트 수행


save_load.sql을 이용해서 5초에 1번씩 부하 상황을 저장합니다.

[oracle@rac1 ~]$ while true
> do
> sqlplus apps/apps @save_load.sql
> sleep 5
> done

5. Jmeter로 Run 테스트


다음과 같이 Jmeter를 설정한 후 실행합니다.

12-01

12-02

12-2. 결과 분석


테스트가 끝난 후에, 다음 쿼리를 이용해서 수행 결과를 분석합니다.

select a.log_time,
       a.timeperc_cs_1,
       a.timeperc_cs_2,
       a.active_cnt_1,
       a.active_cnt_2,
       a.total_cnt_1,
       a.total_cnt_2,
       a.load_1,
       a.load_2,
       b.cpu_1,
       b.cpu_2
from (
    select to_char(log_time,'HH24:MI:SS') log_time,
           max(decode(inst_id, 1, calls,0)) calls_1,
           max(decode(inst_id, 2, calls,0)) calls_1,
           max(decode(inst_id, 1, dbtimepercall*100,0)) timeperc_cs_1, -- 1/100 sec
           max(decode(inst_id, 2, dbtimepercall*100,0)) timeperc_cs_2, -- 1/100 sec
           max(decode(inst_id, 1, dbtimepersec,0)) dbtimepersec_1,
           max(decode(inst_id, 2, dbtimepersec,0)) dbtimepersec_2,
           max(decode(inst_id, 1, active_cnt,0)) active_cnt_1,
           max(decode(inst_id, 2, active_cnt,0)) active_cnt_2,
           max(decode(inst_id, 1, total_cnt,0)) total_cnt_1,
           max(decode(inst_id, 2, total_cnt,0)) total_cnt_2,
           max(decode(inst_id, 1, load,0)) load_1,
           max(decode(inst_id, 2, load,0)) load_2
    from   save_load a
    group  by to_char(log_time,'HH24:MI:SS')
    ) a,
    (select log_time,
            round((delta_busy_time_1/(delta_busy_time_1+delta_idle_time_1)*100),0) cpu_1,
            round((delta_busy_time_2/(delta_busy_time_2+delta_idle_time_2)*100),0) cpu_2
     from ( select log_time,
                   busy_time_1 - lag(busy_time_1) over (order by log_time) delta_busy_time_1,
                   busy_time_2 - lag(busy_time_2) over (order by log_time) delta_busy_time_2,
                   idle_time_1 - lag(idle_time_1) over (order by log_time) delta_idle_time_1,
                   idle_time_2 - lag(idle_time_2) over (order by log_time) delta_idle_time_2
            from (
              select to_char(log_time,'HH24:MI:SS') log_time,
                     max(decode(inst_id, 1, busy_time,0)) busy_time_1,
                     max(decode(inst_id, 2, busy_time,0)) busy_time_2,
                     max(decode(inst_id, 1, idle_time,0)) idle_time_1,
                     max(decode(inst_id, 2, idle_time,0)) idle_time_2
              from   save_load a
              group by to_char(log_time,'HH24:MI:SS'))
          )
   ) b
where a.log_time = b.log_time(+)
order by a.log_time;

결과-1.

아래 그래프와 같이, DBTIMEPERCALL 시간이 짧은 2번 노드로 커넥션 풀 세션이 이전하는 것을 알 수 있습니다. 즉, RLB가 정상적으로 이루어졌습니다. (그림-1, 그림-2. 참조)

그림-1. 노드 별 DBTIMEPERSEC 트렌드

12-03

그림-2. 노드 별 커넥션 풀 세션 수 트랜드

12-04

결과-2.

RLB가 정상적으로 이루어짐에 따라서, 1번 노드의 active 세션 개수가 점차적으로 줄어드는 것을 확인할 수 있습니다. (그림-3. 참조)

그림-3. 노드 별 Active 세션 수 트랜드

12-05

결과-3.

CPU 런-큐 및 CPU(%)은 RLB와는 무관한것으로 확인됩니다. (그림-4, 그림-5. 참조) 즉, 해당 값들 커넥션 시점의 로드 밸런싱(CLB)를 위해서만 사용됩니다. 그리고 커넥션 풀 세션들이 2번 노드로 이전함에 따라, 1번 노드의 CPU(%)이 점차적으로 감소하는 것을 확인할 수 있습니다.

그림-4. 노드 별 CPU Run-Queue 트렌드

12-07

그림-5. 노드 별 CPU(%) 트렌드

12-06

12-3 결과 요약


상기 예제를 통해 UCP 환경에서의 RLB 동작 방식을 확인했습니다. 나머지 3개의 서비스에 대한 테스트 겱는 지면 관계상 요약 정리만 하도록 하겠습니다. (표-1. 참조)

표-1. 서비스 유형 별 테스트 결과 요약

 서비스 명  구성  영향받는 항목  RLB

여부

 GOODNESS  DELTA
 LONG_SRV CLB_GOAL=LONG
DBCP
 커넥션 시,

“해당 서비스의 세션 수”

 X  세션 수  1
 SHORT_SRV  CLB_GOAL=SHORT
RLB_GOAL=NONE

DBCP

커넥션 시,

“CPU 사용률(%)”

X 낮을수록 좋음
최대값

 

(CPU수*5000) 가변적 ONLINE_DBCP

CLB_GOAL=SHORT

RLB_GOAL=SERVICE_TIME
DBCP

커넥션 시,

“CPU 사용률(%)”

X 상동 가변적 ONLINE_UCP CLB_GOAL=SHORT
RLB_GOAL=SERVICE_TIME
UDP

커넥션 시,

“CPU 사용률(%)”
런타임 시,

“콜당 수행 시간”

O 상동 가변적

 

글을 마치며


4회의 연재를 통해 로드 밸런싱의 기본 개념 및 동작 원리를 살펴보았습니다. 테스트를 통해 원리를 검증 또는 역 추적한다는 것은 사실 굉장히 시간 소모적이고 위험할 수도 있는 (즉, 엉뚱한 결론에 이르는) 일입니다. 예를 들어, ONS.JAR 없이 UCP를 테스트했다면 지금과는 아주 상이한 결과가 나왔을 겁니다. 하지만, 다양한 역 추적 기법 (쿼리, 트레이스, OS 명령어 등)을 통해 원리를 이해해 나가는 과정은 재미있는 일임에 분명합니다. 다음 시간의 주제는 Failover 입니다. 다음 시간에 뵙겠습니다.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s