메뉴 건너뛰기

bysql.net

5. 병렬 처리에 관한 기타 상식

2011.06.26 20:20

balto 조회 수:28160

(1) Direct Path Read

- 버퍼 캐시 히트율이 낮은 대용량 데이터를 건건이 버퍼 캐시를 거쳐 읽는다면 오히려 성능이 나빠지게 마련이다.

- 오라클은 병렬 방식 + Full Scan 할 때는 버퍼 캐시를 거치지 않고 곧바로 PGA영역으로 읽어 들이는 Direct Path Read 방식을 사용한다.

- 그러나 버퍼캐시에 충분히 적재될 크기의 중소형 테이블을 병렬쿼리로 읽을 때 오히려 성능이 나빠지는 경우가 있는데, 버퍼경합이 없는 한 Disk I/O가 메모리 I/O보다 빠를 수 없기 때문이다. 게다가 Direct Path Read를 하려면 메모리와 디스크간 동기화를 맞추기 위해서 체크포인트가 먼저 수행해야 하기 때문이다.

 

directpathread.jpg  

(2) 병렬 DML

- 병렬처리를 가능하게 하려면 쿼리, DML, DDL시 다음과 같은 선처리가 필요하다.

   alter session enable parallel query; -- (default)

   alter session enable parallel dml;

   alter session enable parallel ddl; -- (default)

- parallel query와 parallel ddl은 기본적으로 활성화 되어 있지만 parallel dml은 명시적으로 활성화 해주어야 한다.

- 주의할 점

병렬 DML을 수행 할 때 Exclusive 모드 테이블 lock이 걸린다는 사실이며, 다른 트랜잭션이 DML을 수행하지 못하므로 운영환경을 고려하여 사용해야 함. 

[실험] (실행계획 ch07_03.hwp 파일 참조)

create table t

partition by range(no) (

partition p1 values less than(25000)

, partition p2 values less than(50000)

, partition p3 values less than(75000)

, partition p4 values less than(maxvalue)

)

as

select rownum no, lpad(rownum, 10, '0') no2 from dual

connect by level <= 100000;

 

SQL> explain plan for

2 update /*+ parallel(t 4) */ t set no2 = lpad(no,5,'0');

---------------------------------------------------------------------------------------

| Id | Operation | Name | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |

---------------------------------------------------------------------------------------

| 0 | UPDATE STATEMENT | | | | | | |

| 1 | UPDATE | T | | | | | |

| 2 | PX COORDINATOR | | | | | | |

| 3 | PX SEND QC (RANDOM)| :TQ10000 | | | Q1,00 | P->S | QC (RAND) |

| 4 | PX BLOCK ITERATOR | | 1 | 4 | Q1,00 | PCWC | |

| 5 | TABLE ACCESS FULL| T | 1 | 4 | Q1,00 | PCWP | |

---------------------------------------------------------------------------------------

 

SQL> alter session enable parallel dml;

세션이 변경되었습니다.

SQL> explain plan for

2 update /*+ parallel(t 4) */ t set no2 = lpad(no,5,'0');

---------------------------------------------------------------------------------------

| Id | Operation | Name | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |

---------------------------------------------------------------------------------------

| 0 | UPDATE STATEMENT | | | | | | |

| 1 | PX COORDINATOR | | | | | | |

| 2 | PX SEND QC (RANDOM) | :TQ10000 | | | Q1,00 | P->S | QC (RAND) |

| 3 | UPDATE | T | | | Q1,00 | PCWP | |

| 4 | PX BLOCK ITERATOR | | 1 | 4 | Q1,00 | PCWC | |

| 5 | TABLE ACCESS FULL| T | 1 | 4 | Q1,00 | PCWP | |

---------------------------------------------------------------------------------------

