Row Cache Lock

EXEM Knowledge Base

Jump to: navigation, 찾기

목차

[편집] Basic Info

오라클은 딕셔너리의 정보를 SGA내의 Row Cache(혹은 dictionary cache)영역에 저장하고 있다. Row Cache는 Shared Pool 영역에 존재하며 다음과 같은 쿼리로 확인할 수 있다.

SQL>  select pool, name, bytes from v$sgastat
  2   where name = 'row cache';
POOL NAME BYTES --------------- --------------- ---------- shared pool row cache 7480368

딕셔너리의 내용을 변경하고자 하는 프로세스는 그에 해당하는 row cache object에 대해서 row cache lock을 획득해야 한다. row cache lock은 Row Cache 영역을 보호하는 락이다. Row Cache 혹은 Dictionary Cache 영역은 오라클의 딕셔너리 정보를 보관하는 공유 메모리 영역으로, 많은 프로세스가 동시에 딕셔너리 정보를 참조하거나 변경하Row Cache에서의 경합이 발생한다. V$ROWCACHE 뷰를 참조하면 어떤 종류의 데이터가 Row Cache 영역에서 관리되고 있는지 확인할 수 있다.

SQL> SELECT                                            
		CACHE#, TYPE, PARAMETER, COUNT, GETS, GETMISSES     
FROM V$ROWCACHE;
CACHE# TYPE PARAMETER COUNT GETS GETMISSES ---- ---- ------------------- ---- ---- ---------- 1 PARENT dc_free_extents 0 0 0 4 PARENT dc_used_extents 0 0 0 2 PARENT dc_segments 651 17000 1048 0 PARENT dc_tablespaces 10 1665199 11 5 PARENT dc_tablespace_quotas 1 2 1 6 PARENT dc_files 0 0 0 7 PARENT dc_users 32 113166 36 3 PARENT dc_rollback_segments 26 45160 36 8 PARENT dc_objects 1033 58828 1861 … 16 SUBORDINATE dc_histogram_data 4765 38747 4639 16 SUBORDINATE dc_histogram_data 124 7592 130 19 SUBORDINATE dc_partition_scns 0 0 0 7 SUBORDINATE dc_users 29 1211 29 7 SUBORDINATE dc_users 0 0 0 21 SUBORDINATE rule_fast_operators 0 0 0

Row Cache Object(이하 RCO)는 개별 딕셔너리 객체를 의미한다. 가령 하나의 시퀀스 A가 있다고 하자. 시퀀스 A는 물리적으로 SYSTEM 테이블스페이스의 SYS.SEQ$ 테이블에 저장되어 있다. 사용자가 시퀀스 A를 참조하면 SYS.SEQ$ 테이블에서 시퀀스 A에 해당하는 로우(Row)를 buffer cache를 경유하여 Row Cache 영역으로 읽어 들이고, 이 과정에서 시퀀스 A에 해당하는 RCO가 생성된다. 그림:shared pool.jpg 일반 테이블의 경우 블록 단위로 캐시 영역에 읽어 들이는 반면에 딕셔너리 정보는 로우 단위로 캐시 영역에 읽어 들이기 때문에 Row Cache라는 이름이 붙여졌다. RCO를 변경하고자 하는 프로세스는 반드시 row cache lock을 획득해야 한다. 가령 SEQUENCE.NEXTVAL을 호출하는 과정에서 딕셔너리 정보를 변경할 필요가 생기면 시퀀스에 대해 row cache lock을 SRX(5) 모드로 획득해야 한다. ALTER 문을 이용해 테이블을 변경하는 프로세스는 테이블에 대해 row cache lock을 독점(X) 모드로 획득해야 한다.

Row cache lock은 Enqueue 구조를 사용하지 않으며 row cache object 정보안에 존재하는 락보유목록(Owner list)과 락대기목록(Waiter list)을 통해서 블로킹 메커니즘을 구현한다. 이러한 구조는 library cache lock, library cache pin, buffer lock과 마찬가지인데, 가령 버퍼 헤더에는 user list와 waiter list가 존재하고 이 두개의 리스트를 통해 블로킹 메커니즘을 구현한다. 흔히 row cache lock을 row cache enqueue라고 부르지만, 이것은 의미를 정확하게 전달하기 위해 사용하는 용어일 뿐 TX 락과 같이 Enqueue 구조를 사용하지는 않는다는 점에 유의하자.

