Post

<오라클 성능 고도화 원리와 해법2> Ch07-01 병렬 처리 기본 개념

오라클 성능 고도화 원리와 해법2 - Ch07-01 병렬 처리 기본 개념

병렬 처리란, SQL문이 수행해야 할 작업 범위를 여러 개의 작은 단위로 나누어 여러 프로세스(또는 쓰레드)가 동시에 처리하는 것을 말한다(그림 7-1). 여러 프로세스가 동시에 작업하므로 대용량 데이터를 처리할 때 수행 속도를 극적으로 단축시킬 수 있다.

병렬 처리와 관련해서 다루어야 할 개념, 원리, 규칙 등이 많지만, 이를 모두 설명하지는 않을 것이다. 필요하다면 오라클 온라인 매뉴얼을 먼저 참조하기 바란다. 본서에서는 오라클 매뉴얼에서 자세히 다루지 않지만 병렬 SQL 튜닝을 위해 반드시 이해해야 할 핵심 원리만을 설명한다.

(1) Query Coordinator와 병렬 서버 프로세스

Query Coordinator(QC)는 병렬 SQL문을 발행한 세션을 말하고, 병렬 서버 프로세스는 실제 작업을 수행하는 개별 세션들을 말한다.

QC의 역할은 다음과 같다:

  1. 병렬 SQL이 시작되면 QC는 사용자가 지정한 병렬도(DOP, degree of parallelism)와 오퍼레이션 종류에 따라 하나 또는 두 개의 병렬 서버 집합(Server Set)을 할당한다. 우선 서버 풀(Parallel Execution Server Pool)로부터 필요한 만큼 서버 프로세스를 확보하고, 부족분은 새로 생성한다.
  2. QC는 각 병렬 서버에게 작업을 할당한다. 작업을 지시하고 일이 잘 진행되는지 관리·감독하는 작업반장 역할이다.
  3. 병렬로 처리하도록 사용자가 지시하지 않은 테이블은 QC가 직접 처리한다. 예를 들어, 아래 실행 계획에서 dept 테이블을 직렬로 읽어 병렬 서버에 전송하는 8~9번 오퍼레이션은 QC의 몫이다.
  4. QC는 각 병렬 서버로부터의 산출물을 통합하는 작업을 수행한다. 예를 들어 집계 함수(sum, count, avg, min, max 등)가 사용된 아래와 같은 병렬 쿼리를 수행할 때, 각 병렬 서버가 자신의 처리 범위 내에서 집계한 값을 QC에게 전송하면 QC가 최종 집계 작업을 수행한다.
  5. QC는 쿼리의 최종 결과 집합을 사용자에게 전송하며, DML일 때는 갱신 건수를 집계해서 전송해준다. 쿼리 결과를 전송하는 단계에서 수행되는 스칼라 서브쿼리도 QC가 수행한다.
1
2
3
4
5
select /*+ ordered use_hash(d) full(d) full(e) noparallel(d) parallel (e 4) */
       count(*), min(sal), max(sal), avg(sal), sum(sal)
from dept d, emp e
where d.loc = 'CHICAGO'
and e.deptno = d.deptno;

병렬 처리에서 실제 QC 역할을 담당하는 프로세스는 SQL문을 발행한 사용자 세션 자신이다.

(2) Intra-Operation Parallelism과 Inter-Operation Parallelism

1
2
3
select /*+ full(고객) parallel(고객 4) */ *
from 고객
order by 고객명;

그림 7-2는 order by를 포함하는 위 병렬 쿼리의 수행 과정을 도식화한 것이다.

이해를 돕기 위해, 8명의 영업사원이 각자 관리하던 명함을 영업팀에서 통합 관리할 목적으로 명함 전체를 이름순으로 정렬하는 경우를 예로 들어보자. 두 가지 방법을 생각해볼 수 있는데, 첫 번째는 8명이 각자 자신의 것을 정렬하고 나면 영업팀에서 이를 최종적으로 머지(Merge)하는 것이다.

