벤치마크 성격
  • (1)아이디어 또는 접근법 테스트를 위한 벤치마크
  • (2)디자인된 애플리케이션이 실제로 동작하는지를 확인하기 위하여 수행되는 벤치마크
  • (3)프로젝트 초기부터 갖게 되는 구체적인 메트릭(사용자 정의 프로시저의 형태)
  • (4)반드시 현실이 반영되어야 한다는 것을 이해하는 것이 중요하다.
  • (5)반드시 대상 규격을 이용하여 디자인, 테스트 그리고 구현되어야 한다.
  • (6)개인적인 것으로서 대상 애플리케이션마다 고유하다.


8.1 소규모 벤치마킹

소규모 벤치마킹의 성격
  • (1)특정 테크닉의 확장성이 일부 다른 테크닉에 비해 훨씬 우수하다는 것을 증명하는 테스트 (접근법 A가 접근법 B에 비해 우수, 열등, 또는 동일하다는 것을 양적으로 증명)
  • (2)고립된 환경, 즉 단일 사용자 데이터베이스 환경에서 가장 잘 동작한다.
  • (3)테스트 도중 다른 세션이 시스템에 부하를 주지 않아야 하며 일이 진행되는 동안 차단되지도 않아야 한다.


8.1.1 간단한 테스트 도구를 사용하라

Runstats : 수행(경과) 시간, 파스 호출과 같은 시스템 통계, 그리고 래칭을 측정한다.
  • (1)시스템 통계 : 각 접근법이 뭔가를 수행한 횟수와 두 접근법의 차이를 나란히 보여 준다.
  • (2)래칭 정보 : 일종의 상호 배제 또는 잠금 메커니즘(직렬화 장치, 동시성을 금지)


테이블에 데이터를 삽입하는 데 사용되는 두 개의 서로 다른 접근법의 차이를 테스트
  • 첫번째 접근법에서는 SQL 문에 개체 틀의 역할을 하는 바인드 변수를 사용함으로써 SQL 문이 반복적으로 사용될 수 있도록 허용
  • 두번째 접근법에서는 바인드 변수를 사용하지 않을 것이다.


(1)V$ 테이블 액세스를 위한 뷰의 생성

create or replace view stats
as select 'STAT...' || a.name name
         ,b.value
     from v$statname a
         ,v$mystat b
    where a.statistic# = b.statistic#
   union all
   select 'LATCH.' || name
         ,gets
     from v$latch;



(2)통계 수집용 임시 테이블 생성

create global temporary table run_stats(
  runid varchar2(15)
, name  varchar2(80)
, value int
) on commit preserve rows;



(3)Runstats 패키지 생성

create or replace package runstats_pkg
as
    procedure rs_start;                                               --Runstats가 시작되면서 호출된다.
    procedure rs_middle;                                              -- 예상대로 도중에 호출된다.
    procedure rs_stop( p_difference_threshold in number default 0 );  -- 작업을 마치고 보고서를 인쇄한다.
end;
/



(4)Runstats 패키지 본문 생성

-- 실행의 경과 시간을 기록하는 데 사용되는 몇 가지 글로벌 변수로 시작
create or replace package body runstats_pkg
as

g_start number;
g_run1  number;
g_run2  number;

-- RS_START 루틴은 단순히 테이블을 차지하고 있는 통계를 지우고 "사용 전" 통계와 래치로 이 테이블을 채운다
-- 그런 다음 현재 시간 값(경과된 시간을 1/100초 단위로 계산하기 위하여 사용되는 정렬 시계)을 갈무리한다.
procedure rs_start
is
begin
    delete from run_stats;

    insert into run_stats
    select 'before', stats.* from stats;

    g_start := dbms_utility.get_time;