V$ROWCACHE 뷰와 V$ROWCACHE_PARENT 뷰를 이용하면 row cache에서의 경합에 대한 분석이 가능하다. V$ROWCACHE 뷰는 개별 row cache object가 아닌 row cache 전체(종류)의 활동성에 대한 통계정보를 제공한다. V$ROWCACHE_PARENT 뷰를 이용하면 실제 개별 row cache object의 활동성을 파악할 수 있다. row cache lock 대기이벤트의 한가지 불편한 점은 실제 객체 정보가 아닌 객체의 종류에 대한 정보만 제공한다는 것이다. 이로 인해 정확하게 어떤 row cache object가 문제가 되는지를 파악하기 어렵다. row cache 덤프를 이용하면 실제 객체에 대한 정보까지 얻을 수 있다.

[편집] Parameter & Wait Time

[편집] Wait Parameters

RCO를 사용하고자 하는 모든 프로세스들은 row cache lock을 획득해야 한다. Row cache lock을 획득하는 과정에서 경합이 발생하면 row cache lock 이벤트를 대기한다. row cache lock 대기이벤트의 파라미터 정의는 다음과 같다.

  • P1 : cache id. 캐시의 종류를 의미한다. V$ROWCACHE.CACHE# 과 조인하면 캐시의 종류를 얻을 수 있다. 가령 dc_tablespaces, dc_sequences와 같은 값을 지닌다.
  • P2 : mode. Row cache lock을 획득한 모드를 의미한다. 모드 값의 의미는 일반적인 락 모드 값 정의와 일치한다. 1은 N 모드, 2는 RS 모드, 3은 RX 모드, 4는 S 모드, 5는 SRX 모드, 그리고 6은 X 모드를 의미한다
  • P3 : request. Row cache lock을 요청한 모드를 의미한다. 모드 값의 의미는 P2와 동일하다.

[편집] Wait Time

3초 동안 대기한다. 100번의 타임아웃이 발생하면, 프로세스는 중단되고, alert log 파일에 “WAITED TOO LONG FOR A ROW CACHE ENQUEUE LOCK” 이란 에러 메시지를 기록한 후 종료한다.

[편집] Check Point & Solution

[편집] NOCACHE 속성의 시퀀스를 사용하는 경우

Row cache lock의 경합이 가장 빈번하게 발생하는 경우는 NOCACHE 속성의 시퀀스이다. 캐시를 사용하지 않는 시퀀스는 다음 값(NEXTVAL)을 호출할 때마다 딕셔너리 정보를 변경해야 하며, 이 과정에서 RCO에 대해 row cache lock을 SRX(5) 모드로 획득해야 한다. SRX 모드간에서 호환성이 없다. 따라서 NOCACHE 속성의 시퀀스에 대해 여러 세션이 동시에 다음 값(NEXTVAL)을 호출하는 경우에는 row cache lock 이벤트에 대한 대기가 광범위하게 발생하며, P2 값은 “5”로 관찰된다. 아래 예제는 NOCACHE 속성의 시퀀스가 사용되는 환경에서 발생하는 row cache lock 대기 현상을 모니터링 한 것으로 P3(request) 값이 “5”의 값을 보이는 것을 확인할 수 있다.

WAIT #9: nam='row cache lock' ela= 82009 cache id=13 mode=0 request=5 obj#=-1 tim=187377640119
…                                                                                            
SQL> SELECT PARAMETER FROM V$ROWCACHE WHERE CACHE# = 13;
PARAMETER ---------- dc_sequences

NOCACHE속성의 시퀀스를 사용함으로 인해 row cache lock이 발생할 경우 여러 세션에 의해 동시에 사용되는 시퀀스는 캐시 크기를 크게 해주는 것이 바람직하다. 1,000 정도의 캐시 크기를 기본으로 사용하되, 필요하면 10,000 이상의 큰 값을 부여해도 무방하다.

동시성이 높은 시스템에서는 항상 row cache lock 경합이 생길 위험이 상존한다. 시퀀스를 제외한 대부분의 row cache lock 경합 현상은 뚜렷한 튜닝 방법이 없는 것이 현실이다. 적절한 시스템 튜닝을 통해 동시성을 줄이는 것이 최선의 방법이다.

[편집] Event Tip

[편집] 대기 이벤트를 통한 row cache rock의 확인