- 첫 번째 SQL문은 병렬서버가 테이블을 읽고 갱신할 주소만 QC에 전달하고 update는 QC가 실행한다. 두 번째 SQL 문은 병렬서버가 update를 실행하고 갱신한 건수만 QC에 전달한다.

- 오라클 9iR1까지는 한 세그먼트를 두 개 이상의 프로세스가 동시에 갱신할 수 없었다. 따라서 파티션되지 않은 테이블이라면 병렬로 갱신할 수 없었고, 파티션 테이블일 때라도 병렬도를 파티션 개수 이하로만 지정할 수 있었다.

- 오라클 9iR2부터는 블록기반 Granule로 바뀌었다.

 

(3) 병렬 인덱스 스캔

- Index Fast Full Scan이 아닌 한 인덱스는 기본적으로 병렬로 스캔 할 수 없다.

- 파티션된 인덱스일 때 병렬 스캔이 가능하며 파티션 기반 Granule이므로 병렬도는 파티션개수 이하로만 지정가능 함.

fig7-12.jpg   

 

(4) 병렬 NL조인

- 병렬조인은 항상 Table Full Scan을 이용한 해쉬조인 또는 소트머지 조인으로 처리된다고 생각하기 쉽다.

  하지만 인덱스 스캔을 기반으로한 병렬 NL조인도 가능

[실험] (실행계획 ch07_04.hwp 파일 참조)

create table emp

partition by range(sal) (

partition p1 values less than(1000)

, partition p2 values less than(2000)

, partition p3 values less than(3000)

, partition p4 values less than(MAXVALUE) )

as select * from scott.emp ;

create index emp_sal_idx on emp(sal) local;

 

create table dept as select * from scott.dept;

alter table dept add constraint dept_pk primary key(deptno);

 

SQL> select /*+ ordered use_nl(d) full(e) parallel(e 2) */ *

2 from emp e, dept d

3 where d.deptno = d.deptno and e.sal >= 1000;

 

---------------------------------------------------------------------------------------

| Id | Operation | Name | Pstart| Pstop | TQ |IN-OUT| PQ Distrib |

---------------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | | | | | |

| 1 | PX COORDINATOR | | | | | | |

| 2 | PX SEND QC (RANDOM) | :TQ10000 | | | Q1,00 | P->S | QC (RAND) |

| 3 | NESTED LOOPS | | | | Q1,00 | PCWP | |

| 4 | PX BLOCK ITERATOR | | 2 | 4 | Q1,00 | PCWC | |

|* 5 | TABLE ACCESS FULL| EMP1 | 2 | 4 | Q1,00 | PCWP | |

| 6 | TABLE ACCESS FULL | DEPT | | | Q1,00 | PCWP | |

---------------------------------------------------------------------------------------

 

- Parallel Full Scan은 블록 기반 Granule이 사용되므로 병렬도는 파티션 개수보다 더 클 수 있다. 단 병렬도를 너무 크게 잡으면 아무 일도 안 하는 노는 프로세스가 생길 수 있으므로 주의해야 한다.

 

fig7-13.jpg 

 

■ 병렬 인덱스 스캔으로 드라이빙하는 경우

- 파티션된 인덱스부터 드라이빙하여 병렬 NL조인을 수행

SQL> select /*+ ordered use_nl(d) index(e emp_sal_idx)

2 parallel_index(e emp_sal_idx 3) */ *

3 from emp1 e, dept d

4 where d.deptno = d.deptno and e.sal >= 1000;

 

--------------------------------------------------------------------------------------

| Id | Operation | Name | Pstart| Pstop | TQ |

--------------------------------------------------------------------------------------

| 0 | SELECT STATEMENT | | | | |

| 1 | PX COORDINATOR | | | | |

| 2 | PX SEND QC (RANDOM) | :TQ10000 | | | Q1,00 |

| 3 | NESTED LOOPS | | | | Q1,00 |

| 4 | PX PARTITION RANGE ITERATOR | | 2 | 4 | Q1,00 |

