태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

'Deferred Segment Creation'에 해당되는 글 1건

  1. 2010.08.23 Deferred Segment Creation의 재미있는 두가지 버그

Deferred Segment Creation의 재미있는 두가지 버그

오라클 2010.08.23 16:58
Oracle 11gR2에서는 Deferred Segment Creation이라는 기능이 추가되었습니다. 간단한게 말하면 CREATE TABLE 문을 선언해도 실제로 데이터가 추가될 때까지는 세그먼트를 생성하지 않는 것을 말합니다. Deferred Segment Creation과 INSERT ... SELECT에 관한 재미있는 버그 두 개가 있어 포스트를 통해 소개합니다.
  • 버그 9078678: 세그먼트가 없는 테이블에 대한 병렬 INSERT ... SELECT 문에 대해 예상 실행 계획(Explain Plan)을 보면 병렬이 아닌 직렬(Serial) 실행으로 보이지만, 실제 수행하면 병렬로 수행되는 버그입니다.
  • 버그 9329566: 세그먼트가 없는 테이블에 대해 INSERT ... SELECT 문을 수행하면, SELECT 문장을 두번 수행하는 버그입니다. 정확하게 말하면 SELECT 수행 후 한번 페치를 한 후, 다시 수행하는 버그입니다.
두 버그 모두 세그먼트가 없다는 특징으로 인해 발생하는 버그입니다. 간단한 테스트 케이스를 통해 살펴 보겠습니다. 우선 버그 9078678에 해당하는 현상입니다.

1. 오라클 버전은 11.2.0.1입니다.
TPACK@ukja1120> -- version
TPACK@ukja1120> select * from v$version where rownum = 1;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
2. 테이블 T1을 생성합니다. 테이블 T1은 세그먼트가 존재하지 않는 상태입니다. 테이블 T1에 대해 병렬 삽입(Parallel Insert) 문장을 수행하는 경우 실행 계획이 어떻게 되는지 보겠습니다.
TPACK@ukja1120> create table t1(c1 number);

Table created.

TPACK@ukja1120> alter session enable parallel dml;

Session altered.

TPACK@ukja1120> explain plan for
  2  insert /*+ parallel(t1 4) */ into t1
  3  select level from dual connect by level <= 10000;

Explained.

TPACK@ukja1120> select * from table(dbms_xplan.display);

------------------------------------------------------------------------------
| Id  | Operation                     | Name | Rows  | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | INSERT STATEMENT              |      |     1 |     2   (0)| 00:00:01 |
|   1 |  LOAD AS SELECT               | T1   |       |            |          |
|*  2 |   CONNECT BY WITHOUT FILTERING|      |       |            |          |
|   3 |    FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------
힌트를 통해 병렬 실행을 지정했지만 직렬 실행(Serial Execution)이 선택되어 버렸습니다.

3. 예측 실행 계획이 아닌 실제로 SQL 문장을 실행한 후 병렬 실행 여부를 관찰해보겠습니다.

TPACK@ukja1120> insert /*+ parallel(t1 4) */ into t1
  2  select level from dual connect by level <= 10000;

10000 rows created.

TPACK@ukja1120> 
TPACK@ukja1120> commit;

Commit complete.

-- https://sites.google.com/site/ukja/sql-scripts-1/o-s/pqstat
TPACK@ukja1120> @pq_stat

DFO_NUMBER      TQ_ID SERVER_TYP PROCESS      NUM_ROWS 
---------- ---------- ---------- ---------- ---------- 
         1          0 Consumer   P000             2500 
         1          0 Consumer   P001             2500 
         1          0 Consumer   P002             2500 
         1          0 Consumer   P003             2500 
         1          0 Producer   QC              10000 
         1          1 Consumer   QC                  4 
         1          1 Producer   P000                1 
         1          1 Producer   P001                1 
         1          1 Producer   P002                1 
         1          1 Producer   P003                1 


10 rows selected.

STATISTIC                      LAST_QUERY SESSION_TOTAL
------------------------------ ---------- -------------
Queries Parallelized                    0             0
DML Parallelized                        1             1
DDL Parallelized                        0             0
DFO Trees                               1             1
Server Threads                          4             0
Allocation Height                       4             0
Allocation Width                        1             0
Local Msgs Sent                        44            44
Distr Msgs Sent                         0             0
Local Msgs Recv'd                      44            44
Distr Msgs Recv'd                       0             0

11 rows selected.
Explain Plan을 이용한 예측 실행 계획에서는 직렬 실행이라는 결과가 나왔지만, 실제 수행 결과는 병렬 실행이 되었다는 것을 알 수 있습니다. 더 웃긴 것은 이 상태에서(테이블 T1에 데이터가 추가된 상태에서) 예측 실행 계획을 다시 보면 병렬 실행을 하는 것으로 바뀐다는 것입니다.
TPACK@ukja1120> explain plan for
  2  insert /*+ append parallel(t1 4) */ into t1
  3  select level from dual connect by level <= 10000;

Explained.

------------------------------------------------------
| Id  | Operation                         | Name     |
------------------------------------------------------
|   0 | INSERT STATEMENT                  |          |
|   1 |  PX COORDINATOR                   |          |
|   2 |   PX SEND QC (RANDOM)             | :TQ10001 |
|   3 |    LOAD AS SELECT                 | T1       |
|   4 |     PX RECEIVE                    |          |
|   5 |      PX SEND ROUND-ROBIN          | :TQ10000 |
|*  6 |       CONNECT BY WITHOUT FILTERING|          |
|   7 |        FAST DUAL                  |          |
------------------------------------------------------
위 현상은 버그입니다. 세그먼트가 아직 생성되지 않은 테이블에 대해 병렬 실행 문장의 예측 실행 계획을 보면 직렬 실행하는 것으로 나타나지만, 실제로는 병렬 실행되는 버그입니다. 즉, 세그먼트가 없는 테이블에 대해 병렬 실행의 예측 실행 계획을 정확하게 보여주지 못하는 버그입니다.

