Q. 순차적으로 실행하도록 설계된 배치 어플리케이션의 병렬 처리 방법

{code:noneborderStyle=solid}
Create procedure process_data
As
Begin
For x in ( select * from some_table )
Perform complex process on X
Update some other table, or insert the record somewhere else
End loop
end
{code}

병렬 파이프라인 함수

테스트
{code:sqlborderStyle=solid}

-- 테이블 생성
BIG_TABLE@THREE >create table t1
2 as
3 select object_id id, object_name text
4 from all_objects;

Table created.

BIG_TABLE@THREE >begin
dbms_stats.set_table_stats
( user, 'T1', numrows=>10000000,numblks=>100000 );
end;
/ 2 3 4 5

PL/SQL procedure successfully completed.

BIG_TABLE@THREE >create table t2
2 as
3 select t1.*, 0 session_id
4 from t1
5 where 1=0;

Table created.

-- 파이프라인 함수 변환값을 위한 오브젝트 타입
BIG_TABLE@THREE >CREATE OR REPLACE TYPE t2_type
2 AS OBJECT (
3 id number,
4 text varchar2(30),
5 session_id number
6 )
7 /

Type created.

BIG_TABLE@THREE >create or replace type t2_tab_type
2 as table of t2_type
3 /

Type created.

-- 파이프라인 함수 생성 : 실패
BIG_TABLE@THREE >create or replace
function parallel_pipelined( l_cursor in sys_refcursor )
2 3 return t2_tab_type
4 pipelined
5 parallel_enable ( partition l_cursor by any )
is
l_session_id number;
6 7 8 l_rec t1%rowtype;
9 begin
10 select sid into l_session_id
11 from v$mystat
12 where rownum =1;
13 loop
14 fetch l_cursor into l_rec;
15 exit when l_cursor%notfound;
16 -- complex process here
17 pipe row(t2_type(l_rec.id,l_rec.text,l_session_id));
18 end loop;
19 close l_cursor;
20 return;
21 end;
22 /

Warning: Function created with compilation errors.

|

h1. Do-It-Yourself 병렬
* 11gR2 이상의 내장패키지인 DBMS_PARALLEL EXECUTE를 사용해서 데이터를 여러 작은 단위로 분할해 처리함으로써 SQL 또는 PL/SQL문을 병렬로 실행할 수 있다.

|| 테스트||
| {code:sql|borderStyle=solid}
BIG_TABLE@THREE >select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
PL/SQL Release 11.2.0.3.0 - Production
CORE    11.2.0.3.0      Production
TNS for Linux: Version 11.2.0.3.0 - Production
NLSRTL Version 11.2.0.3.0 - Production

-- rowid 조건 추가하여 프로시저 생성
BIG_TABLE@THREE >create or replace
  2  procedure serial( p_lo_rid in rowid, p_hi_rid in rowid )
  3  is
  4  begin
  5      for x in ( select object_id id, object_name text
  6                   from big_table
  7                  where rowid between p_lo_rid
  8                                  and p_hi_rid )
  9      loop
 10          -- complex process here
 11          insert into t2 (id, text, session_id )
 12          values ( x.id, x.text, sys_context( 'userenv', 'sessionid' ) );
    end loop;
end;
/

Procedure created.

-- create_chunks_by_rowid 프로시저를 사용하여 rowid범위로 테이블을 나누어 수행한다.
-- 10000블록이하의 청크 단우로 나누고, rowid범위를 생성하기 위해 블록의 개수를 카운트한다. 
BIG_TABLE@THREE >begin
  2      dbms_parallel_execute.create_task('PROCESS BIG TABLE');
  3      dbms_parallel_execute.create_chunks_by_rowid
  4      ( task_name   => 'PROCESS BIG TABLE',
  5        table_owner => user,
  6        table_name  => 'BIG_TABLE',
  7        by_row      => false,
  8        chunk_size  => 10000 );
  9  end;
 10  /

PL/SQL procedure successfully completed.