| 5 | TABLE ACCESS BY LOCAL INDEX ROWID| EMP1 | 2 | 4 | Q1,00 |

|* 6 | INDEX RANGE SCAN | EMP_SAL_IDX | 2 | 4 | Q1,00 |

| 7 | TABLE ACCESS FULL | DEPT | | | Q1,00 |

--------------------------------------------------------------------------------------

 

- 인덱스를 드라이빙으로 병렬처리하려면 드라이빙 인덱스가 반드시 파티션인덱스여야 한다. 드라이빙 테이블(emp)과 dept 테이블은 파티션과 상관없다.

- 인덱스를 드라이빙한 병렬 NL조인에는 파티션 기반 Granule이 사용되므로 병렬도가 파티션 개수를 초과할 수 없다.

- 여기서는 세 개 파티션만 액세스하므로 병렬도를 3 이상 줄 수 없다. 만약 병렬도를 2로 지정한다면 각각 하나씩 처리하다가 먼저 일을 마친 프로세스가 나머지 하나를 더 처리한다.

 

fig7-14.jpg

 

■ 병렬 NL 조인의 효용성

(사례) - 아래 조건들을 만족할 때 효율적이다.

- outer와 inner 테이블 모두 대용량이다.(어느 한쪽이 작으면 병렬 해시조인 사용)

- outer 테이블에 사용된 특정 조건의 선택도가 낮은데 그 컬럼의 인덱스가 없다.

- inner 쪽 조인 컬럼에 인덱스가 있다

- 수행빈도가 낮다(높으면 인덱스를 만들어 주었을 것이다)

(5) 병렬 쿼리와 스칼라 서브쿼리

- QC의 트레이스는 user_dump_dest에 생성되고, 병렬서버의 트레이스는 background_dump_dest에 생성된다.

- 병렬처리 시 Trace 결과를 보고 스칼라 서브쿼리의 주체가 어떤 프로세스인지 알 수 있다.

- QC에 스칼라 서브쿼리 수행 통계가 나타나지 않는 경우 병렬서버 프로세스가 스칼라 서브쿼리를 수행 것임을 알 수 있고, 병렬서버 프로세스들이 order by를 위한 정렬처리를 함과 동시에 스칼라 서브쿼리를 하면서 sort area(temp)에 중간 결과집합을 담는 것을 알 수 있다.

- 병렬쿼리는 대부분 Full Table Scan을 하는데 중간에 Random액세스 위주의 스칼라 서브쿼리까지 수행한다면 수행 속도를 크게 떨어뜨린다.

- 병렬쿼리에서는 스칼라 서브쿼리를 일반 조인문장으로 변경하여 Full Scan + Parallel 방식으로 처리하는 것이 매우 중요한 튜닝기법 중 하나가 될 수 있다.

 

(6) 병렬쿼리와 사용자 정의 함수

- 함수에 parallel_enable 키워드를 사용하든 안하든 병렬 수행이 가능하다.

create or replace function getDname(p_deptno number)

return varchar2

parallel_enable

is

l_dname dept.dname%type;

begin

select dname into l_dname from dept where deptno = p_deptno;

return l_dname;

end;

/

 

select /*+ parallel(emp 2) */ empno, ename, job, sal, getDname(deptno)

from emp;

- getName() 함수가 세션 변수를 사용하지 않았으므로 parallel_enable에 관계없이 병렬수행이 가능하다.

 

■ parallel_enable 키워드의 역할

- SQL 수행 결과는 병렬로 수행 되는지 여부와 관계없이 항상 일관된 결과를 반환해야 한다.

- 세션변수를 참조하는 함수는 병렬로 실행했을 때

- 패키지 변수는 세션 레벨에서만 유효하며 다른 세션과 값을 공유하지 못한다. 패키지변수를 가진 함수는 병렬 호출시 결과가 다를 수 있다. 일관성이 보장되지 않기 때문에 오라클은 병렬 수행을 거부한다. 그럼에도 불구하고 사용자가 병렬 수행을 원할 때 사용하는 키워드가 parallel_enable이다.