두 번째는, 두 개 조로 나누고 역할을 분담해서 서로 다른 작업을 동시에 진행하는 것으로서 그림 7-2와 같은 방식이다(QC는 영업팀장에 해당). 즉, 4명(분배 팀)은 명함을 분배하고, 다른 4명(정렬 팀)은 그것을 받아 정렬하는 역할만 하면 된다.

정렬 팀은 먼저 알파벳(또는 가나다) 순으로 각자 처리할 연속된 범위(A~G, H~M, N~S, T~Z)를 지정받는다. 분배 팀은 영업팀장으로부터 할당받은 명함 뭉치를 각자 들고 고객명에 따라 정해진 정렬 담당자에게 건넨다. 정렬 담당자는 4명의 분배자로부터 자신에게 건네진 명함만 정렬하면 되고, 나중에 이를 머지하는 작업은 아주 쉽다. 이미 정렬된 묶음을 영업팀장이 순서대로 받아서 포개어 놓기만 하면 된다(그림 7-2에서 QC가 이 역할을 담당).

첫 번째 방법은 작업자가 많을수록 나중에 영업팀장이 머지하는 단계에 부하가 걸려 병렬 처리를 극대화하기 어렵다. 오라클이 order by를 병렬로 처리할 때 두 번째 방식을 사용하는 이유다.

이때, 서로 배타적인 범위를 독립적으로 동시에 처리하는 것을 Intra-Operation Parallelism이라고 한다. 4명이 병렬로 자신이 할당받은 명함을 하나씩 상대편에 분배하는 첫 번째 작업과, 병렬로 각자 전달받은 명함을 정렬하는 두 번째 작업 모두 여기에 해당한다. 같은 조(서버 집합)끼리는 서로 데이터를 주고받을 일이 없다.

반면, 명함을 읽어 상대편 조(다른 서버 집합)에 분배하거나 정렬된 결과를 영업팀(QC)에게 전송하는 작업을 병렬로 동시에 진행하는 것을 Inter-Operation Parallelism이라고 하며, 이때는 항상 프로세스 간 통신이 발생한다.

(3) 테이블 큐

Intra-Operation Parallelism은 한 병렬 서버 집합(Server Set)에 속한 여러 프로세스가 처리 범위를 달리하면서 병렬로 작업을 진행하는 것이므로, 집합 내에서는 절대 프로세스 간 통신이 발생하지 않는다. 반면, Inter-Operation Parallelism은 프로세스 간 통신이 발생하고, 메시지 또는 데이터를 전송하기 위한 통신 채널이 필요하다.

쿼리 서버 집합 간(P-P) 또는 QC와 쿼리 서버 집합 간(P-S, S-D) 데이터 전송을 위해 연결된 파이프라인(Pipeline)을 테이블 큐(Table Queue)라고 한다. 그리고 그림 7-3에서 보듯, 각 테이블 큐에 부여된 이름(:TQ10000, :TQ10001, :TQ10002 등)을 테이블 큐 식별자(TQ Identifier)라고 한다.

1
2
3
4
5
6
select /*+ ordered use_hash(e) full(d) noparallel(d) full(e) parallel(e 2)
           pq_distribute(e broadcast none) */
*
from dept d, emp e
where d.deptno = e.deptno
order by e.ename;

그림 7-3을 보면, 쿼리 서버 집합 간(P-P) Inter-Operation Parallelism이 발생할 때는 사용자가 지정한 병렬도(Degree of Parallelism = 2)의 배수(4개)만큼 서버 프로세스가 필요한 것을 알 수 있다. 또한 테이블 큐(:TQ10001)에는 병렬도의 제곱(2² = 4)만큼 파이프라인이 필요하다는 사실도 알 수 있다. 참고로 그림 7-2를 보면, 병렬도가 4이므로 8(=4X2)개의 서버 프로세스를 위해 16(=4²)개의 파이프라인이 형성되었다.

생산자/소비자 모델

