본문 바로가기

데이터베이스(DA, AA, TA)/Oracle

[오라클] RAC 튜닝 방법(2)

gc cr/current grant 2-way


프리 블록에 대한 권한을 부여 받는 경우 오라클 10g에서는 gc current grant 2-way 이벤트를 대기한 것으로 관찰되지만, 오라클 9i에서 global cache open x 이벤트를 대기한 것으로 관찰됩니다.


gc cr/current grant 2-way 이벤트는 gc cr/current request 이벤트에 대한 Fixed-up 이벤트로, 블록을 요청한 프로세스가 마스터 노드로부터 블록을 읽을 권한을 부여 받았음을(Grant) 의미합니다. gc cr/current request 이벤트가 gc cr/current grant 2-way 이벤트로 바뀌는(Fixed-up되는) 흐름은 다음과 같습니다.


- 요청 노드의 유저 프로세스가 특정 데이터 블록을 읽고자 한다.


- 유저 프로세스는 해당 데이터 블록의 적절한 버전이 로컬 버퍼 캐시에 없는 것을 확인하고, 마스터 노드의 LMS(Lock Monitoring Services) 프로세스에 블록 전송을 요청한다. 유저 프로세스는 응답을 받을 때까지 gc cr/current request 이벤트를 대기한다.


- 마스터 노드의 LMS 프로세스는 GRD를 참조하여, 클러스터의 어떤 노드도 해당 블록 이미지를 로컬 캐시에 가지고 있지 않다는 것을 확인하고, 요청 노드에 블록을 직접 읽을 권한을 부여한다.


- 유저 프로세스는 블록을 읽을 권한을 부여 받은 후, gc cr/current request 이벤트를 Fixed-up 이벤트인 gc cr/current grant 2-way 이벤트로 변경한다. 권한을 부여 받은 후에는 일반적으로 싱글 블록 I/O를 통해 해당 블록을 디스크에서 직접 읽어 들이며, db file sequential read 이벤트에 대한 대기가 뒤따른다. 만일 로컬 캐시에 BL 락을 업그레이드(S→X) 혹은 다운그레이드(X→S) 가능한 버전의 블록이 있다면 해당 블록에 대해 락 변환을 수행한 후 사용한다.


간단한 테스트를 통해 gc cr grant 2-way 이벤트와 db file sequential read 이벤트의 관계를 확인할 수 있습니다.


-- 마스터 노드가 노드 2인 rac_test 테이블. 노드1과 노드2의 버퍼 캐시를 플러시해서, 클러스터 내의 어떤 노드도 해당 테이블 블록 이미지를 갖지 않도록 한다.

SQL#1> ALTER SYSTEM FLUSH BUFFER_CACHE;

SQL#2> ALTER SYSTEM FLUSH BUFFER_CASHE;

-- 노드 1에서 두 번에 걸쳐 rac_test 테이블에 대해 일관된 읽기 모드의 블록 요청을 수행한다. Full Table Scan을 통해 멀티 블록 I/O 요청이 이루어진다는 사실을 기억하자.

SQL#1> SELECT * FROM rac_test;

SQL#1> /

이 두번의 쿼리를 SQL Trace를 통해 추적한 결과는 다음과 같다.


-- 1번째 요청: 최초의 요청에서는 gc cr grant 2-way 이벤트와 db file sequential read 이벤트에 대한 대기가 순차적으로 발생한다. 이 두개의 대기 이벤트는 세그먼트 헤더 블록에 대한 읽기 요청이다. 데이터 블록들에 대해서는 멀티 블록 I/O가 발생하며, 이 경우 gc cr multi block request 대기와 db file scattered read 이벤트 대기가 반복적으로 발생한다.

select * from rac_test

WAIT #2: nam='SQL*Net message to client' ela=2 p1=16508 p2=1 p3=0

WAIT #2: nam='gc cr grant 2-way' ela=5327 p1=14 p2=10270 p3=4

WAIT #2: nam='db file sequential read' ela=178217 p1=14 p2=10270 p3=1

WAIT #2: nam='gc cr grant 2-way' ela=2634 p1=14 p2=10249 p3=8

WAIT #2: nam='db file sequential read' ela=28691 p1=14 p2=10249 p3=1

WAIT #2: nam='gc cr grant 2-way' ela=4457 p1=14 p2=10269 p3=9

WAIT #2: nam='db file sequential read' ela=33336 p1=14 p2=10269 p3=1

WAIT #2: nam='gc cr multi block request' ela=1881 p1=14 p2=10312 p3=1

WAIT #2: nam='gc cr multi block request' ela=4 p1=14 p2=10312 p3=1

...


(오라클 10g R1에서는 한번의 멀티 블록 I/O에서 요청하는 블록수만큼 gc cr multi block request 대기가 반복적으로 발생하지만, 오라클 10g R2에서는 한번의 멀티 블록 I/O 요청에 대해서 gc cr multi block request 대기는 한번만 발생한다. 이는 멀티 블록 I/O 처리 메커니즘이 개선되었다는 것을 의미한다)

WAIT #2: nam='db file scattered read' ela=117417 p1=14 p2=10297 p3=16


-- 2번째 요청: 글로벌 캐시 동기화 작업이 발생하지 않는다. 1번째 요청을 통해 데이터 블록들에 대해 BL 락을 이미 공유 모드로 획득했기 때문이다.

select * from rac_test

WAIT #2: nam='SQL*Net message from client' ela=225 p1=16581 p2=1 p3=0

WAIT #2: nam='SQL*Net message to client' ela=2 p1=16232 p2=1 p3=0

WAIT #2: nam='SQL*Net message from client' ela=777 p1=16502 p2=1 p3=0


블록을 읽을 권한을 부여하는 과정은 2-way 통신만으로 이루어지기때문에 gc cr/current grant 3-way와 같은 대기이벤트는 존재하지 않는다는 점에 주의하자



* Grant와 디스크 I/O, 락 변환과의 관계