-- 청크 정보와 수를 확인 ( 총 213개 )
BIG_TABLE@THREE >select *
from (
   select chunk_id, status, start_rowid, end_rowid
   from dba_parallel_execute_chunks
   where task_name = 'PROCESS BIG TABLE'
   order by chunk_id
 )
where rownum <= 5
/

  CHUNK_ID STATUS               START_ROWID        END_ROWID
---------- -------------------- ------------------ ------------------
         1 UNASSIGNED           AAAJd3AAEAAAASAAAA AAAJd3AAEAAAASHCcP
         2 UNASSIGNED           AAAJd3AAEAAAASIAAA AAAJd3AAEAAAASPCcP
         3 UNASSIGNED           AAAJd3AAEAAAASQAAA AAAJd3AAEAAAASXCcP
         4 UNASSIGNED           AAAJd3AAEAAAASYAAA AAAJd3AAEAAAASfCcP
         5 UNASSIGNED           AAAJd3AAEAAAASgAAA AAAJd3AAEAAAASnCcP

-- 태스크 실행 : 4개의 JOB이 생성
BIG_TABLE@THREE >begin
    dbms_parallel_execute.run_task
    ( task_name      => 'PROCESS BIG TABLE' ,
      sql_stmt       => 'begin serial( :start_id, :end_id ); end;',
      language_flag  => DBMS_SQL.NATIVE,
      parallel_level => 4 );
end;
/ 

-- 실행 결과 : STATUS 갱신 ( PROCESSED/PROCESSED_WITH_ERROR )
BIG_TABLE@THREE >select *
from (
   select chunk_id, status, start_rowid, end_rowid
   from dba_parallel_execute_chunks
   where task_name = 'PROCESS BIG TABLE'
   order by chunk_id
 )
where rownum <= 5
/

  CHUNK_ID STATUS               START_ROWID        END_ROWID
---------- -------------------- ------------------ ------------------
         1 PROCESSED            AAAJd3AAEAAAASAAAA AAAJd3AAEAAAASHCcP
         2 PROCESSED            AAAJd3AAEAAAASIAAA AAAJd3AAEAAAASPCcP
         3 PROCESSED            AAAJd3AAEAAAASQAAA AAAJd3AAEAAAASXCcP
         4 PROCESSED            AAAJd3AAEAAAASYAAA AAAJd3AAEAAAASfCcP
         5 PROCESSED            AAAJd3AAEAAAASgAAA AAAJd3AAEAAAASnCcP

-- 병렬 서버의 처리량 비교
BIG_TABLE@THREE >select session_id, count(*)
  2  from t2
  3  group by session_id
  4  order by session_id;

SESSION_ID   COUNT(*)
---------- ----------
   1003051    4463786
   1003052    4181209
   1003053    4105086
   1003054    4389247


|

전통적인 Do-It-Yourself 병렬 처리

  • rowid를 사용하여 중복되지 않는 범위로 테이블 분할
테스트
{code:sqlborderStyle=solid}

BIG_TABLE@THREE >drop table t2;

Table dropped.

BIG_TABLE@THREE >create table t2
as
select object_id id, object_name text, 0 session_id
from big_table
where 1=0; 2 3 4 5

Table created.

– JOB에 입력값을 전달할 파라미터 테이블, ROWID범위를 구하기 위해 조회
BIG_TABLE@THREE >create table job_parms
( job number primary key,
lo_rid rowid,
hi_rid rowid
)
/ 2 3 4 5 6

Table created.

--
BIG_TABLE@THREE >create or replace
2 procedure serial( p_job in number )
3 is
4 l_rec job_parms%rowtype;
5 begin
6 select * into l_rec
7 from job_parms
8 where job = p_job;
9
10 for x in ( select object_id id, object_name text
11 from big_table
12 where rowid between l_rec.lo_rid
13 and l_rec.hi_rid )
14 loop
15 -- complex process here
16 insert into t2 (id, text, session_id )
17 values ( x.id, x.text, p_job );
18 end loop;
19
20 delete from job_parms where job = p_job;
21 commit;
22 end;
23 /