테이블 큐에는 항상 생산자(Producer)와 소비자(Consumer)가 존재한다. 그림 7-3을 보면, 처음 dept 테이블을 읽어 분배하는 :TQ10000에서는 QC가 생산자이고 서버 집합 1이 소비자이다. 이어지는 두 번째 테이블 큐 :TQ10001에서는 서버 집합 1이 생산자가 되고, 서버 집합 2가 소비자가 된다. 마지막으로, 정렬된 최종 결과 집합을 전송하는 :TQ10002에서는 서버 집합 2가 생산자가 되고 QC가 소비자가 된다. select 문장에서의 최종 소비자는 항상 QC이다. 그림 7-3에서 보듯 Inter-Operation Parallelism이 나타날 때, 소비자 서버 집합은 from 절에 테이블 큐를 참조하는 서브(Sub) SQL을 가지고 작업을 수행한다.

병렬 실행 계획에서 생산자와 소비자 식별

아래는 앞서 본 쿼리에 대한 실행 계획이다. 10g 이후부터는 이처럼 생산자에 PX SEND, 소비자에 PX RECEIVE가 표시되므로 테이블 큐를 통한 데이터 분배 과정을 좀 더 쉽게 확인할 수 있다.

각 오퍼레이션이 어떤 서버 집합에 속한 병렬 프로세스에 의해 수행되는지는 ‘TQ’ 컬럼에 보이는 서버 집합 식별자를 통해 확인할 수 있다.

  1. QC가 dept 테이블을 읽어 첫 번째 서버 집합(Q1,01)에게 전송한다.
  2. 이어서 첫 번째 서버 집합(Q1,01)은 emp 테이블을 병렬로 읽으면서 앞서 QC에게서 받아둔 dept 테이블과 조인한다. 조인에 성공한 레코드는 바로 두 번째 서버 집합(Q1,02)에게 전송한다.
  3. 마지막으로, 두 번째 서버 집합(Q1,02)은 전송받은 레코드를 정렬하고 나서 QC에게 전송한다.

생산자로부터 소비자로 데이터 재분배가 일어날 때마다 ‘Name’ 컬럼에 테이블 큐(:TQxxxx 형태)가 표시된다.

(4) IN-OUT 오퍼레이션

아래 병렬 쿼리 실행 계획(9)을 보면, 뒤에서 두 번째 컬럼에 테이블 큐를 통한 IN-OUT 오퍼레이션 정보가 출력되는 것을 볼 수 있다.

이는 plan_table을 쿼리할 때 other_tag 컬럼에서 가져온 것이며, 병렬 쿼리를 이해하는 데 매우 중요한 정보를 제공한다. 각각의 의미를 살펴보자.

S→P: PARALLEL_FROM_SERIAL

위 실행 계획에서 9번 오퍼레이션이 여기에 해당하며, QC가 읽은 데이터를 테이블 큐를 통해 병렬 서버 프로세스에게 전송하는 것이다. 맨 우측 PQ Distrib 컬럼에 ‘BROADCAST’라고 표시된 것의 의미는 다음 항목(데이터 재분배)에서 자세히 설명할 것이다.

P→S: PARALLEL_TO_SERIAL

위 실행 계획에서 1번 오퍼레이션이 여기에 해당하며, 각 병렬 서버 프로세스가 처리한 데이터를 QC에게 전송하는 것을 의미한다. 병렬 프로세스로부터 QC로 통신이 발생하므로 Inter-Operation Parallelism에 속한다. 참고로, S→P도 통신이 발생하지만(QC로부터 병렬 프로세스로) 이는 병렬 오퍼레이션이 아니므로 Inter-Operation Parallelism에 속하지 않는다.

맨 우측 PQ Distrib 컬럼에 ‘QC (ORDER)’라고 표시된 것은 QC에게 결과 데이터를 전송할 때 첫 번째 병렬 프로세스로부터 마지막 병렬 프로세스까지 순서대로 진행함을 의미하며, SQL이 ORDER BY 절을 포함할 때 나타난다. 참고로, ORDER BY가 없을 때는 ‘QC (RANDOM)’이라고 표시되며 병렬 프로세스들이 무순위로(작업이 끝나는 순서대로) QC에게 데이터를 전송함을 뜻한다.

P→P: PARALLEL_TO_PARALLEL