- 즉, 직렬과 비교해 병렬처리 시 수행결과가 달라질 수 있음에도 parallel_enable을 선언하면 오라클은 사용자의 지시에 따라 함수를 병렬로 실행할 수 있도록 허용하며 결과에 대한 책임은 사용자의 몫이라는 의미이다.

 

(7) 병렬쿼리와 Rownum

- SQL에 rownum을 포함하면 쿼리문을 병렬로 실행하는 데에 제약을 받게 되므로 주의해야 함.

SQL> select /*+ parallel(t1 24) */ no, no2

2 from t1

3 order by no2;

 

----------------------------------------------------------------

| Id | Operation | Name |IN-OUT| PQ Distrib |

----------------------------------------------------------------

| 0 | SELECT STATEMENT | | | |

| 1 | PX COORDINATOR | | | |

| 2 | PX SEND QC (ORDER) | :TQ10001 | P->S | QC (ORDER) |

| 3 | SORT ORDER BY | | PCWP | |

| 4 | PX RECEIVE | | PCWP | |

| 5 | PX SEND RANGE | :TQ10000 | P->P | RANGE |

| 6 | PX BLOCK ITERATOR | | PCWC | |

| 7 | TABLE ACCESS FULL| T1 | PCWP | |

----------------------------------------------------------------

 

SQL> select /*+ parallel(t1 24) */ no, no2, rownum

2 from t1

3 order by no2;

 

---------------------------------------------------------------

| Id | Operation | Name |IN-OUT| PQ Distrib |

---------------------------------------------------------------

| 0 | SELECT STATEMENT | | | |

| 1 | SORT ORDER BY | | | |

| 2 | COUNT | | | |

| 3 | PX COORDINATOR | | | |

| 4 | PX SEND QC (RANDOM)| :TQ10000 | P->S | QC (RAND) |

| 5 | PX BLOCK ITERATOR | | PCWC | |

| 6 | TABLE ACCESS FULL| T1 | PCWP | |

---------------------------------------------------------------

 

- 각 프로시져가 P->P로 동작하여 정렬부담을 나누어가져야 함에도 불구하고 QC가 정렬을 전담하는 상황이 발생함

- 병렬 DML 문장에도 rownum을 사용하는 순간 병렬처리에 제약을 받는다.

alter session enable parallel dml;

 

explain plan for

update /*+ parallel(t 4) */ t set no2 = lpad(rownum, 5, '0');

 

(실행계획 ch07_06.hwp 파일 참조)

(8) 병렬쿼리 시 주의사항

 

■ 병렬처리가 바람직한 경우

- 동시 사용자 수가 적은 애플리케이션 환경에서(야간 배치, DW, OLAP) 직렬로 처리할 때 보다 성능 개선효과가 확실할 때.

- OLTP성 시스템 환경이더라도 작업을 빨리 완료함으로써 직렬로 처리할 때보다 오히려 전체적인 시스템 리소스 사용률을 감소시킬 수 있을 때(수행 빈도가 낮을 때), 데이터 마이그레이션 등.

 

■ 병렬쿼리와 관계된 주의사항

- workarea_size_policy를 manual로 설정한다면, 사용자가 지정한 sort_area_size가 모든 병렬서버에 적용되므로 적절한 sort_area_size를 설정하지 않은 경우 OS레벨에서 과도한 페이징이 발생하고 심할 경우 시스템이 마비될 수 있다.

- 병렬도를 지정하지 않으면 (cpu_count * parallel_threads_per_cpu) 만큼의 병렬 프로세스가 할당되어 의도하지 않은 수의 프로세스가 작동하게 된다. adaptive multiuser 기능을 사용하는 경우가 아니면 반드시 병렬도를 지정하자.