Row cache lock의 경합을 관찰하는 가장 손쉬운 방법은 대기이벤트를 이용하는 것이다. 하지만, 대기이벤트만으로는 락 홀더 세션과 대기 세션간의 경합 관계를 파악하기가 힘들다. V$ROWCACHE_PARENT 뷰를 이용하면 이러한 경합 관계를 파악할 수 있다. 우선 V$ROWCACHE_PARENT 뷰의 중요한 컬럼들의 의미를 알아보자.

  • CACHE#: 캐시의 종류를 의미한다. V$ROWCACHE.CACHE# 컬럼과 같은 의미이다.
  • CACHE_NAME: 캐시의 종류를 의미한다. V$ROWCACHE.PARAMETER 컬럼과 같은 의미이다.
  • LOCK_MODE: 해당 RCO에 대해 row cache lock을 획득한 모드. 1 ~ 6 사이의 값을 지닌다. row cache lock 대기이벤트의 P2와 같은 의미이다.
  • LOCK_REQUEST: 해당 RCO에 대해 row cache lock을 요청한 모드. 1 ~ 6 사이의 각을 지닌다. row cache lock 대기이벤트의 P3와 같은 의미이다.
  • SADDR: 해당 RCO를 현재 사용 중인 세션의 주소. “사용 중”이라는 것은 row cache lock을 획득했거나 요청 중이라는 것을 의미한다. V$SESSION.SADDR 컬럼과 조인 가능하다.

아래 스크립트는 NOCACHE 속성의 시퀀스 사용에서 발생하는 row cache lock의 경합을 V$ROWCACHE_PARNET 뷰를 통해서 관찰하는 예제이다.

SQL> SELECT                                          
cache_name, lock_mode, lock_request, saddr           
FROM v$rowcache_parent                               
WHERE address = ‘[특정 RCO address]’;
CACHE_NAME LOCK_MODE LOCK_REQUEST SADDR ---------- -------- ---------- ------------- dc_sequences 0 5 C00000009E0923E0 dc_sequences 0 5 C00000009E0A92A0 dc_sequences 0 5 C00000009E0C15C0 dc_sequences 0 5 C00000009E133F80 dc_sequences 0 5 C00000009E0CCD20 dc_sequences 0 5 C00000009E07C980 dc_sequences 0 5 C00000009E053D80 dc_sequences 5 0 C00000009E089540

세션 주소 값 “C00000009E089540”에 해당하는 세션이 5(SRX)번 모드로 row cache lock을 획득 중이며, 나머지 세션들은 5(SRX)번 모드로 row cache lock을 획득하기 위해 대기하고 있다. 캐시의 종류는 모두 시퀀스(dc_sequences)이다. 이처럼 V$ROWCACHE_PARENT 뷰와 V$SESSION 뷰를 적절히 활용하면 row cache lock의 홀더 세션/대기 세션 관계를 명확하게 파악할 수 있다.

[편집] row cache 덤프와 V$ROWCACHE_PARENT 뷰로부터 정확한 객체 정보를 확인하는 방법

SQL> create sequence seq_seq nocache;
-- NOCACHE 속성의 시퀀스를 생성한다. -- 시퀀스의 Object ID를 얻고, 이를 16진수로 변환한다. 16진수로 변환된 값을 이용해야만 row cache 덤프 내에서
시퀀스에 대한 정보를 참조할 수 있다.
select object_id from DBA_OBJECTS where object_name = 'SEQ_SEQ';
OBJECT_ID ---------- 107886 SQL> select to_hex(107886) from dual;
TO_HEX(107886) ------------------------------------------ 1A56E <-- 이 값을 이용해 row cache 덤프를 찾아본다.
-- 다음 명령을 사용해서 row cache를 덤프 한다. alter session set events 'immediate trace name row_cache level 12'
-- Row Cache 덤프 파일의 내용은 다음과 같다. BUCKET 104: row cache parent object: address=201D46CC cid=13(dc_sequences) hash=ba7abee7 typ=9 transaction=00000000 flags=00000002 own=201D4740[201D4740,201D4740] wat=201D4748[201D4748,201D4748] mode=N status=VALID/-/-/-/-/-/-/-/- data= 0001a56e 00020004 000f0002 00020001 000002c1 00000000 00000000 00000000 02c10000 00000000 00000000 00000000 00000000 64640ace 64646464 64646464 00646464 00800000 00000000 00000000 00000000 00000000 000002c1 00000000 00000000 00000000 2d2d0000 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d

위의 덤프 파일에서 의미 있는 정보는 다음과 같다.

  • Bucket : row cache 또한 일반적인 다른 객체들처럼 해시 값에 의한 버킷 구조로 관리됨을 알 수 있다.
  • address : Row Cache object의 메모리 어드레스. V$ROWCACHE_PARENT 뷰와 조인할 수 있다.
  • cid : Category id. V$ROWCACHE.CACHE#와 조인할 수 있다.
  • own : row cache lock을 보유한 프로세스목록
  • wat : row cache lock을 대기하는 프로세스목록
  • mode : 현재 row cache lock 보유 모드(N=Null)
  • data : 첫번째 16진수가 1a56e 로 SEQ_SEQ의 object_id의 16진수값과 동일함을 알 수 있다.