데이터를 재분배하는 오퍼레이션으로서, 위 실행 계획에서 2번 단계가 여기에 속한다. 기억할 것은, 실행 계획에 P→P가 나타날 때면 해당 오퍼레이션을 두 개의 서버 집합(Server Set)이 처리한다는 사실이다. 따라서 사용자가 지정한 병렬도의 2배수만큼 병렬 프로세스가 필요하다.

데이터를 정렬(ORDER BY) 또는 그룹핑(GROUP BY)하거나 조인을 위해 동적으로 파티셔닝할 때 사용되며, 첫 번째 병렬 서버 집합이 읽거나 가공한 데이터를 두 번째 병렬 서버 집합에 전송하는 과정에서 병렬 프로세스 간 통신이 발생하므로 Inter-Operation Parallelism에 속한다. 위 실행 계획에서 2번 오퍼레이션 PQ Distrib 컬럼에 ‘RANGE’라고 표시된 것의 의미는 다음 항목(데이터 재분배)에서 자세히 설명할 것이다.

PCWP: PARALLEL_COMBINED_WITH_PARENT

위 실행 계획에서 4~8번 오퍼레이션이 여기에 해당하는데, 이는 한 서버 집합이 현재 스텝과 그 부모(Parent) 스텝을 모두 처리함을 의미한다. 예를 들어, 위 실행 계획의 8-7-4-3번 순으로 이어지는 계층 구조 상에서 주문_IDX1 인덱스를 스캔(7~8번 스텝)하면서 주문 테이블을 액세스(3번 스텝)하는 일련의 오퍼레이션을 한 서버 집합이 처리한다는 뜻이다.

PCWP도 분명히 병렬 오퍼레이션이지만 한 서버 집합 내에서는 프로세스 간 통신이 발생하지 않으므로 Intra-Operation Parallelism에 속한다. 즉, 한 서버 집합에 속한 서버 프로세스들이 각자 맡은 범위 내에서 두 스텝 이상의 오퍼레이션을 처리하는 것이며, 자식 스텝의 처리 결과를 부모 스텝에서 사용할 뿐 프로세스 간 통신은 필요치 않다.

PCWC: PARALLEL_COMBINED_WITH_CHILD

위 실행 계획에서 3번 오퍼레이션이 여기에 해당하며, 한 서버 집합이 현재 스텝과 그 자식(Child) 스텝을 모두 처리함을 의미한다. PCWC도 병렬 오퍼레이션이지만 한 서버 집합 내에서는 프로세스 간 통신이 절대 발생하지 않으므로 Intra-Operation Parallelism에 속한다. 자식 스텝의 처리 결과를 받아 현재 스텝의 입력값으로 사용할 뿐이며, 프로세스 간 통신은 필요치 않다.

참고로, 앞서 본 실행 계획의 뒤에서 두 번째 컬럼(-oL)이 공백(Blank)일 때는 Serial 방식으로 처리됨을 의미하며, 맨 아래쪽 오퍼레이션(0=10)이 여기에 해당한다.

지금까지 설명한 IN-OUT 오퍼레이션에 대해 정리해보자.

  • S→P, P→S, P→P는 프로세스 간 통신이 발생한다.
  • PCWP와 PCWC는 프로세스 간 통신이 발생하지 않으며, 각 병렬 서버가 독립적으로 여러 스텝을 처리할 때 나타난다. 하위 스텝의 출력값이 상위 스텝의 입력값으로 사용된다.
  • P→P, P→S, PCWP, PCWC는 병렬 오퍼레이션인 반면, S→P는 직렬(Serial) 오퍼레이션이다.

병렬 쿼리 실행 계획에 S→P가 나타난다면 해당 오퍼레이션이 병목 지점인지의심해볼 필요가 있다. 만약 처리할 데이터 양이 수백 MB 이상이라면 병렬 오퍼레이션으로 바꾸는 것을 고려해야 한다.

(5) 데이터 재분배

