개요
Oracle 12c에 Asynchronous Global Index Maintenance 기능이 추가되었습니다. 이 기능은 글로벌 인덱스의 관리 작업을 비동기로 수행함으로써 글로벌 인덱스를 가진 테이블에 대한 DROP PARTITION과 TRUNCATE PARTITION의 성능을 개선합니다.
테스트를 위해 아래와 같이 테이블을 생성하겠습니다.
-- 1
DROP TABLE t1 PURGE;
CREATE TABLE t1 (c1, c2)
PARTITION BY RANGE (c1) (
PARTITION p1 VALUES LESS THAN (5000001)
, PARTITION p2 VALUES LESS THAN (MAXVALUE)
)
AS
SELECT ROWNUM, 'X' FROM XMLTABLE ('1 to 10000000');
CREATE INDEX t1_x1 ON t1 (c1);
비교를 위해 *_INDEXES 뷰와 ANALYZE INDEX 문의 수행 결과를 조회하겠습니다. *_INDEXES 뷰의 orphaned_entries 값은 NO, INDEX_STATS 테이블의 del_lf_rows 값은 0입니다.
-- 2-1
SELECT index_name, status, orphaned_entries
FROM user_indexes
WHERE index_name = 'T1_X1';
INDEX_NAME STATUS ORPHANED_ENTRIES
---------- ------ ----------------
T1_X1 VALID NO
1개의 행이 선택되었습니다.
-- 2-2
ANALYZE INDEX t1_x1 VALIDATE STRUCTURE;
SELECT name, blocks, lf_rows, lf_blks, br_rows, br_blks, del_lf_rows, btree_space, used_space
FROM index_stats;
NAME BLOCKS LF_ROWS LF_BLKS BR_ROWS BR_BLKS DEL_LF_ROWS BTREE_SPACE USED_SPACE
----- ------ -------- ------- ------- ------- ----------- ----------- ----------
T1_X1 29696 10000000 29100 29099 48 0 233185536 209263810
1개의 행이 선택되었습니다.
기본 동작
ALTER TABLE TRUNCATE PARTITION 문에 UPDATE GLOBAL INDEXES 절을 사용하면 Asynchronous Global Index Maintenance 기능이 동작합니다.
ALTER TABLE [schema.] table TRUNCATE PARTITION partition UPDATE GLOBAL INDEXES;
아래와 같이 p1 파티션을 TRUNCATE하겠습니다.
-- 3
ALTER TABLE t1 TRUNCATE PARTITION p1 UPDATE GLOBAL INDEXES;
경 과: 00:00:00.03
TRUNCATE 후 *_INDEXES 뷰의 orphaned_entries 값은 YES, INDEX_STATS 테이블의 del_lf_rows 값은 5,000,000으로 변경됩니다.
-- 4-1
SELECT index_name, status, orphaned_entries
FROM user_indexes
WHERE index_name = 'T1_X1';
INDEX_NAME STATUS ORPHANED_ENTRIES
---------- ------ ----------------
T1_X1 VALID YES
1개의 행이 선택되었습니다.
-- 4-2
ANALYZE INDEX t1_x1 VALIDATE STRUCTURE;
SELECT name, blocks, lf_rows, lf_blks, br_rows, br_blks, del_lf_rows, btree_space, used_space
FROM index_stats;
NAME BLOCKS LF_ROWS LF_BLKS BR_ROWS BR_BLKS DEL_LF_ROWS BTREE_SPACE USED_SPACE
----- ------ -------- ------- ------- ------- ----------- ----------- ----------
T1_X1 29696 10000000 29100 29099 48 5000000 233185536 209263810
1개의 행이 선택되었습니다.
아래 쿼리는 각각 p1, p2 파티션을 조회합니다. 5-1번 실행 계획에서 TRUNCATE된 p1 파티션에 대한 블록 I/O가 발생하지 않는 것을 알 수 있습니다. 이것은 Predicate Information 항목의 TBL$OR$IDX$PART$NUM(<?>,0,8,0,"T1".ROWID)=1 필터 조건에 의한 동작으로 보입니다.
-- 5-1: P1
SELECT * FROM t1 WHERE c1 BETWEEN 1 AND 100;
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Pstart| Pstop | A-Rows | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 0 | 3 |
| 1 | TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| T1 | 1 | 1 | 0 | 3 |
|* 2 | INDEX RANGE SCAN | T1_X1 | | | 0 | 3 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C1">=1 AND "C1"<=100)
filter(TBL$OR$IDX$PART$NUM(<?>,0,8,0,"T1".ROWID)=1)
-- 5-2: P2
SELECT * FROM t1 WHERE c1 BETWEEN 5000001 AND 5000100;
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Pstart| Pstop | A-Rows | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 100 | 6 |
| 1 | TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| T1 | 2 | 2 | 100 | 6 |
|* 2 | INDEX RANGE SCAN | T1_X1 | | | 100 | 4 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C1">=5000001 AND "C1"<=5000100)
filter(TBL$OR$IDX$PART$NUM(<?>,0,8,0,"T1".ROWID)=1)
아래와 같이 p1 테이블에 데이터를 삽입하겠습니다.
-- 6
INSERT INTO t1 SELECT ROWNUM, 'X' FROM XMLTABLE ('1 to 5000000');
COMMIT;
인덱스 공간이 재사용되지 못해 INDEX_STATS 테이블의 blocks 값이 29,696에서 45,056로 늘어난 것을 볼 수 있습니다.
-- 7
ANALYZE INDEX t1_x1 VALIDATE STRUCTURE;
SELECT name, blocks, lf_rows, lf_blks, br_rows, br_blks, del_lf_rows, btree_space, used_space
FROM index_stats;
NAME BLOCKS LF_ROWS LF_BLKS BR_ROWS BR_BLKS DEL_LF_ROWS BTREE_SPACE USED_SPACE
----- ------ -------- ------- ------- ------- ----------- ----------- ----------
T1_X1 45056 15000000 44132 44131 98 5000000 353843136 313394236
1개의 행이 선택되었습니다.
p1 파티션을 다시 조회하면 블록 I/O가 p2 파티션을 조회한 5-2번 쿼리와 동일한 것을 알 수 있습니다. 같은 데이터를 다시 입력해도 조회 성능이 저하되지 않는 것으로 보입니다.
-- 8
SELECT * FROM t1 WHERE c1 BETWEEN 1 AND 100;
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Pstart| Pstop | A-Rows | Buffers |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 100 | 6 |
| 1 | TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| T1 | 1 | 1 | 100 | 6 |
|* 2 | INDEX RANGE SCAN | T1_X1 | | | 100 | 4 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C1">=1 AND "C1"<=100)
filter(TBL$OR$IDX$PART$NUM(<?>,0,8,0,"T1".ROWID)=1)
CLEANUP 동작
글로벌 인덱스의 orphaned entries를 정리하기 위해 매일 오전 2시에 DBMS_PART.CLEANUP_GIDX_JOB 프로시저가 자동으로 수행됩니다. DBMS_PART.CLEANUP_GIDX_JOB 프로시저는 CLEANUP 작업을 직렬로 수행하기 때문에 인덱스 크기가 큰 경우 많은 시간이 소요될 수 있습니다. 수행 시간을 단축하기 위해 PARALLEL 매개변수에 병렬도를 지정할 수 있습니다. OPTIONS 매개변수는 CLEANUP_ORPHANS나 COALESCE를 지정할 수 있습니다. CLEANUP_ORPHANS는 orphaned entries만 정리하고, COALESCE는 블록 병합을 함께 수행합니다. 기본값은 CLEANUP_ORPHANS입니다.
-- 9-1
SELECT b.program_action, b.number_of_arguments, c.repeat_interval
FROM dba_scheduler_jobs a
, dba_scheduler_programs b
, dba_scheduler_schedules c
WHERE a.owner = 'SYS'
AND a.job_name = 'PMO_DEFERRED_GIDX_MAINT_JOB'
AND b.owner = a.program_owner
AND b.program_name = a.program_name
AND c.owner = a.schedule_owner
AND c.schedule_name = a.schedule_name;
PROGRAM_ACTION NUMBER_OF_ARGUMENTS REPEAT_INTERVAL
-------------------------- ------------------- ---------------------------------------------
dbms_part.cleanup_gidx_job 2 FREQ=DAILY; BYHOUR=02; BYMINUTE=0; BYSECOND=0
1개의 행이 선택되었습니다.
-- 9-2
SELECT argument_name, argument_position, argument_type, default_value
FROM dba_scheduler_program_args
WHERE owner = 'SYS'
AND program_name = 'PMO_DEFERRED_GIDX_MAINT';
ARGUMENT_NAME ARGUMENT_POSITION ARGUMENT_TYPE DEFAULT_VALUE
------------- ----------------- ------------- -------------
PARALLEL 1 VARCHAR2
OPTIONS 2 VARCHAR2
2 행이 선택되었습니다.
-- 9-3:
EXEC DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE ('SYS.PMO_DEFERRED_GIDX_MAINT_JOB', 'PARALLEL', '2');
-- 9-4: CLEANUP_ORPHANS or COALESCE
EXEC DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE ('SYS.PMO_DEFERRED_GIDX_MAINT_JOB', 'OPTIONS', 'COALESCE');
참고로 DBMS_PART.CLEANUP_GIDX_JOB 프로시저는 SYS.INDEX_ORPHANED_ENTRY_V$ 뷰에서 CLEANUP 대상을 조회합니다.
-- 10-1
SELECT index_owner, index_name, index_subname, table_owner, table_name, table_subname
FROM sys.index_orphaned_entry_v$;
INDEX_OWNER INDEX_NAME INDEX_SUBNAME TABLE_OWNER TABLE_NAME TABLE_SUBNAME
----------- ---------- ------------- ----------- ---------- -------------
TUNA T1_X1 TUNA T1
1개의 행이 선택되었습니다.
-- 10-2
SELECT indexobj#, tabpartdobj# FROM sys.index_orphaned_entry$;
INDEXOBJ# TABPARTDOBJ#
--------- ------------
224430 224428
1개의 행이 선택되었습니다.
아래의 네 가지 방법을 사용하면 글로벌 인덱스의 orphaned entries를 수동으로 정리할 수 있습니다.
-- 11-1
DBMS_SCHEDULER.RUN_JOB (SYS.PMO_DEFERRED_GIDX_MAINT_JOB, TRUE);
-- 11-2
EXEC DBMS_PART.CLEANUP_GIDX ('TUNA', 'T1', '2', 'CLEANUP_ORPHANS');
-- 11-3: CLEANUP = 'COALESCE', CLEANUP ONLY = 'CLEANUP_ORPHANS'
ALTER INDEX t1_x1 COALESCE CLEANUP PARALLEL 2;
-- ALTER INDEX t1_x1 COALESCE CLEANUP ONLY PARALLEL 2;
-- 11-4
ALTER INDEX t1_x1 REBUILD PARALLEL 2 ONLINE;
INDEX COALESCE CLEANUP은 ONLINE으로 수행할 수 없으며 21.59초가 소요됩니다. 수행 후 *_INDEXES 뷰의 orphaned_entries 값은 NO, INDEX_STATS 테이블의 del_lf_rows 값은 0으로 변경되고, sys.index_orphaned_entry_v$ 뷰의 결과가 반환되지 않습니다.
-- 12-1
ALTER INDEX t1_x1 COALESCE CLEANUP PARALLEL 2;
경 과: 00:00:21.59
-- 12-2
SELECT index_name, status, orphaned_entries
FROM user_indexes
WHERE index_name = 'T1_X1';
INDEX_NAME STATUS ORPHANED_ENTRIES
---------- ------ ----------------
T1_X1 VALID NO
1개의 행이 선택되었습니다.
-- 12-3
ANALYZE INDEX t1_x1 VALIDATE STRUCTURE;
SELECT name, blocks, lf_rows, lf_blks, br_rows, br_blks, del_lf_rows, btree_space, used_space
FROM index_stats;
NAME BLOCKS LF_ROWS LF_BLKS BR_ROWS BR_BLKS DEL_LF_ROWS BTREE_SPACE USED_SPACE
----- ------ -------- ------- ------- ------- ----------- ----------- ----------
T1_X1 45056 10000000 29135 29134 98 0 233867136 209264235
1개의 행이 선택되었습니다.
-- 12-4
SELECT * FROM sys.index_orphaned_entry_v$;
선택된 레코드가 없습니다.
INDEX REBUILD는 ONLINE으로 수행할 수 있으며 5.27초가 소요됩니다. 전체 테이블의 크기와 TRUNCATE되는 파티션의 크기에 따라 선택적으로 INDEX COALESCE CLEANUP과 INDEX REBUILD를 사용할 수 있습니다.
-- 13-1
ALTER INDEX t1_x1 REBUILD PARALLEL 2 ONLINE;
경 과: 00:00:05.27
-- 13-2
ANALYZE INDEX t1_x1 VALIDATE STRUCTURE;
SELECT name, blocks, lf_rows, lf_blks, br_rows, br_blks, del_lf_rows, btree_space, used_space
FROM index_stats;
NAME BLOCKS LF_ROWS LF_BLKS BR_ROWS BR_BLKS DEL_LF_ROWS BTREE_SPACE USED_SPACE
----- ------ -------- ------- ------- ------- ----------- ----------- ----------
T1_X1 29480 10000000 29101 29100 49 0 233201568 209264182
1개의 행이 선택되었습니다.
아래는 지금까지 수행한 테스트에 대한 INDEX_STATS 테이블의 조회 결과입니다. blocks 값에서 INDEX COALESCE CLEANUP이 미사용 공간을 해제하지 않는다는 것을 알 수 있습니다.
NAME BLOCKS LF_ROWS LF_BLKS BR_ROWS BR_BLKS DEL_LF_ROWS BTREE_SPACE USED_SPACE
----- ------ -------- ------- ------- ------- ----------- ----------- ----------
T1_X1 29696 10000000 29100 29099 48 0 233185536 209263810
T1_X1 29696 10000000 29100 29099 48 5000000 233185536 209263810 -- TRUNCATE PARTITION UPDATE GLOBAL INDEXES
T1_X1 45056 15000000 44132 44131 98 5000000 353843136 313394236 -- INSERT
T1_X1 45056 10000000 29135 29134 98 0 233867136 209264235 -- INDEX COALESCE CLEANUP
T1_X1 29480 10000000 29101 29100 49 0 233201568 209264182 -- INDEX REBUILD
관련 링크
- MOS - Is It Possible To Execute The Job PMO_DEFERRED_GIDX_MAINT_JOB With Parallel Degree? (Doc ID 2126370.1)
- MOS - Bug 24515918 - PMO_DEFERRED_GIDX_MAINT_JOB causes TX contention on busy tables and runs serially (Doc ID 24515918.8)
- Richard Foote - 12c Asynchronous Global Index Maintenance Part I (Where Are We Now ?)
- Richard Foote - 12c Asynchronous Global Index Maintenance Part II (The Space Between)
- Toad World Blog - Oracle Database 12c Deferred Global Index