Gc cr/current request

EXEM Knowledge Base

(V$CURRENT BLOCK SERVER에서 넘어옴)
Jump to: navigation, 찾기

목차

[편집] Basic Info

로컬 캐시에 존재하지 않는 데이터 블록을 읽고자 하는 프로세스는, 해당 데이터 블록을 관리하는 마스터 노드에게 블록 전송을 요청하고, 응답을 받을 때까지 gc cr request 이벤트나 gc current request 이벤트를 대기한다. 이벤트의 파라미터 정보는 다음과 같다.

SQL> SELECT 
	name, parameter1, parameter2, parameter3 
FROM  v$event_name 
WHERE name = 'gc cr request';
NAME				PARAMETER1	PARAMETER2	PARAMETER3
-----------------------------------------------------------------------
gc cr request			file#		block#		class#

P1(file#) 값과 P2(block#) 값은 데이터 블록에 대한 정보를 제공한다. P3(class#) 값은 데이터 블록의 클래스(Class)를 의미한다. 오라클은 총 18가지의 데이터 블록을 사용하며 아래와 같이 V$WAITSTAT 뷰를 통해 조회 가능하다.

SQL> SELECT ROWNUM AS class#, class FROM v$waitstat
    CLASS# CLASS
-------- ------------------
         1 data block
         2 sort block
         3 save undo block
         4 segment header
         5 save undo header
         6 free list
         7 extent map
         8 1st level bmb
         9 2nd level bmb
        10 3rd level bmb
        11 bitmap block
	        12 bitmap index block
        13 file header block
        14 unused
        15 system undo header
        16 system undo block
        17 undo header 
        18 undo block

어떤 클래스의 데이터 블록에 대해 경합 및 대기가 발생하느냐가 대단히 중요하다. 블록의 종류, 즉 일반 데이터 블록인지, 세그먼트 헤더 블록인지, 언두 블록인지, 비트맵 블록인지 등에 따라 문제를 해결하는 방법이 전혀 다를 수 있기 때문이다. 또한 gc cr request 이벤트와 gc current request 이벤트는 어떤 모드로 블록을 요청하느냐, 즉 CR 모드인가, 혹은 Current 모드인가의 차이만 있을 뿐이다. 따라서 이벤트의 발생 이유와 대기 현상을 해소하는 기법은 모두 동일하다.

클래스# 15 이상의 데이터 블록은 언두 영역의 블록을 의미하며, 롤백 세그먼트 번호에 의해 결정된다. 
클래스#와 롤백 세그먼트 번호(r)의 관계는 다음과 같다.
  • 언두 헤더 블록: 클래스# = 2*r + 15
  • 언두 블록: 클래스# = 2*r + 16
SYSTEM 롤백 세그먼트는 r 값이 0이기 때문에 언두 헤더 블록의 클래스# = 2*0 + 15 = 15 가 되며, 
언두 블록의 클래스# = 2*0 + 16 = 16 이 된다. 
또한 롤백 세그먼트 번호에 따라 클래스#는 {17,18}, {19,20}, {21,22} … 의 순으로 증가한다.

gc cr/current request 대기 이벤트는 db file sequential read 이벤트와 거의 유사한 속성을 지닌다. db file sequential read 대기 이벤트가 디스크 레벨에서 싱글 블록 I/O 작업을 처리하는 과정에서 발생한다면, gc cr/current request 대기 이벤트는 인터커넥트 레벨에서 싱글 블록 I/O 작업을 처리하는 과정에서 발생한다는 차이가 있을 뿐이다. 따라서, 두 대기 이벤트는 발생 이유도 거의 유사하며, 대기 현상을 해소하는 기법 또한 아래 표와 같이 거의 유사하다.

이벤트 gc cr/current request db file sequential read
발생

이유

- 비효율적인 SQL에 의한 과다한 블록 요청

- 핫 블록

- 느린 인터커넥트

- 비효율적인 네트워크 설정

- 비효율적인 버퍼 캐시

- LMS 프로세스의 부하

- 비효율적인 SQL에 의한 과다한 블록 요청

- 핫 블록(이 경우는 db file sequential read 이벤트보다는 buffer busy waits 이벤트에 가까움)

- 느린 I/O 시스템

- 비효율적인 I/O 설정

- 비효율적인 버퍼 캐시

- I/O 프로세스의 부하(예: 비동기 I/O 프로세스)

해소

방안

- SQL 튜닝을 통해 블록 요청 회수를 줄임

- 핫 블록 해소

- 보다 높은 대역폭(Bandwidth)의 인터커넥트 사용

- 네트워크 설정(UDP 버퍼 크기 등)을 최적화

- 버퍼 캐시의 효율적 사용

- LMS 프로세스의 효율성 증가

- SQL 튜닝을 통해 블록 요청 회수를 줄임

- 핫 블록 해소

- 보다 높은 처리량의 I/O(Disk) 시스템사용

- I/O 설정(디스크간 경합, 스트라이프 크기 등)을 최적화

- 버퍼 캐시의 효율적 사용

- I/O 프로세스의 효율성 증가

[편집] Parameter & Wait Time

[편집] Wait Parameters

gc cr/current request 대기이벤트의 대기 파라미터는 다음과 같다.

  • P1 : File#
  • P2 : Block#
  • P3 : class# (gc cr request인 경우)
  • P3 : id (gc current request인 경우)

P3 = id인 경우에는 하위 2 Byte의 값이 Class#을 나타낸다.

즉, 다음과 같이 gc current request의 Parameter가 주어졌다면

gc current request  file#= 717   block#= 2  id#= 33554445 
gc buffer busy      file#= 1058  block#= 2  id#= 65549    

Hex(33554445) = 200000D이므로 하위 2 Byte인 0D=13이 Class#가 된다. Hex(65549) = 1000D이므로 역시 Class#는 13이다. 13의 Class#의 의미는 V$WAITSTAT 뷰에서 알 수 있다.

UKJA@ukja102> select rownum, class from v$waitstat;

    ROWNUM CLASS
---------- ------------------
         1 data block
         2 sort block
         3 save undo block
         4 segment header
         5 save undo header
         6 free list
         7 extent map
         8 1st level bmb
         9 2nd level bmb
        10 3rd level bmb
        11 bitmap block
        12 bitmap index block
        13 file header block
        14 unused
        15 system undo header
        16 system undo block
        17 undo header
        18 undo block

즉 Class#=13은 File Header(순수 File Header나 LMT에서의 Bitmap Block)을 의미한다.

[편집] Wait Time

1초

[편집] Check Point & Solution

[편집] 비효율적인 SQL문장으로 인한 과다한 블록요청

비효율적인 SQL 문장, 즉 과다한 Logical I/O를 수행하는 SQL 문장이 gc cr/current request 이벤트 대기의 주범이다. 비효율적인 SQL 문장을 튜닝 하지 않은 채로, 다른 튜닝 방법을 적용하려는 시도는 대부분 실패한다는 것은 간과할 수 없는 사실이다. 비효율적인 SQL로 인한 문제는 그 원인이 단순한 만큼, 해결책 또한 단순하다. SQL 문장에 비효율적인 요소는 없는지를 점검하고 최적의 실행 경로를 따를 수 있도록 한다.

RAC에서는 병렬 실행(Parallel Execution)이 여러 노드 간에 분배된다. 대량의 작업을 여러 노드를 거쳐 분산 수행하는 것은 클러스터의 장점을 극대화하는 것으로, RAC의 존재 이유 중 하나라고 할 수 있다. 문제는 개별 병렬 작업의 결과가 인터커넥트를 통해 전송되는 과정에서, 인터커넥트 리소스의 부족을 초래하게 되고, OLTP 성의 일반 쿼리 성능에 영향을 줄 수 있다는 것이다. 하나의 시스템에서 DSS 성의 쿼리와 OLTP 성의 쿼리를 사용하는 것은 여러 가지 성능 문제를 유발할 수 있으며, RAC에서도 예외는 아니다. RAC에서 병렬 작업을 수행하는 경우에는 노드 간에 부하를 어떻게 조절할 지를 미리 고려할 필요가 있다. INSTANCE_GROUPS 파라미터와 PARALLEL_INSTANCE_GROUP 파라미터를 활용하면, 병렬 작업을 노드 간에 지능적으로 분배할 수 있다.

[편집] Hot Block

핫 블록은 여러 프로세스 혹은 여러 인스턴스에 의해 동시 다발적으로 접근 혹은 변경되는 블록을 말한다. 핫 블록은 단일 인스턴스 환경에서는 cache buffers chains 래치 경합이나 버퍼 락 경합을 유발하며, 성능 저하의 주범이 되곤 한다. RAC 환경에서의 핫 블록은 래치나 버퍼 락 경합뿐만 아니라, 과도한 인터커넥트 전송을 유발함으로써 RAC 시스템 전체의 성능을 저하시키는 요인이 된다.

핫 블록에 의한 성능 저하 현상은 핫 블록을 물리적으로 분산시킴으로써 해결 가능하다. 핫 블록을 분산시키는 방법에는 다음과 같은 것들이 있다.

  • 세그먼트 파티셔닝 사용. 해시 파티셔닝이 대표적인 방법
  • 작은 크기의 블록 사용
  • 높은 값의 PCTFREE 속성 사용
  • 세그먼트 데이터 재생성. 삭제 후 재 삽입이나 테이블 재생성 등
  • 핫 블록에 의해 경합이 발생하는 경우에는 Fixed-up 이벤트로 gc cr/current block busy 이벤트가 관찰되는 경우가 많으며, gc buffer busy 이벤트도 많이 관찰된다.

[편집] 동시 다발적인 DML 수행

여러 노드에서 동일 세그먼트에 대해 대량의 DML을 동시 다발적으로 수행하면 광범위한 버퍼 락 경합이 발생하고 이로 인해 gc cr/current request 이벤트의 대기 시간이 증가한다. 대량의 DML 작업은 노드 간에 적절히 작업을 분배하는 것이 좋다. 여러 노드 중 특정 하나의 노드를 배치 작업용 노드, 즉 대량의 DML을 수행하게끔 지정하는 것도 현실적인 방법이다.

[편집] 느린 인터컨넥트

인터커넥트를 통해 주고 받는 데이터의 절대적인 양이 많다면, SQL 튜닝만으로는 문제를 해결할 수 없다. 가령 인터커넥트의 대역폭이 1G Bit(Gigabit Ethernet)라면 이론상 동시에 교환 가능한 데이터의 양은 125M 바이트가 된다. 따라서 125M 바이트 이상의 데이터를 인터커넥트에서 교환하게 되면, 블록 전송 과정에서 응답 시간이 지연되는 현상이 발생하게 된다. 인터커넥트를 통한 데이터 교환이 지나치게 많으면 LMS 프로세스의 부하가 증가하고, 응답 처리 과정에서 지연이 발생하게 된다.

인터커넥트에서의 혼잡에 의한 지연 현상은, 마치 고속도로에서의 차량이 지나치게 몰려 혼잡이 발생하는 것과 동일한 원리에서 발생한다. 도로가 좁아서, 즉 인터커넥트의 대역폭이 낮아서 발생하는 혼잡 현상이라면, 도로를 넓혀서, 즉 인터커넥트의 대역폭을 높이는 방법을 통해 해결 가능하다. 하지만 도로를 넓히는 것이 엄청난 비용을 요구하듯이, 인터커넥트의 대역폭을 높이는 것 또한 추가적인 비용을 요구한다는 사실을 명심해야 한다. 언제나 최선의 선택은 SQL 문을 최적으로 튜닝하고, 불필요한 경합에 의한 블록 전송을 최소화하는 것이 되어야 한다.

현재 인터커넥트로 가장 많이 사용되는 것이 Gigabit Ethernet이며, 최근에 와서는 10 Gigabit Ethernet이 채택되고 있다. 따라서 대역폭을 넓히기 위한 선택의 폭이 넓지 않다는 것 또한 유념할 필요가 있다.

[편집] 비효율적인 네트워크 설정

인터커넥트가 사용하는 네트워크 프로토콜에 따라 적절한 네트워크 설정이 필요하다. Gigabit Ethernet 인터커넥트에서는 UDP(User Datagram Protocol)가 사용된다. oradebug 툴을 사용하면 인터커넥트에서 어떤 프로토콜을 사용하는지 확인할 수 있다.

SQL> oradebug setmypid
SQL> oradebug ipc
SQL> oradebug tracefile_name
/oracle/ORA10g/admin/LAS10/udump/ora102_ora_7040.trc
SSKGXPT 0xc03f844 flags SSKGXPT_READPENDING     info for network 0
socket no 8     IP 192.168.2.202        UDP 41210
        sflags SSKGXPT_UP

네트워크 설정 중 RAC의 인터커넥트 성능과 가장 연관이 깊은 것은 UDP 버퍼의 크기이다. OS에 따라 UDP 버퍼의 크기의 권장치는 조금씩 다를 수 있다. 오라클이 공식적으로 권고하는 UDP 버퍼의 크기는 256K 바이트이다. 대부분의 시스템에서는 256K 바이트의 크기로 적절한 성능을 보장받을 수 있다. 성능 테스트를 위한 BMT 환경과 같은 상황이나, 동시에 인터커넥트를 사용하는 세션수가 대단히 많은 시스템이라면 더 큰 크기의 UDP 버퍼를 사용할 수 있다. 보통 최대 1M ~ 2M 바이트 정도 크기의 UDP 버퍼를 사용하기도 한다. OS 별로 UDP 버퍼 크기를 조회하고 설정하는 방법은 “5.3 네트워크 모니터링”에서 자세하게 논의할 것이다.

UDP 버퍼 크기가 지나치게 작은 경우에는 패킷 유실(Packet Loss) 현상이 빈번하게 발생한다. UDP 패킷 유실이 발생할 경우, 오라클에서는 Fixed-up 이벤트인 gc cr block lost 이벤트나 gc current block lost 이벤트로 관찰된다. 만일 이 이벤트에 대한 대기 현상이 자주 목격된다면, 네트워크 모니터링을 통해 패킷 유실이 지나치게 자주 발생하지 않는지 모니터링 할 필요가 있다. 다양한 툴들을 통해 패킷 유실 현상을 관찰할 수 있는데, 리눅스 환경에서 netstat 툴을 이용하는 예는 다음과 같다.

-- netstat -i 옵션. 네트워크 인터페이스 별로 패킷 유실(RX-ERR)을 관찰할 수 있다.
prompt> netstat -i
Iface	MTU	Met	RX-OK	RX-ERR	RX-DRP	RX-OVR	TX-OK	TX-ERR	TX-DRP	...
eth0	1500	0	8664	0	0	0	7708	0	0	... eth1	1500	0	8772	0	0	199	3541	0	0	...
lo	16436	0	2262	0	0	0	2262	0	0	...

-- nestat -s 옵션. 네트워크 관련 통계치를 통해 패킷 유실(packet receive errors)을 조회할 수 있다.
prompt> netstat -su
Udp:
    495820735 packets received
    50018 packets to unknown port received.
    114 packet receive errors
    584466009 packets sent

gc cr block lost 이벤트나 gc current block lost 이벤트에 대한 대기 현상이 UDP 패킷 유실과 같이 발생하는 경우에는, UDP 버퍼 크기가 지나치게 작게 설정되어 있지 않은지 점검해보아야 한다. UDP 버퍼의 크기를 점진적으로 최대 2M 바이트 정도까지 키우면서 문제가 해결되는지 확인한다. 만일 UDP 버퍼의 크기가 이미 충분히 큰데도 같은 문제가 계속 재현된다면 물리적인 네트워크 설정에 문제가 없는지 확인할 필요가 있다.

인터커넥트의 성능을 향상시키는데 유용한 또 하나의 네트워크 설정은 Jumbo Frame을 사용하는 것이다. Jumbo Frame이란 NIC(Network Interface Card) 설정 시 1500 바이트 크기 이상의 프레임 크기(또는 MTU. Maximum Transmission Unit)를 사용하는 것을 말한다. MTU가 1500 바이트라는 말의 의미는 하드웨어적으로 한번에 네트워크를 통해 전송할 수 있는 패킷의 최대 크기가 1500 바이트라는 의미이다. 일반적인 네트워크 환경에서는 대부분 기본값인 1500 바이트의 MTU로 충분하다. 하지만 인터커넥트와 같이 대량의 데이터가 대단히 빈번하게 교환되는 경우에는 더 큰 크기의 MTU를 사용함으로써 성능을 더욱 향상시킬 수 있다. MTU 크기에 이론적인 제한은 없지만, 일반적으로 9000 바이트 크기의 MTU를 사용한다. 현재 사용 중인 MTU의 크기는 netstat 툴이나 ifconfig 툴을 통해 확인할 수 있다.

Prompt> netstat -i
Kernel Interface table
Iface      MTU Met    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0       1500   0 88277330      0      0      0 79745731      0      0      0 BMRU
eth1       1500   0 1385955750      0      0    199 3879777348      0      0      0 BMRU
lo        16436   0 30702314      0      0      0 30702314      0      0      0 LRU

Prompt> ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 00:11:11:EB:BE:7E
          inet addr:210.122.227.204  Bcast:210.122.227.255  Mask:255.255.255.0
          inet6 addr: fe80::211:11ff:feeb:be7e/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:88277786 errors:0 dropped:0 overruns:0 frame:0
          TX packets:79746070 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:3981177070 (3.7 GiB)  TX bytes:2529189640 (2.3 GiB)

Jumbo Frame을 사용하려면 OS, 네트워크 카드(NIC), 스위치 등 관련된 모든 소프트웨어와 하드웨어가 이를 지원해야 한다.

[편집] 비효율적인 buffer cache 사용

버퍼 캐시를 효율적으로 사용하는 것은 싱글 인스턴스 환경에서뿐만 아니라 RAC 환경에서도 대단히 중요하다. 버퍼 캐시가 비효율적으로 사용되는 경우에는 애써 로컬 캐시로 불러들인 블록이 주기적으로 버퍼 캐시에서 밀려 나게 된다. 이러한 블록을 다시 읽어 들이는 과정에서 다시 글로벌 캐시 동기화가 발생하게 되고, gc cr/current request 이벤트에 대한 대기가 불필요하게 증가한다.

버퍼 캐시를 효율적으로 사용하는 방법은 어떤 원칙이 있다기 보다는 어플리케이션의 데이터 액세스 유형에 따라 적절한 기법을 창조적으로 응용해서 적용한다고 보는 것이 좋을 것이다. 일반적으로 적용 가능한 기법들은 다음과 같다.

  • 다중 버퍼 풀의 사용: 세그먼트의 용도에 따라 Keep 버퍼, Default 버퍼, Recycle 버퍼를 적절히 사용함으로써 버퍼 캐시를 효율적으로 사용할 수 있다.
  • 전역 임시 테이블의 사용: 데이터가 임시로 필요한 경우 가능한 전역 임시 테이블(Global Temporary Table)을 사용한다. 전역 임시 테이블은 세션 레벨에서 데이터를 관리하기 때문에 글로벌 캐시 동기화 작업이 불필요하다.
  • 읽기 전용 테이블스페이스의 사용: 만일 특정 테이블스페이스가 읽기 전용의 성격을 가지고 있다면 테이블스페이스의 속성 자체를 읽기 전용(Read only)으로 변경하는 것이 바람직하다. 오라클은 읽기 전용 테이블스페이스에 속한 데이터 블록은 글로벌 캐시 동기화를 수행하지 않는다.
  • 충분한 크기의 버퍼 캐시 크기: 마지막으로 가능하다면 항상 충분한 크기의 버퍼 캐시를 사용하도록 한다. 버퍼 캐시의 크기가 작아서 버퍼 캐시로부터 자주 쓰이는 데이터 블록이 내려 가는 일이 발생하지 않도록 할 필요가 있다.

[편집] LMS 프로세스의 부하

요청 노드의 블록 전송 요청은 항상 리모트 노드의 LMS 프로세스에 의해 처리된다. 비동기 I/O 프로세스가 디스크 I/O 작업을 처리하는 반면, LMS 프로세스는 인터커넥트 I/O 작업을 처리한다고 볼 수 있다. 비동기 I/O 작업을 위해 비동기 I/O 프로세스를 사용하는 시스템의 경우에는, 비동기 I/O 프로세스가 사용할 수 있는 자원을 최대한 보장해줌으로써 I/O 경합을 어느 정도 개선시킬 수 있다. 마찬가지로 LMS 프로세스가 사용하는 자원을 최대한 보장해줌으로써 인터커넥트에서의 지연(Delay)을 개선시킬 수 있다. LMS 프로세스가 보다 원활하게 작업을 처리하면 그 만큼 gc cr request 이벤트에 대한 대기 현상도 개선된다. LMS 프로세스의 작업은 대부분 적절한 CPU 리소스를 필요로 한다. 따라서 LMS 프로세스가 CPU 자원을 효율적으로 사용할 수 있도록 보장하는 것이 필요하다.

CPU 리소스가 충분한 경우에는, LMS 프로세스의 개수를 증가시킴으로써 개별 LMS 프로세스의 성능을 개선시킬 수 있다. 오라클 10g부터는 GCS_SERVER_PROCESSES 파라미터의 값을 이용해 LMS 프로세스의 개수를 변경한다. 오라클의 권고치는 4개의 CPU 당 하나의 LMS 프로세스를 할당하는 것이며, 최소값은 2개이다. 글로벌 캐시 동기화 작업이 극단적으로 많은 시스템이라면 1~2개의 CPU 당 하나의 LMS 프로세스를 할당하는 것을 고려해 볼만 하다.

LMS 프로세스의 성능을 극대화하기 위해 OS 차원에서 LMS 프로세스가 보다 많은 CPU 자원을 사용할 수 있도록 보장하는 방법을 사용할 수 있다. 가장 쉬운 방법은 renice 명령을 이용해서 LMS 프로세스의 우선순위(Priority)가 높아지도록 조정하는 것이고, 또 하나의 방법은 LMS 프로세스의 우선순위 스케쥴링 방식을 실시간(Real-Time)으로 변경하는 것이다.

이 문제를 이해하려면, OS가 개별 프로세스에게 CPU를 할당하는 방식에 대한 이해가 필요하다. OS는 기본적으로 시분할(Time Sharing) 스케줄 방법을 사용해서 개별 프로세스에게 CPU 자원을 할당한다. 시분할 방법을 사용하는 경우, OS는 CPU를 사용하고자 하는 프로세스의 우선순위를 참조하여 우선순위가 가장 높은(실제로는 Priority 속성값이 낮을수록 우선순위는 높음) 프로세스를 먼저 실행시킨다. 일정 시간 CPU를 사용한 프로세스는 다시 대기 상태로 들어가고, 다음 번 프로세스가 다시 CPU를 할당 받는다. 프로세스의 우선순위는 프로세스의 CPU 사용 정도와 CPU 사용 시간 등을 고려하여 계산된다. 만일 CPU를 사용하는 정도가 비슷하다면, 오랜 시간 동안 CPU를 사용하는 프로세스의 우선순위가 낮아진다. 시분할 방법은 CPU 자원을 수많은 프로세스가 공평하게 나누어 쓴다는 장점이 있지만, 거꾸로 특정 프로세스가 CPU 자원을 최대한 사용하는 것을 방해하는 부작용을 가지고 있다. 인터커넥트를 통한 데이터 교환이 매우 빈번한 상황에서는 전반적인 CPU 사용률이 증가한다. 이때 시분할 기법의 영향으로, 가장 우선적으로 CPU를 할당 받아야 할 LMS 프로세스가 자주 인터럽트를 당하며, 점점 우선순위가 낮아지게 된다. 결과적으로 LMS 프로세스가 필요한 만큼 CPU를 할당 받지 못하는 현상이 발생하게 된다.

이 문제를 해결하는 한 가지 방법은, renice 명령을 이용해 LMS 프로세스의 우선순위가 항상 높은 상태를 유지하도록 하는 것이다. 대부분의 OS에서는 NICE 속성을 이용해 프로세스의 우선순위를 조정할 수 있다. 개별 프로세스는 우선순위를 나타내는 Priority 속성과 함께 NICE 속성을 제공한다. NICE 속성은 말 그대로, 특정 프로세스가 다른 프로세스에게 얼마나 “친절한 프로세스인가”, 즉 CPU를 다른 프로세스에 양보하는 친절한 일을 어느 정도 하는지를 결정한다. NICE 값이 높으면, 즉 더 친절하면 CPU를 다른 프로세스에게 더 많이 양보할 수 있도록 우선순위가 낮아진다. 반대로 NICE 값이 낮으면, 즉 더 불친절하면 CPU를 되도록이면 다른 프로세스에게 양보하지 않는다. NICE 값은 +19(가장 친절) ~ -20 (가장 불친절) 사이의 값을 지니며, 오라클과 같은 일반적인 유저 프로세스는 “0”의 값을 지닌다. 아래와 같이 LMS 프로세스의 우선순위와 NICE 값을 확인할 수 있다(Priority 속성값이 낮을수록 우선순위가 높다는 것을 명심하자).

[root@rac02 ~]# ps -efl | grep lms
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
0 S oracle    7991     1  1  65   0 - 428439 -     Sep11 ?        05:26:48 ora_lms0_ORA102
0 S oracle    7993     1  1  75   0 - 428439 -     Sep11 ?        05:23:51 ora_lms1_ORA102

인터커넥트를 통한 데이터 교환이 매우 빈번하게 발생하고 이로 인해 CPU 경쟁이 심한 시스템이라면 다음과 같이 renice 명령을 이용해 LMS 프로세스의 NICE 값을 최대 -20까지 낮추는 것이 좋다. 아래 명령은 반드시 root 유저로 실행해야 한다.

[root@rac02 ~]# renice -20 -p 7991 7993   # 7991, 7993 프로세스의 NICE 값을 -20으로 변경
7991: old priority 0, new priority -20
7993: old priority 0, new priority -20
[root@rac02 ~]#  ps -efl | grep lms
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
0 S oracle    7991     1  1  60 -20 - 428439 -     Sep11 ?        05:26:51 ora_lms0_ORA102
0 S oracle    7993     1  1  60 -20 - 428439 -     Sep11 ?        05:23:54 ora_lms1_ORA102

시분할 기법에 의한 LMS 성능 저하 현상을 해결하는 또 하나의 방법은 실시간 스케쥴링 기법을 사용하는 것이다. 실시간 스케쥴링 기법의 엄격한 의미는 특정 프로세스가 특정 작업을 수행하는데 항상 “일정한” 응답 시간을 보장받는 것으로, 실시간 OS(Real-Time OS)에서 대단히 중요한 개념이다. 실시간 OS가 아닌 대부분의 OS에서는 완벽하게 일정한 응답 시간을 보장하지 못한다. 하지만 가능한 실시간의 스케쥴링이 가능하도록 추가적인 스케쥴링 기법을 제공하고 있다. 일반적으로 실시간 스케쥴링을 사용하는 프로세스는 고정된 우선순위를 부여 받으며, 시분할 기법에 비해 높은 우선순위를 부여 받는다. 높은 우선순위에 있고, 그 우선순위가 변경되지 않기 때문에 CPU를 최대한 사용할 수 있다. 따라서 LMS 프로세스들이 실시간 기법을 사용하도록 변경하는 것을 검토해 볼 필요가 있다. 오라클 10g R2 이전 버전까지는 OS 별로 고유의 기법을 사용해 LMS 프로세스가 실시간 스케쥴링을 사용하도록 해야 한다. 오라클 10g R2부터는 LMS 프로세스는 기본적으로 실시간 스케쥴링을 사용하도록 변경되었다. 이 메커니즘은 _OS_SCHED_HIGH_PRIORITY 파라미터(기본값은 1)에 의해 제어된다. 이 파라미터의 값이 “0”이면, 이전 버전과 마찬가지로 시분할 스케쥴링을 사용한다. 반면 “1” 이상의 값을 부여하면 실시간 스케쥴링 기법을 사용하며, 높은 값을 부여할수록 더 높은 우선순위를 부여 받는다. 하지만 1 보다 큰 값을 지정할 경우에는 LMS 프로세스가 CPU 자원을 지나치게 많이 차지할 수 있다는 사실에 유념해야 한다.

[편집] LGWR/DBWR 프로세스의 부하

LGWR 프로세스와 DBWR 프로세스는 싱글 인스턴스 환경에서뿐만 아니라, RAC 환경에서도 매우 중요한 일을 담당한다.

LGWR 프로세스는 블록 전송 과정에서 “리두 플러시”를 담당한다. 로컬 캐시의 더티 블록을 리모트 노드로 전송하기 전에 더티 블록에 해당하는 리두 데이터를 리두 로그로 저장해야 한다. 이것을 흔히 “리두 플러시”라고 부른다. 만일 LGWR 프로세스의 작업이 원활하지 못하면 리두 플러시가 지연되고 이로 인해 gc cr/current request 이벤트의 대기 시간이 증가하게 된다.

DBWR 프로세스는 “Fusion Write” 기능을 담당한다. 로컬 캐시의 더티 블록을 디스크에 기록(Checkpoint)하는 작업은 반드시 글로벌하게 이루어진다. 클러스터 내에 같은 블록에 대한 여러 개의 더티 블록이 존재할 수 있기 때문이다. 특정 인스턴스에서 더티 블록에 대한 기록 요청이 발생하면, GRD를 참조하여 블록의 마스터 노드로 요청이 전달되고, 마스터 노드는 블록의 “최신” 버전을 가진 노드에 디스크에 기록 요청을 전달한다. 디스크 기록이 완료되면 다른 인스턴스의 PI(과거 이미지) 블록들은 캐시에서 플러시 된다. 이러한 일련의 메커니즘을 Fusion Write라고 부른다. DBWR 프로세스의 작업이 원활하지 못하면, Fusion Write 작업 시간이 지연된다. DBWR 프로세스에 의해 기록 중인 블록을 전송 받고자 하는 프로세스는 기록 작업이 끝날 때까지 대기해야 하기 때문에 gc cr/current block request 이벤트 대기 시간이 증가하게 된다.

리두 플러시와 Fusion Write의 성능 문제는 V$CURRENT_BLOCK_SERVER 뷰를 통해서도 간접적으로 확인할 수 있다.

만일 리두 플러시와 Fusion Write의 성능 저하로 인해 gc cr/current request 이벤트 대기가 증가한다고 판단되면 LGWR 프로세스와 DBWR 프로세스의 성능을 향상시켜야 한다.


[편집] Event Tip

[편집] PlaceHolder 이벤트와 Fixed-up 이벤트

오라클은 RAC와 관련된 기능을 끊임없이 개선시키고 있다. RAC와 관련된 대기 이벤트의 정의 또한 예외가 아니다. 오라클 9i와 오라클 10g의 클러스터 관련 대기 이벤트들은 이름부터가 새로 정의되었으며, 무엇보다 특정 이벤트들이 서로 간에 종속적인 관계를 지니게 되었다. 즉, 특정 이벤트들은 “Placeholder”(자리점유자)의 역할을 하고, 또 다른 이벤트들은 “Fixed-up”(사후 조정)의 역할을 한다.

오라클 10g의 클러스터 대기 이벤트들은 Placeholder 이벤트와 Fixed-up 이벤트라는 두 개의 카테고리로 분류된다. Placeholder 이벤트란 특정 프로세스가 글로벌 자원, 즉 데이터 블록을 “획득하는” 과정 동안 대기하는 이벤트를 말한다. 반면에 Fixed-up 이벤트란 특정 프로세스가 데이터 블록을 최종 “획득한” 시점에 대기한 것으로 기록되는 이벤트를 말한다. “획득하는”과 “획득한”의 시점 차이가 Placeholder 이벤트와 Fixed-up 이벤트의 역할을 결정하는 것에 주목해야 한다. 가령, 일관된 읽기(CR) 모드로 특정 블록을 리모트 노드에 요청한 프로세스는, 해당 블록을 전송 받을 때까지 gc cr request 이벤트(Placeholder)를 대기한다. 요청의 결과로 실제 블록 이미지를 전송 받았다면, 결과적으로는 gc cr block 2-way 이벤트(Fixed-up)를 대기했던 것으로 최종 보고를 하게 된다. 즉 Placeholder 이벤트는, 프로세스가 어떤 결과를 받았느냐에 따라 특정 Fixed-up 이벤트로 바뀌게 된다.

Placeholder 이벤트와 Fixed-up 이벤트 체계에서 또 하나의 주의할 점은 모니터링 방법이다. Placeholder 이벤트는 “획득하는” 동안 관찰되기 때문에, 현재의 대기 정보를 제공하는 V$SESSION_WAIT 뷰에서 관찰된다. 또한 아직 Fixed-up 이벤트로 변경되지 않았다면 V$SESSION_EVENT 뷰에서도 관찰된다. 반면[Fixed-up 이벤트는 “획득한” 후에 관찰되기 때문에 V$SESSION_WAIT 뷰에서는 관찰되지 않고, 오로지 V$SESSION_EVENT 뷰나 V$SYSTEM_EVENT 뷰에서만 관찰된다. 따라서, 시스템 모니터링을 위해서 V$SYSTEM_EVENT 뷰를 조회하고 있다면, gc cr request 이벤트와 같은 Placeholder 이벤트가 아닌 gc cr block 2-way 이벤트와 같은 Fixed-up 이벤트들만이 관찰된다는 점을 이해해야 한다.

Placeholder/Fixed-up 이벤트 체계는 클러스터 클래스의 대기 이벤트에서만 사용되며, 오라클 10g 이전 버전에서는 전혀 사용되지 않는 개념이다. Placeholder/Fixed-up 이벤트의 체계를 따르지 않은 이벤트들은 기존의 대기 이벤트와 동일한 방식으로 동작한다.

오라클이 제공하는 RAC 관리자 가이드에서는 클러스터 이벤트들을 그 속성에 따라 다음과 같이 분류하고 있다.

위의 이벤트 분류는 이벤트의 발생 이유에 근거를 둔 것으로, 각 이벤트의 의미를 개괄적으로 파악하는 목적으로 사용될 수 있다. Block-oriented 이벤트들은 실제 블록 이미지를 인터커넥트를 통해 교환했음을 의미한다. Message-oriented 이벤트들은 블록 이미지가 아닌, 블록을 읽을 권한만을 부여했음을 의미한다.

Contention-oriented 이벤트들은 블록을 전송 받는 과정에서 경합(Contention)이 발생했음을 의미한다. Load-oriented 이벤트들은 인터커넥트에서 과도한 혼잡(Congestion)이 발생해서 전송 작업이 지연되었음을 의미한다. 하지만, 이 분류법으로는 Placeholder/Fixed-up 이벤트의 정확한 구분을 설명할 수 없다. 가령 위의 이벤트 목록에서 gc buffer busy 이벤트는 Fixed-up 이벤트가 아닌, 일반 독립 이벤트이다. 또한 gc cr request 이벤트나 gc current request 이벤트와 같은 Placeholder 이벤트는 아예 목록에서 빠져 있다. 이 문서에는 위의 분류법과 Placeholder/Fixed-up 이벤트 체계의 특징을 모두 살린 아래와 같은 분류 체계를 사용한다.

오라클 10g RAC에는 크게 2개의 Placeholder 이벤트가 존재한다. gc cr request 이벤트와 gc current request 이벤트가 그것이다. 이 두 개의 대기 이벤트는 각각 “일관된 읽기 모드(이하 CR)의 I/O”와 “현재 모드(이하 Current)의 I/O” 작업에서 발생하는 대기 현상을 나타내는 것으로, RAC에서 가장 보편적으로 발생한다. gc cr request 이벤트와 gc current request 이벤트는 각각 많은 수의 Fixed-up 이벤트를 거느린다. 반면에 gc cr/current multi block request 이벤트와 gc buffer busy 이벤트는 Placeholder/Fixed-up 체계를 사용하지 않는 일반적인 대기 이벤트들이다.

Placeholder/Fixed-up 이벤트 체계에서 한 가지 유의할 점은 Fixed-up 이벤트는 Placeholder 이벤트가 무엇이었느냐에 무관하다는 것이다. Fixed-up 이벤트는 요청의 종류와 무관하게 어떤 “결과”를 받았느냐에 대한 정보만을 제공한다. 따라서 gc cr request 이벤트에 대한 Fixed-up 이벤트로 gc cr block 2-way 이벤트뿐만 아니라, gc current block 2-way 이벤트도 사용되며, Fixed-up 이벤트로 기술된 모든 이벤트가 사용될 수 있다는 사실을 유의해야 한다.

[편집] gc cr/current request의 fixed-up 이벤트

gc cr/current request 이벤트는 Placeholder 이벤트이며, 블록 요청의 결과로 어떤 종류의 응답을 받았느냐에 따라 다음과 같이 여러 종류의 Fixed-up 이벤트로 변경된다.

응답 유형 Fixed-up 이벤트
마스터 노드로부터 블록 이미지를 전송 받은 경우(즉, 마스터 노드가 홀더 노드인 경우) gc cr block 2-way

gc current block 2-way

마스터 노드가 아닌 제 3의 홀더 노드로부터 블록 이미지를 전송 받은 경우 gc cr block 3-way

gc current block 3-way

마스터 노드로부터 블록을 읽을 권한을 부여 받은 경우 gc cr grant 2-way

gc current grant 2-way

블록 이미지를 전송 받는 과정에서 경합이 발생한 경우 gc cr block busy

gc current block busy

블록을 읽을 권한을 부여 받는 과정에서 경합이 발생한 경우 gc cr grant busy

gc current grant busy

블록 이미지를 전송 받는 과정에서 혼잡이 발생한 경우 gc cr block congested

gc current block congested

블록을 읽을 권한을 부여 받는 과정에서 혼잡이 발생한 경우 gc cr grant congested

gc current grant congested

블록 전송 요청에 대한 응답이 유실(Lost)된 경우(10g R2) gc cr block lost

gc current block lost

이들 Fixed-up 이벤트들은 대기 현상의 발생 원인을 좀 더 세밀하게 분석할 수 있도록 도와준다. 같은 gc cr request 이벤트라고 하더라도 Fixed-up 이벤트가 gc cr block 2-way 이벤트인지, gc cr block busy 이벤트인지에 따라, 대기 현상이 발생하는 원인과 해결책이 전혀 다를 수 있기 때문이다.

[편집] 병렬 실행(Parallel Execution, PX)

오라클 10g부터 병렬 실행(Parallel Execution. 이하 PX) 엔진에 대해 중요한 변화들이 시도되었다. 기존의 PX 엔진이 가지고 있던 장점을 보완하고, 그리드 환경에 최적화된 성능을 보장하기 위해서 PSC(Parallel Single Cursor) 모델이라는 이름의 새로운 아키텍처를 도입했다. PSC란 말 그대로 병렬 실행 코디네이터(Parallel Execution Coordinator. 이하 PEC)와 병렬 실행 슬레이브(Parallel Execution Slave. 이하 PES)가 하나의 커서(또는 플랜)을 공유하겠다는 의미이다. 아래 예제는 오라클 9i와 오라클 10g에서 같은 종류의 병렬 작업을 수행한 경우 각 프로세스들이 실행하는 SQL 문장을 캡쳐한 것으로, PSC를 사용함으로써 생긴 변화를 눈으로 확인할 수 있다.

-- 오라클 9i에서는 PEC와 PES가 전혀 다른 커서를 사용하는 것을 확인할 수 있다.
오라클 9i:
PEC: PX 작업 수행
SQL> SELECT /*+ PARALLEL(A 20) */ COUNT( * ) FROM   big_table a;
PES(s): 아래와 같이 변형된 SQL문이 PES에 의해 수행
SQL> SELECT /*+ PIV_SSF */
       sys_op_msr( COUNT( * ) ) 
FROM   (
        SELECT /*+ NO_EXPAND ROWID(A2) */
               0 
        FROM   "MAXGAUGE"."BIG_TABLE" px_granule( 0 , block_range , dynamic ) a2 
       ) a1;

-- 오라클 10g에서는 PEC와 PES가 동일한 커서를 사용하는 것을 확인할 수 있다.
오라클 10g
PEC: PX 작업 수행
SQL> SELECT /*+ PARALLEL(A 20) */ COUNT( * ) FROM big_table a;
PES(s): PEC가 수행한 SQL문과 동일한 형태의 SQL문이 수행
SQL> SELECT /*+ PARALLEL(A 20) */ COUNT( * ) FROM big_table a;

오라클 10g 이전 버전에서는 PEC가 수행한 PX 작업을 PES에게 분배하기 위해, 특별한 형태의 SQL 문장을 PES가 실행하도록 하는 방식을 사용했다. 위의 예제에서 확인할 수 있는 암호문과 같은 SQL 문장이 그것으로, 각 PES들이 이 특별한 형태의 SQL 문장을 통해 각각 영역을 나누어 작업을 수행하게 된다. 이러한 처리 방법은 병렬 작업을 SQL 문장으로 변환하는 복잡한 과정을 거치게 되는 단점을 내포하고 있다.

이러한 문제점을 극복하기 위해 오라클 10g에서는 PEC가 생성한 커서(또는 플랜)를 모든 PES들이 공유하는 모델을 사용한다. 이로 인해 복잡한 변환 과정이 불필요해졌으며, PX와 관련된 모든 프로세스들이 동일한 SQL 문을 사용하기 때문에 모니터링 및 관리가 용이해졌다. PSC 모델이 채택한 방법의 핵심은 실행 계획은 공유하되, 각 PES들이 실행 계획의 특정 부분만을 실행하도록 PEC가 추가적인 메시지를 통해 제어하는 것이다. 아래 예제에 이러한 개념이 간략하게 표현되어 있다.

-- 두 개의 테이블을 HASH JOIN으로 PX로 수행한다.
SQL> SELECT /*+  USE_HASH(A B) PARALLEL(A)  PARALLEL(B)*/ COUNT(*) 
FROM BIG_OBJECT A, BIG_OBJECT B;
-- 실행 계획은 아래와 같다.
SELECT STATEMENT ALL_ROWS-Cost : 40564
  SORT AGGREGATE 
   PX COORDINATOR  
    PX SEND QC (RANDOM) 
     SORT AGGREGATE  
      HASH JOIN   
       PX RECEIVE   
        PX SEND HASH 
         PX BLOCK ITERATOR  
          TABLE ACCESS FULL BIG_OBJECT(1) 
       PX RECEIVE   
        PX SEND HASH 
         PX BLOCK ITERATOR  
          TABLE ACCESS FULL BIG_OBJECT(2)

-- 위의 암호와도 같은 실행 계획을 그림으로 표현하면 아래 그림과 같다. PEC와 PES는 같은 실행 계획을 공유하며, PEC가 각 PES들에게 실행 계획 중 어떤 부분을 담당할 것인지 제어한다.

그림:실행계획.jpg

싱글 인스턴스 환경에서는 PEC가 생성한 커서를 PES가 자연스럽게 공유할 수 있지만, RAC와 같은 멀티 인스턴스 환경에서는 PEC는 다른 인스턴스의 특정 PES에게 SQL 텍스트를 전송하고, 이 PES가 생성한 커서를 다른 PES들이 공유하는 방식으로 PSC 모델을 구현한다.

RAC와 같은 그리드 환경은 병렬 작업에 있어서 또 다른 장점을 제공한다. 높은 CPU 파워를 가진 싱글 인스턴스 환경에서도 병렬 작업의 장점을 극대화할 수 있지만, RAC 시스템에서는 여러 인스턴스간에 병렬 작업을 분할할 수 있으므로 진정한 의미에서의 병렬 수행이 가능해진다. 하지만 인스턴스간에 병렬 작업을 분할해서 사용하는 것이 항상 유리한 것은 아니라는 점이 중요하다. 같은 인스턴스내에서 병렬 프로세스간의 통신은 공유 메모리(Shared Memory)를 통해서 이루어진다. 이에 반해 인스턴스간의 병렬 프로세스간의 통신은 인터커넥트(Interconnect)를 통한 IPC를 통해서 이루어진다. 비록 1Gbps나 10Gbps 급의 고속 인터커넥트가 보편화되고 있지만, 같은 머신 내의 공유 메모리를 통한 데이터 교환에 비해서는 역시 비효율적이다. 따라서 과도한 노드 간의 병렬화는 인터커넥트의 부하를 유발해서 오히려 성능을 저하시킬 수 있다.

같은 인스턴스내에서 이루어지는 병렬 작업을 인스턴스내 병렬화(Intra-Instance Parallelism)라고 부르며, 여러 인스턴스 사이에서 이루어지는 병렬 작업을 인스턴스간 병렬화(Inter-Instance Parallelism)라고 부른다. 이 각각의 방법은 서로 장단점을 가지고 있기 때문에 각각의 특징을 고려하여 사용해야 한다. 오라클의 기본적인 정책은 인스턴스내 병렬화를 먼저 시도하고, 하나의 인스턴스에서 처리하기 힘든 작업의 경우에는 인스턴스간 병렬화를 시도하는 것이다. 즉, 사용자가 요청한 병렬 작업이 하나의 인스턴스가 보유한 리소스 파워로 처리 가능한 경우에는 오라클은 가능한 인스턴스내 병렬 작업을 시도한다. 하지만 여러 인스턴스가 일을 나누어 수행하는 것이 유리하다고 판단되는 경우에는 클러스터내의 다른 인스턴스들에게도 작업을 분배한다. 오라클이 기본적으로 합리적인 판단을 하기 때문에 대부분의 경우 이것을 그대로 따르는 것이 좋다.

하지만, 어떤 상황에서는 특정 인스턴스나 인스턴스 그룹에 특정 병렬 작업을 수행하게끔 할당해야 할 경우가 있을 것이다. 가령 다섯 개의 노드로 이루어진 RAC 시스템에서 2개의 노드는 배치 작업만을 위해 사용하고, 나머지 세 개의 노드는 온라인 작업 만을 위해 사용한다고 가정해 보자. 대용량 데이터 처리를 위한 병렬 작업은 두 개의 노드에서만 실행하게끔 제어할 수 있는 방법이 없다면 클러스터를 관리하는 것은 대단히 힘든 일이 된다. 다행히 오라클은 사용자가 직접 인스턴스내 병렬화와 인스턴스간 병렬화를 제어할 수 있는 지능적인 방법을 제공한다. INSTANCE_GROUPSPARALLEL_INSTANCCE_GROUP 두 개의 파라미터를 이용하면 이러한 목적을 달성할 수 있다. INSTANCE_GROUPS 파라미터는 특정 인스턴스를 특정 그룹에 지정하는 역할을 한다. PARALLEL_INSTANCE_GROUPS 파라미터는 병렬 작업 수행 시 어떤 인스턴스 그룹에 PES를 배정할 지를 결정하는 역할을 한다. 이 두 파라미터가 가지는 값은 모두 논리적인 값이라는 사실에 주의해야 한다. 파라미터 이름으로 인해 이 파라미터의 값들이 실제 인스턴스 명을 사용해야 하는 것으로 착각하는 사례가 많다. 아래에 사용 예제가 있다.

-- 인스턴스 1번. 인스턴스 1번은 서울, 부산 작업에 모두 사용한다.
SQL> ALTER SYSTEM SET INSTANCE_GROUPS = SEOUL, BUSAN SCOPE=SPFILE;

-- 인스턴스 2번. 인스턴스 2번은 부산 작업에만 사용한다.
SQL> ALTER SYSTEM SET INSTANCE_GROUPS = BUSAN SCOPE=SPFILE;

-- 두 인스턴스를 모두 재 기동한다.

-- 서울과 관련된 작업을 실행하고자 할 때는 “SEOUL” 그룹을 지정한다. 아래와 같이 작업을 수행하면 어떤 인스턴스에 작업을 수행하든, PES들은 항상 인스턴스 1번에서만 수행된다.
SQL> ALTER SESSION SET PARALLEL_INSTANCE_GROUP = SEOUL;
SQL> SELECT /*+ PARALLEL(A) PARALLEL(B) */
COUNT(*) 
FROM BIG_SEOUL1 A, BIG_SEOUL2 B
WHERE A.ID = B.ID;

-- 부산과 관련된 작업 실행하고자 할 경우에는 “BUSAN” 그룹을 지정한다. 아래와 같이 작업을 수행하면 어떤 인스턴스에서 작업을 수행하든, PES들은 인스턴스 1번과 인스턴스 2번을 모두 사용한다.
SQL> ALTER SESSION SET PARALLEL_INSTANCE_GROUP = BUSAN;
SQL> SELECT /*+ PARALLEL(A) PARALLEL(B) */
COUNT(*) 
FROM BIG_BUSAN1 A, BIG_BUSAN2 B
WHERE A.ID = B.ID;

위의 예제를 통해 알 수 있듯이 병렬 작업 그룹을 잘 활용하면 병렬 작업들을 효과적으로 인스턴스에 분배할 수 있다. 업무 설계와 잘 연동해서 사용하면 불필요한 인터커넥트 낭비를 사전에 차단할 수 있으므로 성능 면에서도 큰 장점을 제공한다.

오라클은 병렬 작업을 최적화하기 위해 파티션을 적극적으로 활용한다. 가령 파티션으로 분할되어 있는 테이블에 대한 DML 작업은 병렬 DML(Parallel DML. PDML)로 전환할 수 있다. 병렬로 수행되는 DML 작업은 각 파티션 별로 독립적인 DML이 수행되므로 대량 DML의 성능을 극대화할 수 있다.

병렬 작업과 파티션의 관계에서 반드시 언급해야 할 기능이 파티션 지향 조인(Partition-wise Join)이다. 파티션 지향 조인이란 파티션 별로 병렬로 조인 작업을 수행함으로써 대량 데이터에 대한 조인 성능을 최적화하는 기능을 말한다. 파티션 지향 조인은 조인 대상이 되는 테이블의 파티션 구성이 동일한 지 여부에 따라 전체 파티션 지향 조인(Full Partition-wise Join)과 부분 파티션 지향 조인(Partial Partition-wise Join)으로 구분된다.

  • 전체 파티션 지향 조인: 조인 대상이 되는 두 개의 테이블이 동일한 파티션(Equi-partitioned)으로 구성되어 있는 경우. 동일한 파티션이란 동일한 파티션 방식(해시/리스트/범위)과 동일한 파티션 수, 동일한 파티션 키를 가진다는 것을 의미한다. 이 경우에는 각 테이블의 파티션들이 1:1로 매칭되어, 각 파티션의 짝들이 개별적으로 조인이 수행된다. 따라서 병렬 조인에서 최적의 성능을 낼 수 있다. 한가지 단점은 DOP(Degree Of Parallelism)가 파티션 수에 의해 제한된다는 것이다. 가령 파티션 수가 4개인 테이블들에 대해 전체 파티션 지향 조인이 발생한다면 최대 DOP는 4가 된다.
  • 부분 파티션 지향 조인: 조인 대상이 되는 테이블의 파티션 구성이 서로 다른 경우. 이 경우 오라클은 조인의 선두가 되는 테이블을 기준으로, 나머지 테이블을 가상의 파티션을 나눈 후, 각 파티션의 짝들에 대해 개별적으로 조인을 수행한다. 풀 파티션 조인보다는 성능 면에서 불리하지만, 병렬 작업의 장점을 극대화할 수 있는 방법 중 하나이다. 부분 파티션 지향 조인에서도 전체 파티션 지향 조인과 동일하게 최적의 성능을 위해 DOP를 선두 테이블의 파티션 수로 제한한다.

아래 예제는 조인 대상이 되는 테이블의 파티션 구성에 따라 전체/부분 파티션 지향 조인이 어떻게 수행되며, 그 실행 계획은 어떻게 나타나는지를 테스트한 결과이다.

-- PX_TEST 테이블: 파티션수가 4인 해시 파티션
SQL> CREATE TABLE PX_TEST(ID NUMBER, NAME VARCHAR2(100))
	PARTITION BY HASH(ID) PARTITIONS 4;
-- PX_TEST2 테이블: 파티션수가 4인 해시 파티션
SQL> CREATE TABLE PX_TEST2(ID NUMBER, NAME VARCHAR2(100))
	PARTITION BY HASH(ID) PARTITIONS 4;
-- PX_TEST3 테이블: 파티션 없는 테이블
SQL> CREATE TABLE PX_TEST3(ID NUMBER, NAME VARCHAR2(100));

---------------------------------------------------------------
-- Case1: 전체 파티션 지향 조인. PX_TEST 테이블과 PX_TEST2 테이블
SELECT /*+ PARALLEL(A) PARALLEL(B) */ COUNT(*)
FROM PX_TEST A, PX_TEST2 B
WHERE A.ID = B.ID

--->  두 테이블은 파티션 구성이 동일하므로 전체 파티션 지향 조인으로 풀린다.
 SELECT STATEMENT ALL_ROWS-Cost : 33882
  SORT AGGREGATE 
   PX COORDINATOR  
    PX SEND QC (RANDOM) 
     SORT AGGREGATE  
      PX PARTITION HASH ALL  <---  전체 파티션 지향 조인을 의미
       HASH JOIN   
        TABLE ACCESS FULL OWI.PX_TEST2(2) 
        TABLE ACCESS FULL OWI.PX_TEST(1) 

---------------------------------------------------------------
-- Case 2: 부분 파티션 지향 조인. PX_TEST 테이블과 PX_TEST3 테이블
SELECT /*+ PARALLEL(A) PARALLEL(B) */ COUNT(*)
FROM PX_TEST A, PX_TEST3 B
WHERE A.ID = B.ID

--->  두 테이블은 파티션 구성이 다르므로 부분 파티션 지향 조인으로 풀린다.
 SELECT STATEMENT ALL_ROWS-Cost : 41720
  SORT AGGREGATE 
   PX COORDINATOR  
    PX SEND QC (RANDOM) 
     SORT AGGREGATE  
      HASH JOIN   
       PX RECEIVE   
        PX SEND PARTITION (KEY)  <---  PX_TEST3는 파티션이 없으므로 논리적인 파티션 생성
         PX BLOCK ITERATOR  
          TABLE ACCESS FULL OWI.PX_TEST3(2) 
       PX PARTITION HASH ALL  <---  PX_TEST는 파티션이 이루어져 있으므로 파티션 스캔
        TABLE ACCESS FULL OWI.PX_TEST(1) 

---------------------------------------------------------------
-- Case 3: 일반 조인. PX_TEST3 테이블과 PX_TEST3 테이블(셀프 조인)
SELECT /*+ PARALLEL(A) PARALLEL(B) */ COUNT(*)
FROM PX_TEST3 A, PX_TEST3 B
WHERE A.ID = B.ID

---> 두 테이블은 파티션이 없으므로 일반 조인으로 풀린다.
 SELECT STATEMENT ALL_ROWS-Cost : 40564
  SORT AGGREGATE 
   PX COORDINATOR  
    PX SEND QC (RANDOM) 
     SORT AGGREGATE  
      HASH JOIN   
       PX RECEIVE   
        PX SEND  <---  PX_TEST3는 파티션이 이루어져 있지 않으므로 단순 병렬 스캔
         PX BLOCK ITERATOR  
          TABLE ACCESS FULL OWI.PX_TEST3(1) 
       PX RECEIVE   
        PX SEND  <---  PX_TEST3는 파티션이 이루어져 있지 않으므로 단순 병렬 스캔
         PX BLOCK ITERATOR  
          TABLE ACCESS FULL OWI.PX_TEST3(2)

파티션 지향 조인에서는 조인 성능의 최적화를 위해 DOP를 파티션 수로 제한한다. 강제로 DOP를 지정하지 않는다면 오라클은 파티션 지향 조인을 수행하는 실행 계획을 생성하며 DOP도 자연스럽게 파티션 수에 의해 결정된다. 만일 강제로 DOP를 지정하면 오라클은 파티션 지향 조인을 하지 않고 일반적인 병렬 조인을 수행한다. 어떤 것이 성능에 더 유리한지는 상황에 달려 있다. 만일 파티션 수와 CPU 수가 큰 차이가 없다면 파티션 지향 조인이 단연 유리할 것이다. 반면 CPU 수가 파티션 수에 비해 충분히 크다면 모든 CPU 자원을 활용할 수 있는 일반적인 병렬 조인이 더 유리할 것이다.

병렬 작업 수행에 있어서 파티션만큼 중요한 요소가 클러스터이다. 파티션이 세그먼트의 분할을 담당한다고 하면 클러스터는 리소스(CPU, 메모리)의 분할을 담당한다고 할 수 있다. 자연스럽게 오라클은 병렬 작업 수행 시 클러스터에 대한 정보를 참조해서 최적의 실행 계획을 마련한다.

2 개의 노드로 이루어진 RAC 환경에서의 네 개의 파티션으로 이루어진 테이블들에 대해 전체 파티션 지향 조인을 수행하는 경우를 가정해 보자. 이상적인 경우 오라클은 네 개의 PES를 사용하며, 아래와 같은 형태로 PES들을 분배한다.

그림:노드간분배방식.jpg

위의 같은 분배 방식은 노드간의 인터커넥트 통신을 최소화함으로써 파티션 지향 조인의 장점을 극대화한다. 문제는 사용자가 DOP를 지정할 경우에 발생한다. 가령 위의 예에서 DOP를 파티션 수인 4와 무관하게 강제로 8로 지정했다고 하자. 이 경우 파티션 지향 조인을 적용할 수 없기 때문에 일반적인 병렬 조인으로 수행된다. 싱글 인스턴스 환경에서는 이러한 현상이 전혀 문제가 되지 않지만, RAC와 같은 멀티 인스턴스 환경에서는 자칫 과도한 인터커넥트 통신을 유발할 수 있다. 서로 다른 노드의 PES 간에 조인이 발생하면 인터커넥트를 통해 데이터를 교환해야 하기 때문이다. 이런 부작용을 피하기 위해 오라클은 최대한 노드 간에 통신이 발생하는 않는 구조로 PES 들을 분배한다. 즉, 다음과 같은 형태의 병렬 조인이 수행된다.

그림:병렬조인.jpg

이와 같은 형태의 병렬 조인은 높은 DOP의 장점을 그대로 누리면서도 적절한 PES 분배를 통해 불필요한 인터커넥트 통신을 최소화한다.

병렬 실행에 관여하는 PEC와 PES들은 작업 수행 도중 메시지나 데이터를 전송 받기 위해 대기하는 경우가 많으며, 이 경우 PX: XXX 형태의 이벤트를 대기하는 것으로 관찰된다. 병렬 실행과 관련된 대기이벤트들에 대한 정확한 정의와 설명은 메타링크 문서번호 191103.1을 참조하면 얻을 수 있다. 여기서는 모든 대기이벤트들을 일일이 열거하는 대신 간단한 예제를 통해 각 대기이벤트들의 의미를 간략하게 살펴볼 것이다. 다음과 같이 두 개의 테이블에 대해 병렬 해시 조인을 수행하는 작업을 가정해보자. 이 작업의 SQL 문장과 실행 계획은 다음과 같다.

-- 두 개의 테이블을 HASH JOIN으로 PX로 수행한다.
SQL> SELECT /*+  USE_HASH(A B) PARALLEL(A)  PARALLEL(B)*/ 
		COUNT(*) 
FROM BIG_OBJECT A, BIG_OBJECT B;
-- 실행 계획은 아래와 같다.
SELECT STATEMENT ALL_ROWS-Cost : 40564
  SORT AGGREGATE 
   PX COORDINATOR  
    PX SEND QC (RANDOM) 
     SORT AGGREGATE  
      HASH JOIN   
       PX RECEIVE   
        PX SEND HASH 
         PX BLOCK ITERATOR  
          TABLE ACCESS FULL BIG_OBJECT(1) 
       PX RECEIVE   
        PX SEND HASH 
         PX BLOCK ITERATOR  
          TABLE ACCESS FULL BIG_OBJECT(2)

위의 실행 계획과 각 단계에서 대기하게 되는 대기이벤트들을 그림과 목록으로 정리하면 다음과 같다.

그림:대기이벤트.jpg

  • PX Deq: Parse Reply: PEC가 PES에게 파싱 요청을 한 후 응답이 올 때까지 대기하는 이벤트이다. 오라클 10g이전까지는 PES가 사용할 별도의 SQL 문들이 생성되기 때문에 각 PES들은 자신에게 할당된 SQL 문을 파싱하고, 변수를 바인딩 하는 일련의 작업을 거쳐야 한다. 오라클 10g에서 도입된 PSC(Parallel Single Cursor) 모델에서는 PEC가 생성한 커서를 공유하기 때문에 이러한 과정이 생략된다. 단, RAC에서는 여전히 PEC와 다른 노드에 존재하는 PES는 PEC가 생성한 SQL 문을 파싱하는 역할을 수행한다. 이 이벤트에 대한 대기시간이 크게 나타나는 경우는 매우 드물다. 만일 라이브러리 캐시 경합으로 인해 PES들이 SQL 문을 파싱 하는데 불필요하게 많은 시간이 걸린다면 이벤트 대기 시간이 증가할 수 있다.
  • PX Deq: Execute Reply: PX Deq: Parse Reply 이벤트가 PES에 의한 파싱 과정이 끝나기를 기다리는 대기이벤트라면 PX Deq: Execute Reply는 PES의 실제 작업이 끝나기를 기다리는 대기이벤트이다. 즉 PEC가 PES가 작업을 끝낸 후 데이터를 보내주기를 기다리는 동안 이 이벤트를 대기한다. PEC가 가장 보편적으로 대기하는 이벤트라고 할 수 있다. PEC가 이 이벤트를 대기하는 것은 매우 자연스러운 현상이다. 하지만, 긴 대기시간은 PES들의 수행 속도가 느려 데이터를 빨리 보내주지 못하는 것을 의미할 수도 있다. 물론 작업의 속성에 따라 긴 대기시간이 매우 자연스러운 결과일 수도 있다. 만일 불필요하게 긴 대기 시간이 의심된다면 개별 PES들이 불필요하게 많은 일을 수행하지 않는지, 또는 성능에 문제가 없는지 확인해보아야 한다.
  • PX Deq Credit: need buffer: PEC/PES 간, PES/PES 간의 통신은 테이블 큐(Table Q)를 통해 이루어진다. 가령 PES가 테이블 큐에 데이터를 집어 넣으면, PEC가 테이블 큐에서 그 데이터를 빼가는 형식이다. 테이블 큐는 각 프로세스간에 존재한다. 오라클은 두 프로세스 중 한 순간에 오직 하나의 프로세스만이 테이블 큐에 데이터를 집어 넣을 수 있도록 보장한다. 테이블 큐에 데이터를 집어 넣을 수 있는 자격을 확보할 때까지 기다리는 이벤트가 PX Deq Credit: need buffer 이벤트이다.
  • PX Deq: Execution Msg: PES가 어떤 작업을 수행하기 위한 메시지를 기다리는 이벤트이다. 병렬 실행에 관계하는 각 PES들은 특정 작업이 자신에게 할당될 때까지 기다려야 하며, 그 동안 PX Deq: Execution Msg 이벤트를 대기한다. PX Deq: Execute Reply 이벤트가 PEC가 가장 보편적으로 대기하는 이벤트라면 PX Deq: Execution Msg 이벤트는 PES가 가장 보편적으로 대기하는 이벤트이다.
  • PX Deq: Table Q Normal: PES가 테이블 큐에 데이터가 들어오기를 기다리는 이벤트이다. PES가 다른 PES로부터 데이터를 받아서 작업을 수행해야 하는 경우에 보편적으로 발생하는 이벤트이다. 오라클은 병렬 실행에서 필요한 경우에는 생산자/소비자(Producer/Consumer) 모델을 사용한다. 즉, 생산자 PES가 데이터를 생산하면 소비자 PES가 이를 소비하는 패턴을 사용한다. 가령 SELECT /*+ PARALLEL … */ FROM TABLEA ORDER BY NAME 과 같은 형태의 병렬 작업을 수행하면 테이블로부터 데이터를 페치하는 생산자 PES와 페치된 데이터를 받아서 소비(ORDER BY)하는 소비자 PES가 협력하는 방식으로 작동한다.
  • direct path read: 이 대기이벤트는 병렬 작업과는 무관하지만 병렬 실행에서 매우 보편적으로 등장하기 때문에 언급하고자 한다. direct path read 이벤트는 버퍼 캐시(Buffer Cache)를 경유하지 않고 데이터 파일로부터 직접 데이터를 읽는 과정에서 발생하는 대기이벤트이다. 이러한 방식의 I/O를 Direct Path I/O라고 부른다. PES가 테이블로부터 데이터를 페치하는 작업은 대부분 데이터 파일에서 직접 데이터를 읽는 방식을 사용한다. 따라서 이 이벤트는 PES에서 가장 보편적으로 목격된다. 어플리케이션이나 SQL 튜닝을 통해 direct path read 이벤트에 대한 대기시간을 줄이는 방법은 없으며, Direct Path I/O의 성능을 개선시키려면 I/O 작업 자체의 성능을 개선시키는 것이 유일한 방법이다. I/O 작업의 성능을 개선시키는 방법에 대해서는 부록 B를 참조하기 바란다.

병렬 실행 과정에서 보편적으로 목격되는 또 하나의 대기이벤트는 enq: TC – contention 이벤트이다. PES가 Direct Path I/O를 수행하려면, 해당 테이블에 대한 체크포인트(Checkpoint) 작업이 선행되어야 한다. 버퍼 캐시의 더티 버퍼가 모두 데이터 파일에 기록되어야 버퍼 캐시를 경유하지 않고 데이터 파일에서 직접 데이터를 읽을 수 있기 때문이다. PEC는 PES에게 작업을 지시하기 전에 체크포인트 요청을 하고 작업이 끝날 때까지 기다려야 하며 그 동안 enq: TC – contention 이벤트를 대기하는 것으로 관찰된다.

[편집] 세그먼트 파티셔닝

Segment Partitioning을 참조하세요

[편집] 다중 버퍼풀과 LRU

오라클은 버퍼 캐시를 효율적으로 사용하기 위해 두 종류의 LRU(Least Recently Used) 리스트를 사용한다. LRU 리스트는 가장 최근에 사용되거나 미사용된 버퍼들의 리스트로 프리(Free. 미사용) 버퍼, 사용중이거나 사용된 버퍼, 아직 LRUW 리스트(Dirty List)로 옮겨지지 않은 더티(Dirty. 변경된) 버퍼등을 포함한다. 일부 문서에서는 LRU 리스트를 대체 리스트(Replacement List)라고 부른다. LRUW 리스트는 아직 디스크에 기록되지 않은 변경된(Dirty한) 버퍼들의 리스트를 관리한다. 버퍼 캐시의 모든 버퍼들은 반드시 LRU 리스트 또는 LRUW 리스트 둘 중의 하나에 속한다. LRUW 리스트는 더티 리스트(Dirty List), 또는 기록 리스트(Write List)라고도 부른다.

오라클은 리스트 스캔의 효율성을 위해 LRU 리스트나 LRUW 리스트를 다시 메인 리스트(Main List)와 보조 리스트(Auxiliary List)로 나누어 관리한다. 이를 정리하면 다음과 같다.

LRU 리스트(대체 리스트) - 메인 리스트 : 사용된 버퍼들의 리스트. 핫 영역과 콜드 영역으로 구분 관리된다. - 보조 리스트 : 프리 버퍼들의 리스트. 더 정확하게 표현하면, 미사용된 버퍼들이나, DBWR에 의해 기록된 버퍼들의 리스트

LRUW 리스트(기록 리스트) - 메인 리스트 : 변경된 버퍼들의 리스트 - 보조 리스트 : 현재 DBWR에 의해 기록중인 버퍼들의 리스트

오라클은 프리 버퍼 탐색시, 우선 LRU 리스트의 보조 리스트에서 프리 버퍼를 찾는다. 보조 리스트의 버퍼가 모두 사용된 경우에는, 메인 리스트의 콜드 영역에서 프리 버퍼를 찾는다. 인스턴스가 최초로 구동된 때는 모든 버퍼들은 보조 리스트에서 관리된다. 또한 변경된 버퍼들이 DBWR에 의해 기록된 후에는 다시 프리 버퍼로 바뀌며, LRU 리스트의 보조 리스트에 추가된다.

LRU 리스트 와 LRUW 리스트는 항상 짝(Pair)으로 존재하며, 이 짝을 Working Set이라고 부른다(즉 Working Set = LRU + LRUW). 오라클은 복수 개의 Working Set을 사용한다. 하나의 Working Set을 하나의 cache buffers lru chain 래치가 관리한다. LRU 리스트나 LRUW 리스트를 탐색하고자 하는 프로세스는 반드시 cache buffers lru chain 래치를 획득해야 한다. 따라서 동시에 많은 프로세스가 LRU 리스트나 LRUW 리스트를 탐색하고자 할 경우에 cache buffers lru chain 래치를 획득하기 위해 경쟁하게 되며 이 과정에서 latch: cache buffers lru chain 이벤트를 대기한다.

_DB_BLOCK_LRU_LATCHES 히든 파라미터의 값을 조회하거나, 다음 명령문을 이용해 cache buffers lru chain 래치의 최대 개수를 구할 수 있다.

SQL> select count(*) from v$latch_children where name = 'cache buffers lru chain'; 
  COUNT(*)
----------
        16

하지만 위의 래치를 다 사용하는 것은 아니다. 오라클에는 다양한 종류의 버퍼 풀이 존재하며 각 버퍼 풀들이 이들 래치를 골고루 사용한다.

다중 버퍼풀에 대한 개략적이 정보는 다음과 같다.

첫째, Default 버퍼는 자주 사용되는 객체들을 위해 사용한다.

둘째, Keep 버퍼는 “비교적” 자주 사용되는 객체들을 위해 사용한다. 매우 자주 사용되는 객체들은 Default 버퍼의 핫(Hot) 영역에 상주할 가능성이 크기 때문에 굳이 Keep 버퍼에 상주시킬 필요가 없다. 하지만 사용빈도가 비교적 낮은 객체들의 경우에는 Default 버퍼의 콜드(Cold) 영역에 있다가 버퍼 캐시에서 밀려날 확률이 높기 때문에 Keep 버퍼에 상주시키는 것이 바람직하다.

셋째, Recycle버퍼는 사용 빈도가 낮은 객체들에 대해 사용한다. Keep 버퍼와 Recycle 버퍼는 기본적으로 핫 영역(사용빈도가 높은 블록들이 머무르는 영역)과 콜드 영역(사용빈도가 낮은 블록들이 머무르는 영역)의 구분이 없다. 정확하게 말하면 핫 영역을 가지지 않는다. 이 점을 제외하면 Recycle 버퍼의 작동 방식은 Default 버퍼와 완전히 동일하다. 하지만 Keep 버퍼의 경우에는 약간의 차이가 있다. FTS 방식으로 읽히는 블록들은 기본적으로 LRU 리스트의 끝에 위치시키게 된다. 하지만 Keep 버퍼의 경우에는 FTS 방식으로 읽히는 것을 전제로 하기 때문에 FTS 방식으로 읽힌 블록이라고 하더라도 LRU의 제일 앞에 위치한다.

오라클 10g 이전 버전에서는 FTS 방식으로 읽힌 블록에 대해서는 Touch Count를 읽는 즉시 증가시키지 않았다. 이로 인해 FTS 방식으로 매우 자주 읽히는 테이블들이 버퍼 캐시에서 밀려나가는 현상이 생길 수 있다. 따라서 이런 형태의 테이블에 대해서는 가급적이면 Keep 버퍼를 사용하는 것이 바람직하다.

Keep 버퍼의 사용에 있어서 또 한가지 주의할 점은 Keep 버퍼의 크기에 관한 것이다. 특정 객체를 Keep 버퍼에 완전히 상주시키기 위해서는 CR 블록이 차지하는 크기까지 고려해야 한다. 따라서 Keep 버퍼의 크기는 Keep 버퍼에 담을 객체들의 전체 크기보다 크게 잡는 것이 바람직하다.

버퍼와 래치와의 관계는 다음과 같이 정리가 가능하다.

첫째, 버퍼는 크게 Default 버퍼 풀, Keep 버퍼 풀, Recycle 버퍼 풀로 나누어 진다. 둘째, Default 버퍼 풀은 다시 블록 크기 별로 표준블록사이즈, 2K, 4K, 8K, 16K, 32K 버퍼 풀로 나누어 진다. 개개의 버퍼 풀은 각각 독립적인 cache buffers lru chain 래치를 사용한다. 따라서 래치의 최소 개수는 8개가 된다. 다음 명령문을 사용하면 어떤 래치가 어떤 종류의 버퍼에 대해 사용 중인지를 확인할 수 있다.

SQL> 
-- x$kcbwds=Working Set, x$kcbwbpd=Buffer pool, v$latch_children=Latch
select d.blk_size, c.child#, p.bp_name, c.gets, c.sleeps
from x$kcbwds d, v$latch_children c, x$kcbwbpd p
where
 d.set_latch = c.addr
 and d.set_id between p.bp_lo_sid and p.bp_hi_sid
order by c.child#
;

  BLK_SIZE     CHILD# BP_NAME                    GETS     SLEEPS
---------- ---------- -------------------- ---------- ----------
      8192          1 KEEP                             42          0
      8192          2 KEEP                             42          0
      8192          3 RECYCLE                       42          0
      8192          4 RECYCLE                       42          0
      8192             5 DEFAULT                          2337             0     <--- 실제 사용중
        8192             6 DEFAULT                          2322             0     <--- 실제 사용중
       2048          7 DEFAULT                         33          0
      2048          8 DEFAULT                        33          0
      4096          9 DEFAULT                        32          0
      4096         10 DEFAULT                       32          0
      8192         11 DEFAULT                       32          0
      8192         12 DEFAULT                       32          0
     16384         13 DEFAULT                      32          0
     16384         14 DEFAULT                      32          0
     32768         15 DEFAULT                      32          0
     32768         16 DEFAULT                      32          0

위의 결과를 해석하면 Keep 버퍼 풀에 대해 2개, Recycle 버퍼 풀에 대해 2개, 그리고 Default 버퍼 풀에 대해 블록 크기 별로 각각 2개씩 래치를 사용하는 것을 알 수 있다. 만일 Default 버퍼 풀에 8K 표준 크기의 버퍼 풀만 사용한다면 2개의 lru 래치 만을 사용하게 될 것이다. 최대 16개의 래치 개수는 CPU 개수로부터 유래된 것이다. 오라클은 DBWR의 개수가 4보다 작으면 4 * CPU_COUNT 만큼 lru 래치를 생성하고, DBWR의 개수가 4이상이면 DB_WRITER_PROCESSES * CPU_COUNT 만큼 lru 래치를 생성한다. 필자의 시스템에서는 CPU 개수가 네 개이므로 16개의 래치가 생성되었고 그 중 실질적으로 사용되고 있는 것은 8K 버퍼 풀에 할당된 두 개의 래치임을 알 수 있다. 단, 앞서 언급한 것처럼 버퍼 풀의 최소 개수가 8개이기 때문에 lru 래치의 최소 개수도 8개임에 유의하자. 서버 프로세스가 스캔하는 모든 버퍼들이 LRU 리스트에 등록되기 때문에 LRU 리스트를 효율적으로 관리하는 것이 매우 중요하다. 특히 불필요하게 많은 량의 블록을 스캔하는 프로세스에 의해 중요한 버퍼들이 버퍼 캐시에서 밀려나는 것을 최소화할 수 있어야 한다. 오라클은 8i 이후의 버전부터 LRU 리스트를 효율적으로 관리하기 위해 Touch count에 기반한 LRU 알고리즘을 사용한다. 이 알고리즘은 LRU 리스트의 메인 리스트를 관리하는데 사용된다. Touch count 기반의 LRU 알고리즘을 그림으로 표현하면 아래의 그림과 같다.

그림:LRU알고리즘.jpg

Touch count 기반의 LRU 알고리즘은 다음과 같은 방식으로 작동한다.

  1. LRU 리스트의 메인 리스트는 크게 핫 영역(Hot Region)과 콜드 영역(Cold Region)으로 나누어진다. 자주 사용되는 블록은 핫 영역에 머무르며, 사용빈도가 낮은 블록은 콜드 영역에 머무른다. 오라클은 개별 버퍼마다 Touch count(접촉 회수)를 관리하며, 프로세스에 의해 스캔이 이루어질 때마다 Touch count를 1씩 증가시킨다.
  2. 프리 버퍼를 찾을 때는 우선 LRU 리스트의 보조 리스트에서 미사용된 버퍼를 찾는다. 만일 보조 리스트가 비어 있다면, 메인 리스트의 콜드 영역의 꼬리에서부터 프리 버퍼를 찾는다. 메인 리스트의 꼬리에 있으면서 Touch count가 1이하인 버퍼가 프리 버퍼로 사용된다. 프리 버퍼를 찾는 과정에서 Touch count가 2 이상인 블록을 만나면 핫 영역의 머리(Head of Hot Region)로 옮기고 해당 버퍼의 Touch count를 0으로 초기화시킨다. 핫 영역으로 옮기는 기준이 되는 값은 _DB_AGING_HOT_CRITERIA 히든 파라미터이며 기본값이 2이다.
  3. 싱글 블록 I/O에 의해 읽혀진 블록은 Mid-point에 삽입되며 Touch count는 1의 값을 지닌다. Mid-point가 가리키는 위치는 콜드 영역의 머리(Head of Cold Region)이다. 싱글 블록 I/O에 읽혀진 블록은 콜드 영역의 머리에 위치함으로써 버퍼 캐시에 머무를 확률이 높아진다.
  4. 멀티 블록 I/O에 의해 읽혀진 블록들은 Mid-point에 삽입된 후 콜드 영역의 제일 뒤(Tail of Cold Region)으로 옮겨진다. 풀테이블스캔(FTS)이나 인덱스풀스캔으로 읽힌 블록들은 콜드 영역의 꼬리에 위치함으로써 버퍼 캐시에 머무를 확률이 낮아진다.
  5. Keep 버퍼 풀과 Recycle 버퍼 풀은 Default 풀과는 달리 영역의 구분이 불필요하므로 핫 영역을 가지지 않는다. Recycle 버퍼 풀은 핫 영역을 가지지 않는다는 점을 제외하면 Default 버퍼 풀과 완전히 동일한 방식으로 작동한다. 하지만 Keep 버퍼 풀의 경우에는 FTS로 읽히는 작은 크기의 테이블을 메모리에 상주시키기 위해 고안된 공간이기 때문에 멀티 블록 I/O로 읽은 블록들을 싱글 블록 I/O로 읽은 블록과 동일하게 콜드 영역의 제일 앞에 위치시키도록 구현되었다.

[편집] 전역 임시 테이블

전역 임시 테이블(Global Temporary Table)은 세션 레벨의 임시 데이터를 저장하는 용도로 사용된다. 오라클의 실행 계획을 저장하기 위한 PLAN 테이블이 전역 임시 테이블의 대표적인 사례이다. 전역 임시 테이블은 세션 레벨의 임시 데이터를 저장하기 때문에 RAC 시스템에서의 글로벌 동기화가 불필요하다. 따라서 오라클은 전역 임시 테이블에 대해서는 글로벌 동기화를 수행하지 않는다. 따라서 세션 레벨에서만 사용하는 데이터를 조작하는 경우에는 가능한 전역 임시 테이블을 사용하도록 한다.

[편집] 읽기 전용 테이블스페이스

읽기 전용 테이블스페이스(Read-only Tablespace)에 속하는 데이터를 액세스하는 과정에서는 글로벌 동기화가 불필요하다. 읽기 전용 테이블스페이스의 데이터는 절대 사용자의 DML에 의해 변경되지 않기 때문에 오라클은 불필요한 글로벌 동기화 과정을 거치지 않는다. 따라서 읽기 전용 데이터들은 반드시 읽기 전용 테이블스페이스에 위치시키는 것이 바람직하다.

[편집] V$CURRENT_BLOCK_SERVER

V$CR_BLOCK_SERVER 뷰가 CR 블록 전송에 대한 통계 값을 제공하는 반면 이 뷰는 Current 블록 전송에 대한 통계 값을 제공한다. 이 뷰를 통해 Current 블록 전송과 관련된 핀(Pin), 플러시(Flush), 퓨전 기록(Fusion Write) 작업의 수행 성능을 확인할 수 있다. 핀(Pin)에 많은 시간이 소모되었다면 동일 블록에 대한 경합(Contention)이 심하다는 것을 의미한다. 리두 플러시(Redo Flush)에 많은 시간이 소모되었다면 LGWR 프로세스의 성능에 문제가 있음을 암시한다. 퓨전 기록(Fusion Write)에 많은 시간이 소모되었다면 DBWR 프로세스의 성능에 문제가 있음을 암시한다. 각 컬럼의 의미는 다음과 같다.

컬럼 명설명
PIN1 1 밀리 초안에 핀(Pin)을 획득한 회수
PIN10 10 밀리 초안에 핀(Pin)을 획득한 회수
PIN100 100 밀리 초안에 핀(Pin)을 획득한 회수
PIN1000 1000 밀리 초안에 핀(Pin)을 획득한 회수
PIN10000 10000 밀리 초안에 핀(Pin)을 획득한 회수
FLUSH1 1 밀리 초안에 리두 플러시(Redo Flush)를 수행한 회수
FLUSH10 10 밀리 초안에 리두 플러시(Redo Flush)를 수행한 회수
FLUSH100 100 밀리 초안에 리두 플러시(Redo Flush)를 수행한 회수
FLUSH1000 1000 밀리 초안에 리두 플러시(Redo Flush)를 수행한 회수
FLUSH10000 10000 밀리 초안에 리두 플러시(Redo Flush)를 수행한 회수
WRITE1 1 밀리 초안에 퓨전 기록(Fusion Write)을 수행한 회수
WRITE10 10 밀리 초안에 퓨전 기록(Fusion Write)을 수행한 회수
WRITE100 100 밀리 초안에 퓨전 기록(Fusion Write)을 수행한 회수
WRITE1000 1000 밀리 초안에 퓨전 기록(Fusion Write)을 수행한 회수
WRITE10000 10000 밀리 초안에 퓨전 기록(Fusion Write)을 수행한 회수

아래의 스크립트는 필자의 테스트 시스템에서 V$CURRENT_BLOCK_SERVER 뷰를 조회한 것이다. 리두 플러시(Redo Flush)와 퓨전 기록(Fusion Write)에 많은 시간이 소모되는 것을 확인할 수 있으며, 이로부터 I/O 시스템의 성능이 전반적으로 느리다는 추론이 가능하다.

SELECT * FROM V$CURRENT_BLOCK_SERVER
-------------------------------------		
PIN1			: 53642652
PIN10			: 8079
PIN100			: 126715
PIN1000		                : 2276
PIN10000		                : 643
FLUSH1			: 283
FLUSH10		                : 5485
FLUSH100		                : 67842
FLUSH1000                                : 12403
FLUSH10000	                : 311
WRITE1			: 0
WRITE10		                : 18715
WRITE100		                : 947335
WRITE1000	              	: 3607630
WRITE10000		: 863429
-----------------------------------

[편집] Analysis Case

[편집] 인터커넥트 설정에 의한 global cache cr request 대기현상

active session의 peak 시점을 확인해보면, global cache 관련 이벤트를 대기하는 세션이 다수 발생하고 있다.

그림:7_5_1.jpg

global cache cr request 이벤트를 대기하는 세션들은 Logical reads, physical reads 등의 세션 일량이 0으로 전혀 작업을 하지 못하고 계속해서 지연되고 있음을 알 수 있다.

시스템 레벨의 session logical reads/physical reads의 그래프를 확인해 보아도 전혀 일량이 발생하지 않음을 확인할 수 있다. 또한, global cache cr request의 그래프의 수치를 통해 300초 이상 대기하고 있음을 알 수 있다.

global cache cr request 이벤트는 로컬 캐시에 존재하지 않는 데이터 블록을 읽고자 하는 세션이 해당 데이터 블록을 관리하는 마스터 노드에게 블록 전송을 요청하고, 응답을 받을 때까지 대기하는 이벤트이다. 즉, RAC 노드간 블록 교환이 원활하게 이루어지지 않음을 알 수 있다.

global cache cr request의 파라미터 값을 통해 File#, Block#를 알 수 있으며, 위의 세션들은 모두 다른 블록을 요청하고 있으므로, 블록 경합보다는 네트워크 환경 설정으로 인한 지연 원인을 유추할 수 있다. 즉, 느린 인터커넥트, 비효율적인 네트워크 설정, 비효율적인 버퍼 캐시 등을 고려할 수 있다.

위의 사례는 인터커넥트의 대역폭을 잘못 설정하였기 때문에 발생하였다. 일반적으로는 1Gigabit Ethernet의 인터커넥트를 사용하는데, 이 경우에 동시에 교환 가능한 데이터의 양은 125MByte이다. 따라서 125MByte 이상의 데이터를 인터커넥트에서 교환하게 되면, 블록 전송 과정에서 응답 시간이 지연되는 현상이 발생한다. 그런데, 문제의 인스턴스는 동시에 교환 가능한 데이터의 양을 10MByte로 설정되어 거의 블록을 주고 받지 못하고 지연되는 현상이 발생하였다.

인터커넥트의 대역폭을 높인 이후, 다음과 같이 블록 교환이 원활해져 문제가 해결되었다. 그림:7_5_2.jpg Active Session의 추이가 안정되었으며, global cache cr request의 그래프의 수치도 0.1초 이하로 감소하였다.