앞서 살펴본 IN-OUT 오퍼레이션 중에서 S→P, P→P가 데이터 재분배(Redistribution)와 관련 있음을 이해했을 것이다. 데이터를 재분배하는 방식에는 일반적으로 아래 5가지가 사용된다.

RANGE

ORDER BY 또는 SORT GROUP BY를 병렬로 처리할 때 사용된다. 정렬 작업을 맡은 두 번째 서버 집합의 프로세스마다 처리 범위(예를 들어, A~G, H~M, N~S, T~Z)를 지정하고 나서, 데이터를 읽는 첫 번째 서버 집합이 두 번째 서버 집합의 정해진 프로세스에게 “정렬 키 값에 따라” 분배하는 방식이다. 앞서 예시한 명함 정렬 작업을 참고하기 바란다.

QC는 각 서버 프로세스에게 작업 범위를 할당하고 정렬 작업에는 직접 참여하지 않으며, 정렬이 완료되고 나면 순서대로 결과를 받아서 사용자에게 전송하는 역할만 한다.

HASH

조인이나 HASH GROUP BY를 병렬로 처리할 때 사용된다. 조인 키나 그룹 BY 키 값을 해시 함수에 적용하고 리턴된 값에 따라 데이터를 분배하는 방식이며, P→P뿐만 아니라 S→P 방식으로 이루어질 수도 있다.

BROADCAST

QC 또는 첫 번째 서버 집합에 속한 프로세스들이 각각 읽은 데이터를 두 번째 서버 집합에 속한 “모든” 병렬 프로세스에게 전송하는 방식이다. 병렬 조인에서 크기가 매우 작은 테이블이 있을 때 사용되며, P→P뿐만 아니라 S→P 방식으로도 이루어진다. (작은 테이블은 병렬로 읽지 않을 때가 많으므로 오히려 S→P가 일반적이라고 볼 수도 있다.)

KEY

특정 컬럼(들)을 기준으로 테이블 또는 인덱스를 파티셔닝할 때 사용하는 분배 방식으로, 실행 계획에는 PARTITION(KEY)로 표시된다 (줄여서 PART(KEY)).

  • Partial Partition-Wise 조인
  • CTAS(create table as select) 문장으로 파티션 테이블을 만들 때 (Oracle 9i 이전 버전)
  • 병렬로 글로벌 파티션 인덱스를 만들 때 (참고로, 비파티션 인덱스를 만들 때는 RANGE 방식 사용)

3절에서 설명하겠지만 Partial Partition-Wise 조인은, 이미 파티션된 테이블과 조인하기 위해 다른 한쪽 테이블을 동적으로 파티셔닝하고 나서 각 Partition-Pair에 대해 독립적으로 병렬 조인을 수행하는 것을 말한다.

ROUND-ROBIN

파티션 키, 정렬 키, 해시 함수 등에 의존하지 않고 반대편 병렬 서버에 무작위로 데이터를 분배할 때 사용된다. 무작위라고는 하지만 골고루 분배되도록 ROUND-ROBIN 방식을 사용한다.

1
2
3
4
5
6
SQL> ALTER SESSION ENABLE PARALLEL DML;
SQL> EXPLAIN PLAN FOR
  2  INSERT /*+ PARALLEL(t1 2) */ INTO t1
  3  SELECT /*+ PARALLEL (t2 2) FULL(t2) */ * FROM t2;

SQL> @? /rdbms/admin/utlxpls

(6) Granule

데이터를 병렬로 처리할 때 일의 최소 단위를 ‘Granule’이라고 하며, 병렬 서버는 한 번에 하나의 Granule씩만 처리한다. Granule의 개수와 크기는 병렬도와 관련 있으며, 이는 병렬 서버 사이에 일을 고르게 분배하는 데 큰 영향을 미친다.

Granule에는 크게 블록 기반 Granule과 파티션 기반 Granule이 있는데, 이는 오라클 데이터베이스의 내부적인 결정 사항이며 사용자가 그 크기나 종류를 직접 선택할 수는 없다.

블록 기반 Granule (=블록 범위 Granule)