한가지 주의할 점은 gc cr/current grant 2-way 이벤트가 항상 디스크 I/O로 이어지지는 않는다는 점입니다. 만일 락 모드 변환이 가능한 버전의 블록이 있다면 해당 블록을 재활용하며, 이 경우에는 디스크 I/O가 발생하지 않습니다. 이 원리는 gc cr/current grant busy 이벤트에서 동일하게 적용됩니다. 이런면에서, "Grant"라는 용어는 특정 블록을 디스크에서 읽을 권한만을 지칭하는 것이 아니라, BL 락 모드 변환 권한까지 같이 지칭하는 것으로 이해할 수 있습니다. 락 모드 변환은 권한 없음, 널 모드, 공유 모드, 독점 모드 간에 이루어지게 됩니다.


락 모드 변환은 gc cr/current grant ... 류의 이벤트뿐만 아니라 gc cr/current block ... 류의 이벤트에서도 발생한다는 사실에 유의해야 합니다. 가령 현재 널 모드의 블록을 가진 요청 노드가 현재 모드의 블록 이미지를 전송(gc current block 2-way/3-way 이벤트)받은 경우 내부적으로 널 모드의 BL 락을 독점 모드로 업그레이드하는 작업이 수행됩니다.


오라클 9i RAC에서는 이러한 변환에 대해 개별적인 대기 이벤트 명을 사용하기도 합니다. 가령 널 모드의 락을 공유 모드로 변환하고자하는 프로세스는 global cache null to s 이벤트를 대기합니다. 하지만 오라클 10g RAC에서는 항상 gc cr/current request 이벤트만을 사용하며, Fixed-up 이벤트를 이용해서 동일한 레벨의 정보를 제공합니다.



* gc current grant 2-way 이벤트와 HWM 이동과의 관계

gc current grant 2-way 이벤트에 대한 대기와 enq: HW-contention 이벤트에 대한 대기가 같이 관찰되는 경우가 있습니다. 이런 경우 gc current grant 2-way 대기 이벤트는 HWM(High Water Mark)의 이동과 관련이 있습니다. 세그먼트에 새로운 데이터를 추가(Insert)하는 과정에서 프리 블록이 소진되면, 오라클은 HWM을 이동해서 가용한 블록들을 추가로 할당합니다. 새로 추가된 블록들에 대해 Insert 작업을 수행하려면 BL 락을 독점모드로 획득해야 합니다. BL락을 획득하고자 하는 요청 노드의 프로세스는 마스터 노드에게 해당 블록에 대한 권한을 요청한 후 gc current request 이벤트를 대기합니다. 마스터 노드는 해당 블록을 점유한 노드가 없다는 것을 확인한 후, 권한을 부여하는 메시지를 보냅니다. 응답을 받은 요청 노드는 gc current request 이벤트를 gc current grant 2-way 이벤트로 변경합니다.


세그먼트 공간 관리 기법으로 ASSM을 사용하는 경우(오라클 10gR2부터는 기본값)에는 각 노드가 가능한 자신만의 프리 블록을 사용하게끔 보장됩니다. 따라서 노드 A가 할당받은 프리 블록을 노드 B가 사용할 확률이 대단히 낮습니다. 따라서 HWM 이동에 의해 할당된 프리 블록을 획득하는 과정에서 대부분 gc current grant 2-way 이벤트를 대기한 것으로 관찰됩니다. 하지만, 프리 블록에 대한 요구가 매우 많은 경우에는 다른 노드에게 할당된 프리 블록을 훔쳐올 수 있습니다. 이것을 흔히 BMB Stealing(Bitmap block Stealing)이라고 부릅니다. BMB Stealing이 발생할 경우에는 프리 블록을 다른 노드로부터 전송받아야 하므로 gc current block 2-way/3-way 이벤트를 대기한 것으로 관찰될 수 있습니다. ASSM이 아닌 FLM(Free List Management. 수동 모드의 프리 블록 관리 기법)을 사용하는 경우에는 FREELIST GROUPS 속성과 FREELISTS 속성을 적절하게 부여함으로써 ASSM과 같은 효과를 얻을 수 있습니다.



gc cr/current multi block request


로컬 캐시에 존재하지 않는 여러 개의 데이터 블록을 동시에 읽고자 하는 프로세스는, 해당 데이터 블록들을 관리하는 마스터 노드에게 블록 전송을 요청하고, 응답을 받을 때까지 gc cr multi block request 이벤트나 gc current multi block request 이벤트를 대기합니다.


gc cr/current request 이벤트가 싱글 블록 I/O에 의해 발생하는 반면, gc cr/current multi block request 이벤트는 멀티 블록 I/O에 의해 발생합니다. 따라서 gc cr/current request 이벤트는 db file sequential read 이벤트와 유사한 속성을 지니고, gc cr/current multi block request 이벤트는 db file scattered read 이벤트와 유사한 속성을 지닙니다.


FTS(Full Table Scan)와 같은 멀티 블록 I/O를 수행하고자 하는 서버 프로세스는 마스터 노드에게 DB-FILE_MULTIBLOCK_READ_COUNT 파라미터 값만큼 블록 전송 요청을 수행하며, 블록을 전송 받거나 블록을 읽을 권한을 부여 받습니다. 아래 테스트 결과를 보겠습니다. DB_FILE_MULTIBLOCK_READ_COUNT 값에 의해 블록 전송이 어떤 영향을 받는지 알 수있습니다.


-- 멀티 블록에 의한 I/O 수를 8로 변경

SQL> ALTER SESSION SET DB_FILE_MULTIBLOCK_READ_COUNT = 8;

-- 멀티 블록 I/O 수행

SQL> SELECT COUNT(*) FROM multi;

...

-- SQL*Trace를 통해 대기 이벤트 확인

-- 8번의 gc cr multi block request 이벤트 대기 후 db file scattered read 이벤트를 대기한다.

-- 즉, 블록을 읽을 권한을 부여 받았음을 의미한다.

WAIT #1: name='gc cr grant 2-way' ela=285 p1=14 p2=29470 p3=4

WAIT #1: name='db file sequential read' ela=1098 p1=14 p2=29470 p3=1

WAIT #1: name='gc cr multi block request' ela=553 p1=14 p2=29478 p3=1