end;
-- RS_MIDDLE 루틴은 테스트의 첫 번째 실행에 대한 경과 시간을 G_RUN1에 기록한 후 현재의 통계 및 래치의 집합을 삽입
-- 이전에 RS_START에서 저장된 값에서 이들 값을 빼면 첫 번째 방법이 사용한 래치와 커서(통계)의 수 등을 알 수 있다.
procedure rs_middle
is
begin
    g_run1 := (dbms_utility.get_time-g_start);

    insert into run_stats
    select 'after 1', stats.* from stats;
    g_start := dbms_utility.get_time;

end;
-- RS_STOP 루틴은 다음 실행의 시작 시각을 기록한다.
procedure rs_stop(p_difference_threshold in number default 0)
is
begin
    g_run2 := (dbms_utility.get_time-g_start);

    dbms_output.put_line
    ( 'Run1 ran in ' || g_run1 || ' hsecs' );
    dbms_output.put_line
    ( 'Run2 ran in ' || g_run2 || ' hsecs' );
    dbms_output.put_line
    ( 'run 1 ran in ' || round(g_run1/g_run2*100,2) ||
      '% of the time' );
    dbms_output.put_line( chr(9) );

    insert into run_stats
    select 'after 2', stats.* from stats;

    dbms_output.put_line
    ( rpad( 'Name', 30 ) || lpad( 'Run1', 12 ) ||
      lpad( 'Run2', 12 ) || lpad( 'Diff', 12 ) );

    for x in
    ( select rpad( a.name, 30 ) ||
             to_char( b.value-a.value, '999,999,999' ) ||
             to_char( c.value-b.value, '999,999,999' ) ||
             to_char( ( (c.value-b.value)-(b.value-a.value)), '999,999,999' ) data
        from run_stats a
            ,run_stats b
            ,run_stats c
       where a.name  = b.name
         and b.name  = c.name
         and a.runid = 'before'
         and b.runid = 'after 1'
         and c.runid = 'after 2'
         and (c.value-a.value) > 0
         and abs( (c.value-b.value) - (b.value-a.value) )
               > p_difference_threshold
       order by abs( (c.value-b.value)-(b.value-a.value))
    ) loop
        dbms_output.put_line( x.data );
    end loop;

        dbms_output.put_line( chr(9) );
    dbms_output.put_line
    ( 'Run1 latches total versus runs -- difference and pct' );
    dbms_output.put_line
    ( lpad( 'Run1', 10 ) || lpad( 'Run2', 10 ) ||
      lpad( 'Diff', 10 ) || lpad( 'Pct', 8 ) );

    for x in
    ( select to_char( run1, '9,999,999' ) ||
             to_char( run2, '9,999,999' ) ||
             to_char( diff, '9,999,999' ) ||
             to_char( round( run1/run2*100,2 ), '999.99' ) || '%' data
        from ( select sum(b.value-a.value) run1
                     ,sum(c.value-b.value) run2
                     ,sum( (c.value-b.value)-(b.value-a.value)) diff
                 from run_stats a
                    ,run_stats b
                    ,run_stats c
                where a.name  = b.name
                  and b.name  = c.name
                  and a.runid = 'before'
                  and b.runid = 'after 1'
                  and c.runid = 'after 2'
                  and a.name like 'LATCH%'
                )
    ) loop
        dbms_output.put_line( x.data );
    end loop;
end;

end;
/





(5)실습에 사용할 테이블 생성

create table t ( x varchar2(30) );



(6)두가지 접근법을 구현하는 두 개의 루틴(프로시저) 작성

create or replace procedure method1( p_data in varchar2 )
is
begin
   execute immediate
   'insert into t(x) values(:x)'
   using p_data;
end method1;
/
create or replace procedure method2( p_data in varchar2 )
is
begin
   execute immediate
   'insert into t(x) values( ''' || replace( p_data,'''','''''' ) || ''' )';
end method2;
/



(7)두가지 접근법의 성능을 테스트

set serveroutput on size 1000000;
-- dbms_output.put_line 확인
-- ORA-2000: buffer overflow, limit of 2000 bytes 오류 해결