Procedure created.

-- 스케줄링, 분석함수를 사용하여 테이블 분할, DBMS_JOB을 사용해서 해당 ROWID 범위의 데이터를 처리하기 위한 JOB을 SUMMIT
BIG_TABLE@THREE >declare
2 l_job number;
3 begin
4 for x in (
5 select dbms_rowid.rowid_create
6 ( 1, data_object_id, lo_fno, lo_block, 0 ) min_rid,
7 dbms_rowid.rowid_create
8 ( 1, data_object_id, hi_fno, hi_block, 10000 ) max_rid
9 from (
10 select distinct grp,
11 first_value(relative_fno)
12 over (partition by grp order by relative_fno, block_id
13 rows between unbounded preceding and unbounded following) lo_fno,
14 first_value(block_id )
15 over (partition by grp order by relative_fno, block_id
16 rows between unbounded preceding and unbounded following) lo_block,
17 last_value(relative_fno)
18 over (partition by grp order by relative_fno, block_id
rows between unbounded preceding and unbounded following) hi_fno,
19 20 last_value(block_id+blocks-1)
21 over (partition by grp order by relative_fno, block_id
22 rows between unbounded preceding and unbounded following) hi_block,
23 sum(blocks) over (partition by grp) sum_blocks
24 from (
25 select relative_fno,
26 block_id,
27 blocks,
28 trunc( (sum(blocks) over (order by relative_fno, block_id)-0.01) /
29 (sum(blocks) over ()/8) ) grp
30 from dba_extents
31 where segment_name = upper('BIG_TABLE')
32 and owner = user order by block_id
33 )
34 ),
35 (select data_object_id
36 from user_objects where object_name = upper('BIG_TABLE') )
37 )
38 loop
39 dbms_job.submit( l_job, 'serial(JOB);' );
40 insert into job_parms(job, lo_rid, hi_rid)
41 values ( l_job, x.min_rid, x.max_rid );
42 end loop;
43 end;
44 /

PL/SQL procedure successfully completed.

– JOB스케줄 확인
BIG_TABLE@THREE >select * from job_parms;

JOB LO_RID HI_RID



--



--



--
3 AAAJd3AAEAAAPSAAAA AAAJd3AAEAAAWx/CcQ
4 AAAJd3AAFAABfsAAAA AAAJd3AAFAABnr/CcQ
5 AAAJd3AAEAAAkyAAAA AAAJd3AAFAABfr/CcQ
6 AAAJd3AAEAAAWyAAAA AAAJd3AAEAAAcx/CcQ
7 AAAJd3AAEAAAcyAAAA AAAJd3AAEAAAkx/CcQ
8 AAAJd3AAEAAAASAAAA AAAJd3AAEAAAHx/CcQ
9 AAAJd3AAEAAAHyAAAA AAAJd3AAEAAAPR/CcQ
10 AAAJd3AAGAAAACAAAA AAAJd3AAGAAAIB/CcQ

 |

-- 병렬 서버 작업량 조회
!1.JPG!

* 내장 분석 함수 NTILE를 사용해서 중복되지  않는 열개의 기본 키 범위를 생성하는 방법
| {code:sql|borderStyle=solid}

BIG_TABLE@THREE >select nt, min(id), max(id), count(*)
from (
    select id, ntile(10) over (order by id) nt
    from big_table
    )
group by nt 
order by nt;

        NT    MIN(ID)    MAX(ID)   COUNT(*)
---------- ---------- ---------- ----------
         1          1     856967    1713933
         2     856967    1713933    1713933
         3    1713934    2570900    1713933
         4    2570900    3427866    1713933
         5    3427867    4284833    1713933
         6    4284833    5141799    1713933
         7    5141800    5998766    1713933
         8    5998766    6855732    1713933
         9    6855733    8286068    1713932
        10    8286069   10000000    1713932

10 rows selected.


|