블록 기반 Granule은 파티션 테이블인지 여부와 상관없이 대부분의 병렬 오퍼레이션에 적용되는 기본 작업 단위이다. 병렬 쿼리는 물론, 9iR2부터 병렬 DML에도 블록 기반 Granule이 사용되므로 파티션 여부, 파티션 개수와 무관하게 병렬도를 지정할 수 있다.

블록 기반 Granule 단위로 데이터를 읽을 때는 실행 계획상에 ‘PX BLOCK ITERATOR’라고 표시된다. 이 오퍼레이션이 나타날 때면, QC는 테이블로부터 읽어야 할 일정 범위의 블록을 Granule로서 각 병렬 서버에게 할당한다. 그리고 Iterator가 의미하는 바와 같이 병렬 서버가 한 Granule에 대한 일을 끝마치면 이어서 다른 Granule을 할당한다.

Granule 크기와 총 개수는 실행 시점에 오브젝트 사이즈와 병렬도에 따라 QC가 동적으로 결정한다. 물론 목표는 모든 병렬 서버에게 일을 골고루 분배하는 데 있다. 아주 작은 테이블이 아니라면 Granule 개수는 사용자가 지정한 병렬도보다 많을 것이다.

또한 Granule을 계산할 때는 각 병렬 서버에게 가능한 한 서로 다른 데이터 파일에 놓인 블록들을 할당함으로써 경합을 회피하려고 노력한다.

파티션 기반 Granule (=파티션 Granule)

파티션 기반 Granule이 사용될 때, 각 병렬 서버 프로세스는 할당받은 테이블(또는 인덱스) 파티션 전체를 처리할 책임을 진다. 이 방식에선 한 파티션을 두 개 프로세스가 함께 처리할 수 없으므로 병렬도는 당연히 파티션 개수 이하로만 지정할 수 있다.

파티션 기반 Granule일 때는 실행 계획에 ‘PX PARTITION RANGE ALL’ 또는 ‘PX PARTITION RANGE ITERATOR’라고 표시된다. 전자는 파티션 전체를 읽어야 할 때 나타나고, 후자는 일부 파티션만 읽을 때 나타난다. 블록 기반 Granule과 마찬가지로 여기서도 병렬 서버가 한 파티션 처리를 끝마치면 이어서 다른 파티션을 할당받는 식으로 진행한다. 물론 병렬도가 파티션 개수보다 적을 때 그렇다.

파티션 기반 Granule은 아래와 같은 작업을 수행할 때 사용된다.

  • Partition-Wise 조인 시
  • 파티션 인덱스를 병렬로 스캔할 때
  • 파티션 인덱스를 병렬로 갱신할 때
  • 9iR1 이전에서의 병렬 DML
  • 파티션 테이블 또는 파티션 인덱스를 병렬로 생성할 때

파티션 기반일 때는 Granule 개수가 테이블과 인덱스의 파티션 구조에 의해 정적으로 결정되므로 블록 기반 Granule처럼 유연하지 못하다. 우선, 파티션 간 데이터량에 편차가 심할 때 부하를 효과적으로 분산시키는 데 불리하고 리소스를 낭비하게 된다. 따라서 파티션 기반 Granule은 병렬도보다 파티션 개수가 상당히 많을 때라야 유용하다.

시스템 리소스를 최대한 사용함으로써 병렬 효과를 극대화하려 할 때, 파티션 개수보다 많은 병렬도를 지정할 수 없다는 것도 단점이다.

(7) 병렬 처리 과정에서 발생하는 대기 이벤트

병렬 처리를 위한 데이터 재분배 과정에서 프로세스 간 통신이 발생한다고 여러 차례 강조하였다. QC와 병렬 서버 간, 병렬 서버와 병렬 서버 간 테이블 큐를 통해 메시지를 주고받기 위해 내부적으로 메시지 버퍼(parallel_execution_message_size 파라미터 참조)를 사용하는데, 생산자 프로세스가 버퍼에 데이터를 넣으면(enqueue) 소비자 프로세스가 그것을 꺼내가는(dequeue) 식이다.