begin
   runstats_pkg.rs_start;
   for i in 1 .. 10000
   loop
      method1( 'row ' || I );
   end loop;
   runstats_pkg.rs_middle;
   for i in 1 .. 10000
   loop
      method2( 'row ' || I );
   end loop;
   runstats_pkg.rs_stop;
end;
/




  • 이 보고서를 통해 바인드 변수를 사용하는 method1이 method2가 소비한 시간의 58%에 수행됨을 알 수 있다.
  • 래치 부문에서도 바인드 변수를 사용하지 않은 방법이 훨씬 많은 래치(확장성을 저해)를 사용하였다.
method1mothod2차이
래치 합계365471797749432278
  • 바인드 변수를 사용하는 것이 빠를 뿐만 아니라 확장성이 훨씬 우수하다(동시 사용자수면에서 유리)


8.1.2 기타 벤치마크 툴

소규모 벤치마킹에 활용될 수 있는 툴들
  • (1)TKPROF, TIMED_STATISTICS, SQL_TRACE 프로그램이 무엇을 어떻게 수행하는지를 정확하게 볼 수 있는 훌륭한 방법
  • (2)DBMS_PROFILER 정밀 PL/SQL 코드 튜닝용
  • (3)Explain Plan 쿼리가 무엇을 할 것인지를 볼 수 있음
  • (4)Autotrace 쿼리가 이미 수행한 것들을 볼 수 있음


8.2 대규모 벤치마킹

  • "대규모 벤치마킹"은 전체적으로 확장성을 고려하여 시스템을 테스트한다는 것을 의미한다.
벤치마킹을 하지 않아 시스템이 제대로 동작하지 않는 사례
  • (1)시스템을 전혀 테스트하지 않았다.
  • (2)비현실적인 수의 사용자 환경에서 시스템을 테스트하였다.
  • (3)소규모 데이터베이스를 가지고 시스템을 테스트하였다.


8.2.1 전체를 대표할 수 있는 양의 데이터로 테스트하라

  • 사이트의 DB에는 40,000개 이상의 계정이 있었지만, 개발자는 10개 계정으로 테스트
  • \-> 실세계 상황을 고려한 현실적인 테스트의 중요성


8.2.2 실질적인 입력으로 테스트하라

  • 중간 계층의 코드가 다른 소프트웨어로부터 생성된 자바 기반의 3계층 애플리케이션 벤치마크 사례
  • SELECT * FROM USER_TABLE WHERE USERNAME = :X FOR UPDAT
  • \-> 컨테이너가 사용자 수준에서 트랜잭션을 직렬화함에 따라 시스템을 (심하게) 사용하는 한 사용자가 트랜잭션을 실행하기 위하여 굉장히 긴 줄에서 기다리게 되었다.
  • \-> 벤치마킹에 영향을 줄 수 있는 많은 요소를 충분히 고려하고 현실적인 테스트 환경을 설정한 후에 목표를 달성했음.


8.2.3 반드시 결과를 검증하라

  • 벤치마크를 통해 10,000명 이상의 사용자에게 서비스를 원할히 제공하는 것으로 판명된 프로덕션 시스템에서 동시 사용자 200명 이하에서 심각한 성능 장애가 발생한 사례
  • \-> 메트릭을 전혀 기록하지 않았고, 코드에 디버그용 코드를 삽입하지 않아 트랜잭션이 제대로 실행되는지 알수 없었음.
  • 바인드 변수를 사용하지 않아 사용자와 세션을 추가함에 따라 경합이 증가하고 응답시간이 증가했음.


8.2.4 벤치마킹을 허드렛일로 여기지 말라

  • 벤치마킹은 개발에 있어서 가장 중요한 단계 중 하나이다.
  • 고성능을 실현하려는 점에서 보면 올바른 디자인보다도 중요할 수 있다.
  • 벤치마킹을 하게 되면 곤란한 상황을 피할 수 있을 뿐만 아니라 시간과 비용을 절감할 수 있다.