-- 이제, nextval을 대량으로 호출하면서 row cache lock이 어떻게 획득되는지 관찰해보자.           
-- 아래와 같이 10000번 동안 seq_seq.nextval을 호출한다. 
-- row cache lock을 획득하고 해제하는 시간은 매우 짧으므로 많은 회수를 시도해야 관찰이 가능하다.

declare    
  v_seq number;      
begin                   
  for idx in 1 .. 10000 loop    
    select seq_seq.nextval into v_seq from dual;   
  end loop;                        
end;                                
/                               

-- 위의 PL/SQL이 수행되는 동안 덤프를 통해 얻은 address=201D46CC 값을 이용해 
V$ROWCACHE_PARENT 뷰 정보를 조회하면, 아래 결과와 같이 SSX(Shared Sub-Exclusive) 모드로
row cache lock을 획득함을 알 수 있다. SQL> exec print_table('select * from v$rowcache_parent where address=201D46CC'); INDX  : 2237 HASH  : 103 ADDRESS  : 201D46CC CACHE#  : 13 CACHE_NAME  : dc_sequences EXISTENT  : Y LOCK_MODE  : 5 <-- Shared Sub-Exclusive Mode LOCK_REQUEST  : 0 TXN  : 21EEE5BC

Shared Sub-Exclusive 모드의 의미는 객체 전체에 대해서는 Shared 모드로, 객체의 일부분에 대해서는
Exclusive 모드로 락을 획득하는 것이다. Seqneuce.nextval 호출에 의해 시퀀스 딕셔너리 정보가 변경되는 경우,
시퀀스 자체를 변경시키는 것은 아니고 시퀀스의 "다음값"만을 변경시키는 것이므로
SSX 모드로 row cache lock을 획득하는 것이다.

[편집] Analysis Case

[편집] Remote Query 수행 시 발생한 Row Cache 경합 분석

대상 DB의 8i -> 10g 마이그레이션 작업 후 모니터링 중 latch: row cache objects 이벤트의 대기가 주기적 발생하였다. DB를 오픈한 7월 12일 12시 이후의 데이터를 대상으로 Performace Analyzer의 Session List 기능을 이용해 latch: row cache objects 이벤트의 발생 현황을 조회해 본다.

그림:8_1_1.jpg

SELECT * FROM "DUAL" 이란 SQL을 실행하는 세션들이 latch: row cache objects 이벤트를 대기하고 있으며, SQL문이 모두 대문자이고 테이블 명에 " "(큰따옴표)가 되어 있는 것을 보아 원격에서 실행한 쿼리임을 알 수 있다.

latch: row cache objects 이벤트 발생하는 상황에서 V$SESSION 뷰를 조회하여 latch의 address 를 확인할 수 있다.

그림:8_1_2.jpg

EVENT# 205번인 latch: row cache objects 를 대기하는 세션들은 동일한 래치 address를 가지고 있음을 P1RAW 컬럼을 통해 알 수 있다. (C0000001B7C03BC8)

latch: row cache objects 이벤트를 대기와 동일한 시점에 row cache lock 이벤트를 대기하는 세션도 확인할 수 있다.

그림:8_1_3.jpg

특히, latch: row cache objects 이벤트를 대기하는 세션이 수행한 SQL이 동일하다. 또한, row cache lock의 row cache id = 16번임을 알 수 있다.

row cache lock이 발생한 세션의 cache id를 V$ROWCACHE 뷰를 통하여 조회하면 결과는 다음과 같다.

그림:8_1_4.jpg

16번 row cache에 대한 사용횟수(USAGE)와 요청 수(GETS), 요청 실패 수(GETMISSES) 가 높게 나타나고 있다. Row Cache는 오라클의 dictionary meta 정보를 제공하므로, 대기 또는 경합이 발생하지 않는 것이 좋으므로, dc_histogram_defs 딕셔너리 오브젝트에 관하여 어떤 부분에서 사용되는 오브젝트인지 확인하여야 한다.

오라클 메타링크를 찾아보면, Remote Query를 수행할 때, local DB의 테이블, 컬럼 히스토그램을 참조하기 위해 dc_histogram_defs 딕셔너리에 접근하는데, 이때 shared pool의 부하가 발생하는 버그가 있다. (오라클 8버전까지 존재했었다.) 이와 같은 상황을 고려해 10g임에도 재발되지는 않는지 오라클에 문의하는 것이 바람직할 것으로 보인다.