버그 9329566에 해당하는 테스크 케이스도 보겠습니다.

1. 테이블 T1과 테이블 T2를 동일하게 생성합니다. 단, 테이블 T2에는 한건의 로우를 추가해서 세그먼트를 미리 만듭니다. 테이블 T1은 세그먼트가 아직 생성되지 않은 상태입니다. 10,000 블록의 크기를 가지는 테이블 T3도 만듭니다.

TPACK@ukja1120> create table t1(c1 varchar2(2000), c2 varchar2(2000), c3 varchar2(2000), c4 varchar2(1000));

Table created.

TPACK@ukja1120> create table t2(c1 varchar2(2000), c2 varchar2(2000), c3 varchar2(2000), c4 varchar2(1000));

Table created.

TPACK@ukja1120> 
TPACK@ukja1120> -- insert 1 row into table t2 to create the segment
TPACK@ukja1120> insert into t2 values('1','1','1','1');

1 row created.

TPACK@ukja1120> -- table size 10000 block
TPACK@ukja1120> create table t3
  2  as
  3  select rpad('x',2000) as c1, rpad('x',2000) as c2, rpad('x',2000) as c3, rpad('x',1000) as c4
  4  from dual
  5  connect by level <= 10000;

Table created.
2. 테이블 T3에서 최대값(MAX)을 읽어서 테이블 T1(세그먼트 아직 없음), 테이블 T2(세그먼트 있음)으로 삽입하는 쿼리의 Consistent Gets를 비교해보겠습니다. 테스트의 편의를 위해 티팩의 Session Snapshot Report를 사용합니다.
TPACK@ukja1120> exec tpack.begin_session_snapshot;

PL/SQL procedure successfully completed.

TPACK@ukja1120> insert into t1
  2  select max(c1), max(c2), max(c3), max(c4) from t3;

1 row created.

TPACK@ukja1120> exec tpack.add_session_snapshot;

PL/SQL procedure successfully completed.

TPACK@ukja1120> insert into t2
  2  select max(c1), max(c2), max(c3), max(c4) from t3;

1 row created.

TPACK@ukja1120> 
TPACK@ukja1120> exec tpack.add_session_snapshot;

PL/SQL procedure successfully completed.
아래 결과를 보면 테이블 T1(세그먼트 없음)에 대한 삽입이 테이블 T2(세그먼트 있음)에 대한 삽입 작업에 비해 2배 정도의 Consistent Gets를 보입니다.
TPACK@ukja1120> col item format a40
TPACK@ukja1120> col deltas format a20
TPACK@ukja1120> select item, deltas from table(tpack.session_snapshot_report)
  2  where type = 'STAT';

TPACK@ukja1120> select item, deltas from table(tpack.session_snapshot_report)
  2  where type = 'STAT';

ITEM                                     DELTAS
---------------------------------------- --------------------
...
physical read bytes                      164339712->82305024
consistent gets                          20451->10327
...
이것 또한 버그입니다.
  1. 테이블 T3에서 데이터를 읽는 후 그 데이터를 테이블 T1에 INSERT하려고 합니다.
  2. 그런데 테이블 T1은 아직 세그먼트가 없습니다. 따라서 우선 세그먼트를 먼저 만듭니다.
  3. 여기서 오라클은 1번 단계에서 얻은 값을 재활용하지 못하고 다시 테이블 T3에서 데이터를 읽습니다. 현재 쿼리는 테이블 T3에서 최대값(MAX)을 얻는 것이므로 한번 최대값을 얻을 때마다 테이블 크기인 10,000 블록을 읽어어 합니다. 이 때문에 테이블 T1에 대한 INSERT 작업시에는 20,000블록(2번 읽으므로), 테이블 T2에 대한 INSERT 작업시에는 10,000블록(1번 읽으므로)을 읽게 되는 것입니다.
  4. 이후로는 정상적으로 INSERT가 이루어집니다.
첫번째 버그는 MOS 문서 9078678에 해당하고, 두번째 버그는 MOS 문서 9329566에 해당합니다. 단, 9078678 문서는 정상적으로 검색이 불가능한 것 같습니다.

위 버그들은 세그먼트가 없는 테이블에 대해서만 발생하므로 치명적인 버그라고 보기는 힘들겠습니다. 오히려 개발자들이(설령 오라클 커널 개발자라고 하더라도) 얼마나 기본적인 함정에 쉽게 빠질 수 있는지 알 수 있는 좋은 예로 생각됩니다. 우리가 오라클에서 겪는 많은 문제들 중 상당수가 이런 로직 구멍(Logic Hole)에서 발생합니다. 성능 문제가 생겼을 때 이런 로직 구멍을 간파할 수 있는 테스팅 능력을 갖추는게 중요하겠죠.

PS) 오라클이 Deferred Segment Creation 기능을 디폴트로 활성화(TRUE) 상태로 한 것은 저로서는 의외입니다. 최소한 11gR2에서는 FALSE를 기본값으로 하고 12g(?)부터 TRUE로 하는 것이 옳았다고 생각되네요.

저작자 표시
신고
Trackback 0 : Comment 0

Write a comment

티스토리 툴바