- 실행계획에 P->P가 나타날 때는 지정한 병렬도의 두 배수만큼 병렬 프로세스가 필요하다는 것이다.

- 쿼리 블록마다 병렬도를 다르게 지정하면 여러가지 우선순위와 규칙에 따라 최종병렬도가 결정되어 사용된다. 복잡하니 결국 쿼리 작성 시 병렬도를 모두 같게 지정하는 것이 바람직하다.

- parallel 힌트를 사용할 때는 반드시 Full 힌트도 함께 사용하는 것이 바람직하다. 간혹 옵티마이져에 의해 index 스캔이 선택된 경우 parallel 이 무시되는 경우가 발생하기 때문이다.

- parallel_index 힌트를 사용할 때는 반드시 index 또는 index_ffs 힌트도 함께 사용하는 것이 바람직하다. 옵티마이져에 의해 full table scan 스캔이 선택된 경우 parallel_index 힌트가 무시되는 경우가 발생하기 때문이다.

- 병렬 DML 수행 시 Exclusive 모드로 테이블 lock이 걸리므로 업무 트랜젝션이 발생하는 주간에는 삼가한다.

- 테이블이나 인덱스를 빠르게 생성하려고 parallel 옵션을 사용했다면 작업을 완료하자마자 noparallel로 돌려놓는 것을 잊지 말자.

- 부분범위 처리 방식으로 조회하면서 병렬 쿼리를 사용한 경우 필요한 만큼의 fetch 이후 곧바로 커서를 닫아줘야 한다. (Orange, Toad같은 툴의 경우 리소스를 해제하지 못하고 대기 상태에 있는 경우가 있으므로 select * from dual 로 새로운 쿼리를 수행하여 이전 커서를 닫아 주는 방식을 취할 수 있다.)

 

  • 오라클 고도화 원리와 해법 2 (bysql.net 2011년 1차 스터디)
  • 작성자: 박우창 (balto)
  • 최초작성일: 2011년 6월 29일
  • 본문서는 bysql.net 스터디 결과입니다 .본 문서를 인용하실때는 출처를 밝혀주세요. http://www.bysql.net
  • 문서의 잘못된 점이나 질문사항은 본 문서에 댓글로 남겨주세요. ^^

 

 

번호 제목 글쓴이 날짜 조회 수
35 1. 옵티마이저 file 휘휘 2011.04.18 6064
34 3. 옵티마이저의 한계 - P 휘휘 2011.04.18 3699
33 2. 옵티마이저 행동에 영향을 미치는 요소 balto 2011.04.18 6160
32 3. 옵티마이저의 한계 휘휘 2011.04.19 6698
31 4. 통계정보 Ⅰ file 토시리 2011.04.25 16013
30 6. 히스토그램 오예스 2011.04.25 17389
29 5. 카디널리티 오라클잭 2011.04.27 12919
28 7. 비용 file balto 2011.05.02 4997
27 8. 통계정보 Ⅱ AskZZang 2011.05.04 5913
26 1. 쿼리 변환이란? 운영자 2011.05.16 6258
25 3. 뷰 Merging 오라클잭 2011.05.17 6082
24 2. 서브쿼리 Unnesting 토시리 2011.05.18 2079
23 5. 조건절 이행 file balto 2011.05.30 5465
22 4. 조건절 Pushing 오예스 2011.05.31 17482
21 6. 조인 제거 AskZZang 2011.06.01 5441
20 7. OR-Expansion AskZZang 2011.06.01 8327
19 12. 기타 쿼리 변환 휘휘 2011.06.06 3122
18 10. 실체화 뷰 쿼리로 재작성 오라클잭 2011.06.08 9953
17 11. 집합 연산을 조인으로 변환 오라클잭 2011.06.08 4955
16 2. 소트를 발생시키는 오퍼레이션 file balto 2011.06.12 4847