WAIT #1: name='gc cr multi block request' ela=27 p1=14 p2=29478 p3=1

WAIT #1: name='gc cr multi block request' ela=3 p1=14 p2=29478 p3=1

WAIT #1: name='gc cr multi block request' ela=30 p1=14 p2=29478 p3=1

WAIT #1: name='gc cr multi block request' ela=4 p1=14 p2=29478 p3=1

WAIT #1: name='gc cr multi block request' ela=16 p1=14 p2=29478 p3=1

WAIT #1: name='gc cr multi block request' ela=13 p1=14 p2=29478 p3=1

WAIT #1: name='gc cr multi block request' ela=3 p1=14 p2=29478 p3=1

WAIT #1: name='db file scattered read' ela=7976 p1=14 p2=29471 p3=8

WAIT #1: name='gc cr multi block request' ela=539 p1=539 p2=29486 p3=1

WAIT #1: name='gc cr multi block request' ela=27 p1=27 p2=29486 p3=1

...

(오라클 10g R1에서는 한번의 멀티 블록 I/O에서 요청하는 블록수만큼 gc cr multi block request 대기가 반복적으로 발생하지만, 오라클 10g R2에서는 한번의 멀티 블록 I/O 요청에 대해 gc cr multi block request 대기는 한번만 발생한다.)


gc cr multi block request 이벤트는 CR 모드의 멀티 블록 I/O에 의해 발생합니다. CR 모드의 멀티블록 I/O는 읽기 목적, 즉 SELECT에 의한 FTS(Full Table Scan)나 IFFS(Index Fast Full Scan) 등에서 발생합니다. 반면, gc current multi block request 이벤트는 Current 모드의 멀티 블록 I/O에 의해 발생합니다.


대표적인 사례는 FTS를 통한 Update나 Delete 작업 그리고 Insert 작업을 위해 새로운 데이터 블록을 읽어오는 경우입니다. 아래 예를 통해 Insert 작업과 gc current multi block request 대기 이벤트의 관계를 알 수 있습니다.


-- Insert 수행

SQL> INSERT INTO multi VALUES(1, 'TEST');

...

-- 16번(DB_FILE_MULTIBLOCK_READ_COUNT)의 gc current multi block request 대기 이후 db file scattered read 이벤트를 대기한다.

-- 즉, 멀티 블록을 Current 모드로 직접 읽을 권한을 부여 받았음을 알 수 있다.

WAIT #2: nam='gc current grant 2-way' ela=298 p1=14 p2=29451 p3=33619976

WAIT #2: nam='gc current multi block request' ela=6 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=35 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=12 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=6 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=5 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=4 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=7 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=4 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=0 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=4 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=2 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=3 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=2 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=7 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=4 p1=14 p2=29630 p3=33554446

WAIT #2: nam='gc current multi block request' ela=3 p1=14 p2=29630 p3=33554446

WAIT #2: nam='db file scattered read' ela=16045 p1=14 p2=29615 p3=16

...


Current 모드의 멀티 블록 I/O 요청에서 한 가지 주의할 점은, 항상 16개의 블록 단위로 멀티 블록 I/O를 수행한다는 것입니다. CR 모드의 멀티 블록 I/O는 DB_FILE_MULTIBLOCK_READ_COUNT 파라미터로 한 번에 처리되는 블록 수를 제어할 수 있지만, Insert 작업에 의한 Current 모드의 멀티 블록 I/O는 파라미터의 값과 무관하게 항상 16 블록 단위로 작업이 이루어집니다.


gc cr/current multi block request 이벤트에 대한 대기를 해소하는 방법은 기본적으로 gc cr/current request 이벤트와 동일합니다. 단, 멀티 블록 전송에 의한 네트워크 부하 발생이 가능하기 때문에 네트워크 설정과 한번에 전송 요청하는 블록의 수의 적정치를 고려할 필요가 있습니다.


Wait Time

: 대기 시간


Parameter