참고로, 테이블 큐를 보호할 목적으로 오라클은 credit 비트를 사용한다. 데이터를 전송하려면 먼저 상대편 서버 프로세스로부터 credit 비트를 받아야 한다. 따라서 병렬 서버든 QC든 오직 한 프로세스만이 특정 서버 프로세스에게 데이터를 전송할 수 있게 된다.

병렬 처리 과정에서 자주 발생하는 대기 이벤트를 요약하면 다음과 같다.

대기 이벤트 모니터링

큰 테이블에 대해 아래와 같이 병렬 쿼리를 수행해보자.

1
SELECT /*+ parallel(고객 2) */ * FROM 고객 ORDER BY 고객명

위 쿼리가 수행되는 동안 다른 세션에서 아래 쿼리를 수행해보면 QC와 각 병렬 서버에 어떤 대기 이벤트들이 발생하는지를 확인할 수 있다(스크립트 ch7_01.txt 참조).

1
2
3
4
5
6
SELECT decode(a.qcserial#, NULL, 'PARENT', 'CHILD') ST_LVL,
       a.server_set "SET", a.sid, a.serial#, status, event, wait_class
  FROM v$px_session a, v$session b
 WHERE a.sid = b.sid
   AND a.qcsid = 5284
ORDER BY a.qcsid, ST_LVL DESC, a.server_group, a.server_set;

대기 이벤트 해소

v$event_name 뷰를 조회해보면 병렬 처리와 관련된 대기 이벤트가 대부분 Idle로 분류되어 있다. 이유는, 이들 이벤트를 회피하기 위해 사용자가 할 수 있는 일이 거의 없기 때문이다.

큰 테이블을 SP 방식으로 분배(PX Deq: Table Q Normal 이벤트가 많이 발생)하던 것을 PP 방식으로 바꿔주거나, PP 분배 과정에서 프로세스 간 데이터 전송량이 많은 SQL을 튜닝해서 그 양을 줄일 수 있는 경우가 종종 있기는 하다.

하지만 그런 조치들이 가능하지 않은 상황에서 병렬 쿼리 관련 Idle 대기 이벤트가 많이 발생한다면 병렬 메커니즘상 자연스럽게 나타나는 현상이라고 이해하는 수밖에 없다.

Idle 이벤트가 아닌 경우에는 종종 튜닝이 가능할 수 있는데, 앞선 테스트 사례의 경우 PX Deq Credit: send blkd 이벤트가 많이 발생하는 이유는 클라이언트가 천천히 스크롤하면서 데이터를 관찰하거나 Fetch Call과 Fetch Call 사이에 많은 애플리케이션 로직을 수행하기 때문일 것이다. 전자의 경우라면 CPU 리소스를 낭비하는 것이므로 가급적 병렬 쿼리를 사용하지 않는 편이 낫고, 후자의 경우는 애플리케이션 로직을 튜닝할 수 있는지 검토해봐야 한다.

더 자주 발생하는 사례는 아래 insert 문과 같은 경우다.

1
2
INSERT /*+ append */ INTO t1
SELECT /*+ full(t2) parallel(t2 4) */ * FROM t2;

여기서 t2 테이블은 병렬로 읽지만 t1으로의 insert는 직렬로 수행되고 있다. 이럴 때 t2를 읽어 QC에게 전송하는 4개의 병렬 서버들은 아래와 같이 PX Deq Credit: send blkd 이벤트에서 자주 대기하게 된다.

insert 과정에서 병목(direct path write 대기 이벤트 발생)이 생기므로 select 문을 병렬로 수행하는 것은 불필요할 수 있다. select 문을 직렬로 처리한다면 PX Deq Credit: send blkd 이벤트는 당연히 사라진다.

병목을 해소함으로써 속도를 향상시키고 싶다면, 아래와 같이 t1으로의 insert도 병렬로 수행되게 하면 된다.

1
2
3
ALTER SESSION ENABLE PARALLEL DML;
INSERT /*+ append parallel(t1 4) */ INTO t1
SELECT /*+ full(t2) parallel(t2 4) */ * FROM t2;
This post is licensed under CC BY 4.0 by the author.