: P1(File#), P2(Block#), P3(Class#)


Common Causes and Actions

- 원인

SQL 수행에 필요한 인덱스가 존재하지 않아, 비효율적인 Full Table Scan이 발생하였거나, Full Table Scan 속도를 네트워크가 따라가지 못할 경우 해당 대기 이벤트가 높게 발생한다.


- 진단 방법

1. V$SQL_PLAN 및 V$SQL에서 Full Table Scan을 수행하는 악성 SQL이 없는지 검토한다.

2. gc cr multi block request 이벤트의 타임아웃이 높지는 않은지 조사한다.


- 개선 방법

INDEX가 존재하지 않아 과도하게 FTS(Full Table Scan)를 수행하는 SQL이 없는지 검토해야 한다.

Full Table Scan을 수행하는 SQL문들을 추출하고, Full Table Scan이 적합한지 판단한 뒤, Index가 필요한 경우라면 Index를 생성하여, 비효율적인 Full Table Scan을 원천적으로 제거하고, 해당 이벤트의 경합을 최소화한다.


gc cr multi block request 이벤트가 타임아웃이 높게 나온다면, 현재 네트워크 설정으로는 많은 수의 블록 요청을 동시에 처리할 수 없음을 의미하므로, 이런 경우 DB_FILE_MULTIBLOCK_READ_COUNT 파라미터 값을 낮추는 것을 고려할 필요성이 있다.



* gc cr multi block request 이벤트와 요청 블록의 수

CR 모드의 멀티 블록 전송 요청에서는 항상 DB_FILE_MULTIBLOCK_READ_COUNT 파라미터 값만큼의 블록 수를 동시에 요청합니다. 지나치게 많은 블록을 동시에 요청하면 요청과 응답 메시지의 크기가 커져서 작업이 지연되는 현상이 발생할 수 있습니다. 특히 gc cr multi blcok request 이벤트의 타임 아웃(Time Out)횟수가 높게 나온다면 현재 네트워크 설정으로는 많은 수의 블록 요청을 동시에 처리할 수 없다는 것을 암시합니다. 이런 경우에는 DB_FILE_MULTIBLOCK_READ_COUNT 파라미터의 값을 낮추는 것을 고려할 필요가 있습니다. 경우에 따라서 8이나 4와 같은 낮은 값을 사용해도 무방합니다.


* gc cr multi block request 이벤트와 네트워크 설정

MTU(Maximum Transmission Unit) 크기를 크게 하면 하나의 패킷에 많은 양의 메시지를 담을 수 있기 때문에 멀티 블록 I/O 요청에 더 유리합니다. 대량의 멀티 블록 I/O 요청을 효율적으로 처리하기 위해서는 큰 크기의 UDP Receive 버퍼가 필수적입니다. 만일 인터커넥트에서 잦은 패킷 유실이 발생한다면 더욱 그러합니다. 오라클은 기본적으로 OS에서 설정한 버퍼 크기를 사용하며, 최솟값으로 128KB를 사용합니다. 가능한 256KB 이상의 UDP 버퍼 크기를 사용하고, 필요하다면 수  MB 정도의 크기를 사용하는 것도 고려해 볼 수 있습니다.



gc buffer busy


gc buffer busy 이벤트는 로컬 프로세스가 읽고자 하는 블록이 현재 리모트 노드의 요청에 의해 사용 중임을 의미하는 이벤트입니다. gc buffer busy 이벤트는 Placeholder/Fixed-up의 분류에 따르지 않는 독립이벤트입니다.




gc buffer busy 이벤트는 buffer busy waits 이벤트나 read by other session 이벤트의 글로벌 버전으로 이해하면 됩니다. 서버 프로세스가 특정 블록을 사용하고자 하는 시점에 버퍼 락 경합이 발생하면 대기하게 되는데, 그 발생 사유에 따라 이들 대기 이벤트들 중 하나를 사용하게 됩니다.


Wait Time

: 대기 시간


Parameter

: P1(File#), P2(Block#), P3(id)


Common Causes and Actions

- 원인

HOT BLOCK 및 비효율 SQL로 I/O 과다

- 진단 방법

AWR(Automatic Workload Repository) 모든 주요 통계와 작업 로드에 대한 정보를 스냅샷 형태로 저장하는 저장소 중 문제가 발생했던 시점에 해당하는 DBA_HIST_SEG_STAT.GC_BUFFER_BUSY_DELTA 값이 높은 OBJECT를 확인한다. (인덱스라면, 우편향 인덱스로 인한 문제인지 확인한다.)

- 개선 방법

핫블록 문제라면, 핫 블록을 분산시킴으로써 문제를 해결할 수 있다. 세그먼트 레벨의 파티셔닝 적용, 우편향 인덱스 현상의 해소, 시퀀스 캐시 크기의 증가, PCTFREE의 증가 등이 보편적으로 사용되는 방법

SQL 튜닝을 통해 불필요하게 많은 블록이 교환되는 것을 줄인다.


- 원인

FLM 사용시 헤더 블록의 Buffer 경합

- 진단 방법

ORACLE의 BLOCK 관리 방법이 혹시 FLM 방식인지 확인

- 개선 방법

FLM(Free List Management)을 사용하는 경우에는 세그먼트 헤더 블록이 버퍼 경합의 원인이 될 수 있다. 다행히 오라클 10g R2부터는 ASSM(Automatic Segment Space Management)이 기본적으로 사용되기 때문에 FLM 환경에서 발생하는 성능 문제가 크게 줄어든다. 만일 FLM을 사용하는 환경이라면, 반드시 FREELIST GROUPS 속성을 노드 수와 동일하게 설정해서 세그먼트 헤더 블록의 경합을 최소화해야 한다.


- 원인

동일 SEGMENT를 여러 노드에서 동시에 DML 수행

- 진단 방법

동일 OBJECT에 대해 DML이 동시 다발적으로 수행되는지 확인한다.

- 개선 방법

동일한 세그먼트에 대한 대량의 DML 작업이 여러 노드에서 동시 다발적으로 발생하면 광범위한 버퍼 락 경합이 발생할 수 있다. 애플리케이션 실행을 적절히 분배하는 것이 해결 방법이다.


* gc buffer busy wait event가 많이 발생하는 object 추적

SELECT b.owner, obejct_name,
	object_type, buffer_busy_wait
FROM (
	SELECT obj#,
		dataobj#,
		SUM(gc_buffer_busy_delta) buffer_busy_wait
	FROM dba_hist_seg_stat a
	WHERE snap_id BETWEEN 10
	AND   20
	GROUP BY obj#,
		dataobj#
	ORDER BY 3 DESC
	) a,
	dba_hist_seg_stat_obj b
WHERE ROWNUM >= 10
AND   a.obj#=b.obj#
AND   a.dataobj#=b.dataobj#

AWR(Automatic Workload Repository)을 이용하면 buffer busy wait이 높게 발생한 object를 손쉽게 조회가 가능합니다. DBA_HIST_SEQ_STAT_OBJ와 DBA_HIST_SEQ_STAT VIEW를 활용하면 됩니다. 위의 SCRIPT는 간단한 조회 SQL입니다. 위와같인 buffer busy wait이 높은 object를 먼저 찾는다면, 조금 더 쉽게 문제의 원인을 찾을 수 있을 것입니다.



gc current block busy(Redo Flush에 의한 지연)


gc cr/current block busy 이벤트는 gc cr/current request 이벤트에 대한 Fixed-up 이벤트로, 홀더 노드로부터 블록 이미지를 전송받는 과정에서 경합이 발생했다는 것을 의미합니다. gc cr/current request 이벤트가 gc cr/current block busy 이벤트로 변경되는 흐름은 다음과 같습니다.


- 요청 노드의 유저 프로세스가 특정 데이터 블록을 읽고자 한다.


- 유저 프로세스는 해당 데이터 블록의 적절한 버전이 로컬 버퍼 캐시에 없는 것을 확인하고, 마스터 노드의 LMS 프로세스에 블록전송을 요청한다. 유저 프로세스는 응답을 받을 때까지 gc cr/current request 이벤트를 대기한다.


- 마스터 노드(즉, 홀더 노드)의 LMS 프로세스는 요청된 블록에 대해 BL 락을 공유 모드로 획득할 수 있어야 한다. 만일 필요한 노드의 락을 획득하는 과정에서 지연이 발생하면 요청 노드에 블록을 전송하되, 블록을 읽어들이는 과정에서 경합(Contention)이 발생했음을 같이 알린다.


- 유저 프로세스는 블록을 전송 받은 후 응답 메시지로부터 경합이 발생했음을 확인하고, gc cr/current request 이벤트를 Fixed-up 이벤트인 gc cr/current block busy 이벤트로 변경한다.


gc cr/current block busy 대기 이벤트는 블록을 전송 받는 과정에서 경합으로 인한 지연(Delay)이 발생했다는 것을 의미합니다. 오라클은 "경합(Contention)""혼잡(Congestion)"이라는 두 가지 사유에 의해 글로벌 캐시 동기화 과정에서 지연 현상이 발생하는 것으로 정의하고 있습니다. 경합(Contention)이 지연의 발생 사유인 경우에는 "Busy"라는 용어를 사용하며, 혼잡(Congestion)이 발생 사유인 경우에는 "Congested"라는 용어를 사용한다는 것을 기억합니다.


홀더 노드의 LMS 프로세스가 블록을 전송하는 과정에서 경합에 의한 지연이 발생하는 이유에는 다음과 같은 것들이 있습니다.


- 요청된 블록이 로컬 프로세스에 의해 현재 사용 중인 경우. 현재 변경 중인 블록에 대해서는 버퍼 락(Buffer Lock)이 독점 모드로 획득된 상태이므로, LMS 프로세스는 해당 블록의 변경이 완료될 때까지 대기해야 한다. Update 문에 의해 변경 중이거나, Select 문에 의해 디스크에 버퍼 캐시로 적재되고 있는 경우를 포함한다.


- 리두 플러시(Flush) 과정이 발생하는 경우. 홀더 노드의 로컬 프로세스에 의해 변경된 후 아직 Commit이 이루어지지 않은 블록을 요청 노드로 전송하려면, 블록의 변경 내용을 리두 로그에 기록해야 한다. 이 과정을 "리두 플러시"라고 부른다. 이 과정에서 지연이 발생하면 홀더 노드에서는 gcs logs flush sync 이벤트 대기 시간이 높거나, gc cr block flush time 통계 값이나 gc current block flush time 통계값이 높게 나온다. LMS 프로세스가 블록 전송을 위해 더티 블록에 대한 변경 내역을 로그 파일에 기록하는 경우에는 log file sync 이벤트가 아닌 gcs log flush sync 이벤트를 대기하는 것으로 관찰된다는 사실에 유의하자.


Parameter

: gc cr/current block busy 이벤트와 같은 Fixed-up 이벤트는 P1, P2, P3 값이 별도로 부여되지 않으며, Placeholder 이벤트(여기서는 gc cr request 이벤트)와 동일한 값을 가지는 것으로 해석하면 된다.


Common Causes and Actions

- 원인

요청된 블록이 로컬 프로세스에 의해 현재 사용하는 경우

- 진단 방법

Update 문에 의해 변경 중이거나, Select 문에 의해 디스크에서 버퍼 캐시로 적재되고 있는 경우를 의미한다.

- 개선 방법

SQL 튜닝을 통해 읽기 작업과 변경 작업이 동일 블록을 액세스하는 경우의 수를 줄인다.

버퍼 캐시 튜닝을 통해 메모리로 읽어 들인 블록이 캐시에서 밀려나는 경우의 수를 줄인다.

블록 분산 기법(파티셔닝, 리버스 키 인덱스, 시퀀스 캐시 크기)을 이용해 핫 블록이 발생할 확률을 줄인다.

시퀀스의 캐시 크기를 크게 부여한 gc cr block busy 이벤트 대기는 블록 경합에 의해 발생하기 때문에, 인터커넥트의 성능과 무관하게 발생할 수 있다는 사실에 유의해야 한다. 즉 블록을 전송하는 과정에서 지연이 발생하는 것이 아니라, 홀더 노드가 블록 전송을 준비하는 과정에서 지연이 발생하는 것이다.


- 원인

리두 플러시(Flsuh) 과정이 발생하는 경우

- 진단 방법

log file sync이벤트가 아닌 gcs log flush sync 이벤트를 대기하는지를 관찰한다.

- 개선 방법

LGWR 프로세스의 성능을 극대화한다. 리두 로그가 위치한 디바이스를 최적으로 구성한다. LGWR 프로세스의 CPU 우선순위를 높이는 것도 좋은 방법이 된다.

SQL 튜닝을 통해 읽기 작업과 변경 작업이 동일 블록을 액세스하는 경우의 수를 줄인다.

시퀀스의 캐시 크기를 크게 부여 GC CR BLOCK BUSY 이벤트 대기는 블록 경합에 의해 발생하기 때문에, 인터커넥트의 성능과 무관하게 발생할수 있다는 사실에 유의해야 한다. 즉 블록을 전송하는 과정에서 지연이 발생하는 것이 아니라, 홀더 노드가 블록 전송을 준비하는 과정에서 지연이 발생하는 것이다. 따라서 블록 경합을 줄이거나, 블록 경합 과정에서 발생하는 리두 플러시를 최적화하는 것이 튜닝방법이 된다. 물론, 인터커넥트의 성능이 낮으면 대기 횟수에 비해서 대기 시간은 늘어날 수 있다.


간단한 테스트를 통해 gc cr block busy 이벤트 대기와 블록 변경과의 관계를 확인할 수 있다.


-- 노드 2가 마스터 노드인 rac_test 테이블.

-- 노드 2에서 rac_test 테이블에 대해 Update 수행 후 Commit을 수행하지 않음

SQL#2> UPDATE rac_test SET id=3;


-- 노드1에서 rac_test 테이블을 Select. 노드2에 의해 변경된 블록을 읽어 들인다.

SQL#1> SELECT * FROM rac_test;

SQL#1> /

... (같은 쿼리를 여러번 수행)

-- 위의 쿼리들에 대해 SQL Trace를 수행한 결과는 다음과 같다.

-- 홀더 노드에서의 리두 플러시에 의해 지연이 발생하고, 이로 인해 요청 노드는 gc cr block busy 이벤트를 대기하는 것을 확인할 수 있다.

-- 단, 리두 플러시 과정에 매우 가볍다면 gc cr block busy 이벤트가 아닌 gc cr block 2/3-way 류의 이벤트로 관찰된다.

select * from rac_test

WAIT #2: nam='SQL*Net message to client' ela=3 p1=16508 p2=1 p3=0

WAIT #2: nam='gc cr block busy' ela=68615 p1=14 p2=10344 p3=1

WAIT #2: nam='SQL*Net message from client' ela=313 p1=16508 p2=1 p3=0
WAIT #2: nam='SQL*Net message to client' ela=2 p1=16508 p2=1 p3=0
WAIT #2: nam='SQL*Net message from client' ela=410 p1=16508 p2=1 p3=0


* 요청 모드와 busy 이벤트

gc cr/current block busy 이벤트가 발생하는 정확한 시점은 블록 요청이 CR 모드인지, Current 모드인지에 따라 약간 다릅니다. 요청 노드의 CR 모드 요청에 대해 홀더 노드가 아직 Commit이 이루어지지 않은 현재 블록을 전송하는 경우에는 반드시 CR 블록, 즉 변경 이전 이미지를 전송해야 합니다. 문제는 홀더 노드의 버퍼 캐시에 CR 이미지가 존재하지 않는 경우입니다. 이 경우 현재 블록의 카피 버전인 CR 카피 블록을 생성하고, 언두 이미지를 디스크에서 읽은 후, CR 카피 블록에 롤백을 수행하는 일련의 과정을 거쳐야 합니다. 또한 현재 블록에 대한 리두 플러시를 수행합니다. 이러한 일련의 관정이 홀더 노드의 LMS 프로세스에 지나친 부하를 주기 때문에, 오라클은 불완전한 이미지의 CR 블록을 요청 노드로 전송합니다. 불완전한 CR 블록을 전송 받은 요청 노드가 스스로 완전한 이미지의 CR 블록을 생성하게 됩니다. 이것을 Light Weight Rule(경량화 법칙)이라고 부릅니다. V$CR_BLOCK_SERVER 뷰를 조회하면 전체 일관된 작업 중 Light Weight Rule이 몇번이나 적용되었는지 확인할 수 있습니다.


SQL> SELECT cr_requests, light_works FROM v$cr_block_server

CR_REQUESTS              LIGHT_WORKS

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

35331145                    3866363


Commit이 이루어지지 않은 더티 블록에 대한 CR 모드 요청에 대해서는 주로 gc cr block 2-way/3-way 류의 이벤트를 Fixed-up 이벤트로 사용합니다. 이 과정에서 리두 플러시 과정에서 지연이 발생하면 gc cr block busy 이벤트를 Fixed-up 이벤트로 사용합니다.


반면 Commit이 이루어진 더티 블록에 대해 Current 모드의 요청이 발생하면, 즉각 락 다운그레이드가 발생합니다. 이 경우 요청 노드는 일반적으로 gc current block 2-way/3-way 류의 이벤트를 Fixed-up 이벤트로 사용하며, 만일 홀더 노드의 리두 플러시 과정에서 지연이 발생하면 gc current block busy 이벤트를 Fixed-up 이벤트로 사용합니다.



gc cr/current block congested


gc cr/current block congested 이벤트는 gc cr/current request 이벤트에 대한 Fixed-up 이벤트로, 홀더 노드로부터 블록 이미지를 전송받는 과정에서 혼잡에 의한 지연이 발생했다는 것을 의미합니다. gc cr/current request 이벤트가 gc cr/current block congested 이벤트로 변경되는 흐름은 다음과 같습니다.


- 요청 노드의 유저 프로세스가 특정 데이터 블록을 읽고자 한다.


- 유저 프로세스는 해당 데이터 블록의 적절한 버전이 로컬 버퍼 캐시에 없는 것을 확인하고, 마스터 노드의 LMS 프로세스에 블록 전송을 요청한다. 유저 프로세스는 응답을 받을 때까지 gc cr/current request 이벤트를 대기한다.


- 홀더 노드의 LMS 프로세스는 요청 큐(Request Queue)로부터 요청 메시지를 확인한다. 만일 메시지가 요청 큐에 도착한 시간과 LMS 프로세스가 메시지를 확인한 시간이 1ms 이상 차이가 나면, 즉, LMS 프로세스가 요청 메시지를 확인하는 과정에서 1ms 이상의 지연이 발생하면 오라클은 인텀커넥트에 혼잡이 발생했다고 판단한다. LMS 프로세스는 응답 메시지에 혼잡이 발생했음을 같이 알린다.


- 유저 프로세스는 블록을 전송 받은 후 응답 메시지로부터 혼잡이 발생했음을 확인하고, gc cr/current request 이벤트를 Fixed-up 이벤트인 gc cr/current block congested 이벤트로 변경한다.


Wait Time

대기 시간


Parameter

gc cr/current block congested 이벤트와 같은 Fixed-up 이벤트는 P1, P2, P3 값이 별도로 부여되지 않으며, Placeholder 이벤트(여기서는 gc cr request 이벤트)와 동일한 값을 가지는 것으로 해석하면 된다.


LMS의 지연 이유와 해결책

LMS 프로세스가 요청 메시지를 확인하는 과정에서 지연(1ms 이상)이 발생하는 이유와 해결책은 다음과 같습니다.


- 인터커넥트를 통한 메시지 요청 자체가 지나치게 많은 경우에는 LMS 프로세스의 메시지 확인 작업에 지연이 발생하게 됩니다. 따라서 SQL 튜닝이나 버퍼 캐시 튜닝, 핫블록 해소 등을 통해 불필요하게 많은 블록 전송 요청이 발생하는 것을 방지하는 것이 해결책이 됩니다.

LMS 프로세스가 필요한 CPU 자원을 원활하게 보장받지 못하는 경우에도 메시지 확인 작업의 지연이 발생할 수 있습니다. LMS 프로세스의 성능을 극대화하는 방법을 간략히 정리하면 다음과 같습니다.


- LMS 프로세스에 대해 실시간 스케줄링을 적용합니다. 시분할 스케줄링을 적용할 경우에는 LMS 프로세스의 NICE 값을 낮추어서 보다 높은 우선 순위를 할당 받도록 합니다.


오라클 10g R2부터는 LMS 프로세스는 기본적으로 실시간 스케줄링 기법을 사용한다.


CPU 자원이 충분한 경우에는 LMS 프로세스의 수를 증가시킨다.


OS 차원의 불필요한 작업을 제거해서 CPU 자원을 확보하는 것 또한 중요하다. 가령 메모리 부족으로 인해 페이징(Paging) 작업이 빈번하게 발생한다면 LMS 프로세스의 성능에 부정적인 영향을 주게 된다.



* RAC의 Contention & Congestion

RAC에서 경합(Contention)과 혼잡(Congestion)이라는 두 용어가 의미하는 바를 정확히 이해할 필요가 있습니다. "경합"은 홀더 노드의 LMS 프로세스가 블록을 처리하는 과정에서 경쟁에 의한 지연이 발생했음을 의미하는 것이고, "혼잡"은 홀더 노드의 LMS 프로세스가 요청 메시지를 처리하는 과정, 즉 블록을 처리하기 직전의 과정에서 지연이 발생했음을 의미합니다. 이 두 현상은 많은 경우 동시에 발생하지만, 경우에 따라서는 서로 무고나하게 발생합니다. 두 현상의 공통점과 차이점을 다시한번 정리하면 아래와 같습니다.




gc current split


gc current split 이벤트는 gc cr/current request 이벤트에 대한 Fixed-up 이벤트로, 홀더 노드가 인덱스 블록을 전송하는 과정에서 인덱스 분할(Index Split)이 발생했음을 의미합니다. gc cr/current request 이벤트가 gc current split 이벤트로 변경되는 흐름은 다음과 같습니다.


요청 노드의 유저 프로세스가 특정 인덱스 블록을 읽고자 합니다.


- 유저 프로세스는 해당 블록의 적절한 버전이 로컬 버퍼 캐시에 없는 것을 확인하고, 마스터 노드의 LMS 프로세스에 블록 전송을 요청한다. 유저 프로세스는 응답을 받을때까지 gc current request 이벤트를 대기한다. 


- 홀더 노드의 LMS 프로세스가 해당 인덱스 블록에 대해 락을 획득하려고 하는 시점에 인덱스 분할(Index Split)이 발생하고 있다면, 인덱스 분할이 끝날때까지 대기해야 한다. 인덱스 분할이 끝나고 나면 홀더 노드는 해당 블록과 함께 인덱스 분할이 발생했음을 나타내는 메시지를 함께 전송한다.


- 유저 프로세스는 블록을 전송 받은 후 응답 메시지로부터 전송 과정에서 인덱스 분할이 발생 중임을 확인하고, gc cr/current request 이벤트를 Fixed-up 이벤트인 gc current split 이벤트로 변경한다.


gc current split 이벤트는 루트 노드, 브랜치 노드, 리프 노드 블록에 무관하게 해당 블록이 인덱스 분할을 위해 블로킹되고 있는 상황에서 공통적으로 발생할 수 있습니다. 특정 리프 노드를 분할하는 과정에서 그 부모 노드의 블록도 변경이 금지되기 때문입니다.


gc current split 이벤트와 관련 있는 또 하나의 대기 이벤트는 enq: TX-index contention 이벤트입니다. 테이블의 특정 로우를 변경하고자 하는 프로세스는 만일 관련된 인덱스 블록이 다른 프로세스에 의해 분할되고 있다면, 인덱스 분할이 완료될 때까지 기다려야 합니다. 이때 대기하는 이벤트가 enq: TX-index contention 이벤트입니다.


아래 결과는 여러 프로세스에 의한 동시 INSERT 작업에 의한 인덱스 분할이 자주 발생하는 작업 하에서 SQL Trace를 이용해 특정 프로세스의 대기 현상을 캡쳐한 것으로 gc current split 이벤트와 함께 enq: TX-index contention 이벤트가 관찰되는 것을 확인할 수 있습니다.


WAIT #9: nam='gc current split' ela=140584 p1=14 p2=28311 p3=33554433 

WAIT #9: nam='enq: TX - index contention' ela=5478 p1=141505 p2=32737 p3=6585 

...

WAIT #9: nam='gc current split' ela=66390 p1=14 p2=28410 p3=33554433 

WAIT #9: nam='enq: TX - index contention' ela=1343 p1=141505 p2=33420 p3=6576 

...

WAIT #9: nam='gc current block 2-way' ela=1139 p1=14 p2=28421 p3=33554433 

WAIT #9: nam='enq: TX - index contention' ela=163 p1=141505 p2=33424 p3=6576 


Parameter

gc current split 이벤트와 같은 Fixed-up 이벤트는 P1, P2, P3 값이 별도로 부여되지 않으며, Placeholder 이벤트(여기서는 gc cr request 이벤트)와 동일한 값을 가지는 것으로 해석하면 된다.


Common Causes and Actions

- 원인

Index Split 발생

- 진단 방법

우편향 Index가 Split을 하는지 확인한다.

- 개선 방법

Fixed-up 이벤트로 gc current split 이벤트가 많이 목격된다는 것은 동일 테이블 블록에 대한 인덱스 불할이 과도하게 발생한다는 것을 의미한다. 이 이벤트에 대한 대기를 해소하는 기법은 enq: TX-index contention 이벤트를 해소하는 기법과 동일하다.

시퀀스 값을 사용해서 인덱스 키를 생성하는 경우에는 가장 오른쪽 리프 블록에 삽입이 집중되어 인덱스 분할에 의한 경합이 많이 발생한다. 이 경우에는 다음과 같은 값을 적용할 수 있다.


1. 인덱스 키 조합을 변경하여 우편향(Right-Hand) 현상을 해소할 수 있다.

2. 시퀀스의 캐시 크기를 증가시킨다. 시퀀스의 캐시 크기가 크면 각 노드에서 사용하는 시퀀스의 값 집합이 큰 차이를 보이기 때문에 동일한 인덱스 리프 블록에 대한 경합이 줄어든다.

3. 인덱스 블록의 블록 크기를 키우는 방법도 사용 가능하다. 블록 크기가 크면 그만큼 인덱스 분할이 덜 발생하기 때문이다. 하지만 블록 크기는 여러 가지 상황을 고려한 후 결정해야 하는 사안이므로 신중히 접근해야 한다. 블록의 크기가 커지면 인덱스 분할 횟수는 줄일 수 있지만 버퍼 락에 의한 경합은 증가하는 경향이 있기 때문이다.


* Index Split이란

Index Split이란, Leaf Node나 Branch Node가 꽉 찬 상태에서 새로운 키 값이 삽입될 때 공간 확보를 위해서 키 값을 Split하는 것을 말합니다. Index Split이 발생하면 새로운 블록을 할당받고, 기존의 키 값들을 기존 블록과 새로운 블록으로 나누어(Split) 저장합니다.


Index Split은 보통 50-50 Split과 90-10 Split으로 구분됩니다.


50-50 Split은 중간에 위치한 Leaf Node가 Split되는 경우에 발생합니다. 기존 블록과 새로운 블록이 값을 50:50으로 나누어 갖는다고 해서 50-50 Split이라는 이름이 붙여졌습니다.


90-10 Split은 가장 오른쪽 마지막에 위치한 Leaf Node에 최대값이 insert되는 경우에 발생합니다. 기본 블록과 새로운 블록이 값을 90:10으로 나누어 갖는다고 해서 90-10 Split이라는 이름이 붙여졌습니다.


엄밀하게 말하면 90-10이 아니라 99-1이 맞을 것입니다. 새로운 블록에는 기존 블록 중 최댓값만이 들어가고 나머지는 모두 기존 블록에 남기 때문입니다. 그리고 이 새로운 블록에 새로운 최댓값이 추가됩니다.


최댓값이 insert되는 경우에 한해 90-10 Split을 수행하는 이유는 Split의 횟수를 줄이기 위해서입니다. 제일 오른쪽 블록에 insert가 집중되는 경우에 대비해 제일 오른쪽 블록에 여유 공간을 많이 만들기 위한 일종의 트릭입니다.



gc cr failure


gc Block을 전송받는 과정에서 실패가 발생했음을 의미하는 Fixed-up Event입니다. 이는 보통 Block 손실, 잘못된 block을 요청하여 프로세스가 요청에 대한 처리를 할 수 없었다고 볼 수 있습니다.


보통 gc cr Failure 대기 이벤트가 발생하기 전에 gc cr request와 같은 이벤트가 발생하는 경향이 높습니다.


Parameter

gc cr failure 이벤트와 같은 Fixed-up 이벤트는 P1, P2, P3 값이 별도로 부여되지 않으며, Placeholder 이벤트 (여기서는 gc cr request 이벤트)와 동일한 값을 가지는 것으로 해석하면 된다.


Common Causes and Actions

- 원인: 네트워크가 지연되서 발생하는 문제

- 진단 방법: V$SYSSTAT에서 gc block lost나 gc claim block lost 지표를 확인하고, LMS의 상태 그리고 Interconnect의 상태를 점검한다.

- 개선 방법: LMS의 지연 이유와 해결책

LMS 프로세스가 요청 메시지를 확인하는 과정에서 지연(1ms 이상)이 발생하는 이유와 해결책은 다음과 같다.


- 인터커넥트를 통한 메시지 요청 자체가 지나치게 많은 경우에는 LMS 프로세스의 메시지 확인 작업에 지연이 발생하게 된다. 따라서 SQL 튜닝이나 버퍼 캐시 튜닝, 핫 블록 해소 등을 통해 불필요하게 많은 블록 전송 요청이 발생하는 것을 방지하는 것이 해결책이 된다.


LMS 프로세스가 필요한 CPU 자원을 원활하게 보장받지 못하는 경우에도 메시지 확인 작업의 지연이 발생할 수 있다. LMS 프로세스의 성능을 극대화하는 방법을 간략히 정리하면 다음과 같다.


- LMS 프로세스에 대해 실시간 스케줄링을 적용한다. 시분할 스케줄링을 적용할 경우에는 LMS 프로세스의 NICE 값을 낮추어서 보다 높은 우선순위를 할당 받도록 한다.



* gc blocks lost

블록 전송 과정에서 유실(Lost)된 블록의 수를 의미합니다. 블록 유실은 RAC 시스템의 성능에 결정적인 영향을 미치기 때문에 gc blocks lost 통계 값은 가능한 낮은 수치를 유지해야 합니다. 만일 gc blocs lost 통계 값이 증가한다면 다음과 같은 확인 절차를 거쳐야 합니다.


네트워크 설정이나 하드웨어 설정에 이상 없는가를 점검합니다. netstat과 같은 툴을 이용해서 네트워크 패킷 에러가 발생하지 않는지 점검하고, 네트워크 파라미터가 지나치게 작게 설정되어 있지 않은지 확인합니다. 네트워크 버퍼 크기를 크게하고, MTU의 크기를 크게 하는 것 등도 해결 방법이 될 수 있습니다.


잘못된 네트워크 프로토콜을 사용하고 있지 않은가를 점검합니다. 대부분의 OS에서 오라클은 UDP를 기본 프로토콜로 사용할 것을 권장합니다. 많은 종류의 프로토콜에 대해 RAC 성능 테스트와 적용이 이루어졌지만, UDP에서 가장 안정적으로 작동하는 것이 경험적으로 검증되었습니다. 만일 특정 벤더가 제공하는 특정 프로토콜을 사용한다면 반드시 오라클로부터 검증을 거쳐야 합니다.


지나치게 인터커넥트 부하가 높은 경우에는 패킷 유실을 피할 수 없습니다. 이 경우에는 네트워크 대역폭을 높이거나 SQL/애플리케이션 튜닝을 통해 블록 전송 수를 줄여야 합니다.