오라클 내부구조와 OWI

버퍼캐시와 OWI
버퍼 캐시 구조
버퍼 캐시 - 최근에 사용한 블록에 대한 정보를 담고있음 - show sga로 사이즈 확인 가능
버퍼캐시의 효과적인 관리를 위해 해시체인 구조 사용 : 해시 체인은 버킷 - > 체인 -> 헤더 구조를 사용
해시 체인 구조의 시작점은 해시 테이블 - 해시 테이블은 여러개의 해시 버킷으로 이루어짐 - 하나의 해시 버킷은 해시함수 결과와 매칭
오라클은 DBA와 블록 클래스에 해시 함수 적용하여 해시 버킷을 찾아감
해시 버킷에는 같은 해시값을 갖는 버퍼 헤더들이 체인 형태로 걸려있다.
버퍼 헤더에는 버퍼에 대한 메타 정보 + 버퍼 메모리 영역의 실제 버퍼에 대한 포인터 값을 가지고 있다.
해시체인구조는 Shared Pool 영역에 존재한다 - 실제 버퍼에 대한 정보들은 버퍼 캐시 영역에 존재한다.
해시 체인 구조는 cache buffer chains 래치를 이용해 보호된다.
특정 블록을 스캔하려면 cache buffers chains 래치를 획득해야한다.
cache buffers chains 래치 하나가 여러개의 해시 체인을 관리한다 + 한번에 한 프로세스만이 이 락을 취득 가능하다 -> 동시에 많은 프로세스가 버퍼 캐시를 탐한하면 해당 래치에 경합 발생한다.
9i 부터는 읽기의 경우 Shared모드로 래치를 획득하여, 읽기 작업을 수행하는 프로세스간에는 cache buffers chains 래치를 공유할 수 있다.
하지만 버퍼에 대해 buffer lock을 획득할때는 Exclusive이기 때문에 읽기만 하더라도 여전히 경합 발생할 수 있음
cache buffers chains 래치의 개수를 구하는 방법 : select count(*) from v$latch_children where name = 'cache buffers chains'; 혹은 _DB_BLOCK_HASH_LATCHES 히든 파라메터 조회
해시 버킷의 수는 _DB_BLOCK_HASH_BUCKETS
_DB_BLOCK_HASH_BUCKETS / _DB_BLOCK_HASH_LATCHES = 하나의 래치가 관장하는 버킷의 수

Working Set
버퍼 캐시를 효율적으로 사용하기 위해 두 종류의 LRU 리스트를 사용한다 - LRU리스트(대체리스트) & LRUW리스트(기록리스트or더티리스트)
버퍼 캐시의 모든 버퍼들은 반드시 LRU리스트 또는 LRUW리스트 둘 중 하나에 속한다.
LRU 리스트 (대체 리스트)
- 메인 리스트 : 사용된 버퍼들의 리스트. 핫 영역과 콜드 영역으로 구분 관리된다.
- 보조 리스트 : 프리 버퍼들의 리스트. 더 정확하게 표현하면, 미 사용된 버퍼들이나, DBWR에 의해 기록된 버퍼들의 리스트

LRUW 리스트 (기록 리스트)
- 메인 리스트 : 변경된 버퍼들의 리스트
- 보조 리스트 : 현재 DBWR에 의해 기록중인 버퍼들의 리스트
LRU리스트와 LRUW리스트는 항상 짝으로 존재한다 - 이 짝을 Working Set이라고 부른다.
하나의 Working Set을 하나의 cache buffers lru chain 래치가 관리함
_DB_BLOCK_LRU_LATCHES 값을 조회하거나 select count(*) from v$latch_children where name = 'cache buffers lru chain'; 명령문을 통해 cache buffers lru chain 래치의 최대 개수를 구할 수 있다,
버퍼는 크게 default, keep, recycle 버퍼풀로 나뉘고, default 버퍼 풀은 블록 크기별로 표준사이즈, 2K, 4K, 8K, 16K, 32K 버퍼풀로나뉘어 최소 8개의 래치가 필요하게 된다.
cache buffers lru chain래치는 dbwr의 개수가 4개보다 작으면 4 x CPU_COUNT, 4개 이상이면 DB_WRITER_PROCESSES x CPU_COUNT 만큼 cache buffers lru chain래치를 생성한다.
모든 래치가 다 사용되는 것은 아니다.

8i이후로 LRU 리스트를 효율적으로 관리하기 위해, Touch count에 기반한 LRU 알고리즘을 사용한다.

Touch count 기반의 LRU 알고리즘 작동 방식
1. LRU 리스트의 메인 리스트는 크게 핫 영역과 콜드 영역으로 나누어진다. 자주 사용되는 블록은 핫 영역에 머무르며, 사용빈드가 낮은 블록은 콜드 영역에 머무른다. 오라클은 개별 버퍼마다 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로 읽은 블록과 동일하게 콜드 영역의 제일 앞에 위치시키도록 구현되었다.


Buffer lock - 버퍼 자체를 보호하는 역할을 한다.
버퍼의 내용을 변경하거나 읽으려는 프로세스는 buffer lock을 exclusive 혹은 shared 모드로 획득해야한다.

버퍼 탐색
1. 사용자가 요청한 블록의 DBA와 클래스에 대해 해시 함수를 이용해서 해시 값을 생성하고 해시 값에 해당하는 해시 버킷을 찾는다.
2. 해시 버킷을 보호하는 cache buffers chains 래치를 획득한다. 읽기 작업이라면 Shared 모드로, 변경 작업이라면 Exclusive 모드로 래치를 획득한다. 만일 이 과정에서 경합이 발생하면 latch: cache buffers chains 이벤트를 대기한다. 해시 버킷에 딸려있는 체인을 탐색해서 블록에 해당하는 버퍼 헤더가 존재하는지 확인한다. 버퍼 헤더가 이미 존재하고 해당 블록이 버퍼 캐시에 올라와 있는 상태라면 해당 버퍼에 대해 buffer lock을 Shared 모드나 Exclusive 모드로 획득하고 원하는 작업을 수행한다. 일반적으로 buffer lock을 획득하는 과정에서 경합이 발생하면 buffer busy waits 이벤트를 대기하게 된다. DBWR에 의해 기록중인 버퍼에 대해 buffer lock을 획득하는 과정에서 경합이 발생하는 경우에는 write complete waits 이벤트를 대기한다. cache buffers chains 래치를 획득한 후 해시 체인을 탐색하고 버퍼를 사용하기 위해 buffer lock을 획득한 후 버퍼를 읽는 일련의 작업을 "Logical Reads"라고 부른다. Logical Reads가 발생한 블록 수만큼 session logical reads 통계 값이 증가한다. 만일 Logical Reads 작업이 일관딘 모드의 읽기 (Consistent read)작업이라면 consistent gets 통계 값이 증가하고, 현재 모드의 읽기(Current read) 작업이라면 db block gets 통계 값이 증가한다. 따라서 session logical reads 통계값은 consistent gets 통계 값과 db block gets 통계값의 합과 일치한다.
3. 버퍼 캐시에 블록이 존재하지 않으면 우선 Working Set을 관리하는 cache buffers lru chain 래치를 획득한다. 이 과정에서 경합이 발생하면 latch: cache buffers lru chain 이벤트를 대기한다. 래치를 획득한 후 LRU 리스트의 보조 리스트에서 프리 버퍼를 찾는다. 보조 리스트가 비었다면, 메인 리스트에서 가장 덜 사용된 순서로 프리 버퍼를 찾는다. 이 과정에서 더티 버퍼가 발견되면 LRUW 리스트로 이동시킨다. 프리 버퍼를 찾게 되면 해당 버퍼에 대해 buffer lock을 Exclusive하게 획득하고 데이터 파일로부터 블록을 해당 버퍼로 읽어 들인다. 이 때 buffer lock을 획득하는 과정에서 경합이 발생하면 read by other session 이벤트를 대기한다. 데이터 파일로부터 물리적으로 블록을 읽어 들이는 일련의 작업을 "Physical Reads"라고 부른다. Physical Reads가 발생한 블록 수만큼 physical reads 통계 값이 증가한다. physical reads 통계 값은 direct path I/O 작업에서도 증가하기 때문에, 버퍼 캐시를 경유한 정확한 Physical Reads 값은 physical reads 통계 값에서 physical reads directm physical reads direct (lob)통계 값을 빼면 된다.
4. LRU 리스트에서 프리 버퍼를 찾을 때 _DB_BLOCK_SCAN_MAX_PCT (기본 값은 40) 파라미터의 값만큼 스캔을 하고도 프리 버퍼를 찾지 못하면 서버 프로세스는 LRU 리스트의 스캔을 멈춘다. 서버 프로세스는 DBWR에게 더티 버퍼를 파일에 기록하고 프리 버퍼를 확보할 것을 요청한다. DBWR에 의해 프리 버퍼가 확보될때까지 서버 프로세스는 free buffer waits 이벤트를 대기한다. 요청을 받은 DBWR은 DBWR make free request 통계 값을 증가시키고, cache buffers lru chain 래치를 획득한 후 LRUW 리스트를 콜드 영역의 꼬리에서부터 탐색한다. 디스크에 기록할 버퍼를 찾게 되면 buffer lock을 획득한 후 버퍼를 디스크에 기록한다. 디스크에 기록된 버퍼를 찾게 되면 buffer lock을 획득한 후 버퍼를 디스크에 기록한다. 디스크에 기록된 버퍼는 프리 버퍼로 변경되고 LRU 리스트로 옮겨진다. DBWR이 LRUW리스트를 탐색할 때마다 DBWR lru scans 통계 값과 DBWR buffers scanned 통계 값이 증가한다.

Logical Reads -> 블록을 보관하고 있는 버퍼 영역을 찾아가기 위해 버퍼가 속한 해시 버킷을 관리하는 cache buffers chains 래치를 획득하고, 해시 체인 리스트를 탐색한 후, 메모리 I/O를 통해 원하는 블록 이미지를 얻어오는 작업.
Buffer Pinning -> 동일 SQL콜 내에서 2번 이상 스캔되는 버퍼는 Pinned 상태로 변한다. Pinned 상태란 래치 획득없이 바로 버퍼로 접근할 수 있도록, 버퍼의 주소를 저장하고 있는 상태이다.
Pinned 상태의 버퍼를 탐색할 경우에는 session logical reads 통계값 대신 buffer is pinned count 통계값이 증가한다.

 

버퍼 캐시 덤프
버퍼 헤더 덤프방법
alter session set events 'immediate trace name buffers level 1'; 혹은 oradebug dump buffers 1
주요 정보
file#, rdba - 파일 번호와 DBA.
class - 블록의 종류
obj - 오브젝트 번호
hash - 해시 체인에서의 위치
lru - LRU 리스트에서의 위치
use - 해당 버퍼를 사용하고 있는, 즉 buffer lock을 획득 중인 세션 목록
wait - 해당 버퍼를 사용하려고, 즉 buffer lock을 획득하기 위해 대기중인 세션 목록
st - 상태. XCURRENT=현재이미지(Single Instance), CR=Consistent Read 이미지, FREE=미사용블록(블록이 보조 리스트에 위치)
md - 현재 사용중인 모드 EXCL=Exclusive, SHR=Shared
tch - Touch count. 블록이 얼마나 자주 사용되는지를 판단하는 수치로 LRU 리스트에서 위치를 옮기는 기준이 된다.

X$BH뷰를 통해서도 버퍼 헤더 정보를 얻을 수 있다.
주요 컬럼
HLADDR - 해당 버퍼를 관장하는 래치의 주소. V$LATCH_CHILDREN.ADDR과 조인해서 원하는 래치 정보를 얻을 수 있다.
TS# - 블록이 속한 테이블스페이스 번호. V$TABLESPACE.TS#과 조인한다.
DBARFIL - DBA 중 파일번호
DBABLK - DBA 중 블록번호
CLASS - 블록 클래서
STATE - 버퍼의 상태
TCH - Touch count

블록 클래스의 값
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 + 2*r : undo header block ( r= undo segment 번호. 0 = system undo segment)
16 + 2*r : undo block ( r= undo segment 번호. 0 = system undo segment)

버퍼의 상태 값
0 FREE : no valid block image
1 XCUR : a current mode block, exclusive to this instance
2 SCUR : a current mode block, shared with other instance (RAC에서만 쓰임)
3 CR : a consistent read (stale) block image
4 READ : buffer is reserved for a block being read from disk
5 MREC : a block in media recovery mode
6 IREC : a block in instance (crash) recovery mode
8 PI : past image (RAC에서만 쓰임)

 

Shared Pool / Library Cache와 OWI
Shared Pool과 Heap
Shared Pool - SGA의 구성요소 중 하나 - show sga로 공간 확인 가능
select * from (select name, bytes from v$sgastat where pool = 'shared pool' order by bytes desc); 이 명령어를 통해 Shared Pool의 구성 요소를 볼 수 있다 - 크게 4가지로 구분
- Permanent Area : 프로세스 목록, 세션 목록, Enqueue목록, Transaction목록 등의 자원들은 Shared Pool의 영구 영역에 할당되며 인스턴스와 그 수명을 같이한다.
- Library Cache : SQL문을 수행하는데 필요한 모든 객체(SQL, 테이블, 뷰, 프로시저 등)에 대한 정보를 관리한다.
- Row Cache : Dictionary Cache라고도 부르며, 오라클이 사용하는 딕셔너리 정보를 관리한다.
- Reserved Area : 예약영역. 동적인 메모리 할당을 위해 오라클이 여분의 예약 역역을 할당해둔다.
힙방식에 의해 메모리가 관리된다.

alter session set events 'immediate trace name heapdump level 2'; 이 명령어를 통해 힙영역을 덤프뜰 수 있다.
청크 중에 ds=c0000000993293e8과 같은 값을 가지고 있는 청크가 있는데, 그 청크들이 서브힙의 위치를 저장하고 있는 청크이다. ds = Heap Descriptor = 서브힙의 주소
alter session set events 'immediate trace name heapdump_addr addr 0xc0000000993293e8'; 이 명령어를 통해 서브힙을 덤프뜰 수 있다.
서브힙 : 익스텐트 크기가 가변적이며 예약 프리리스트를 관리하지 않는다는 점 외엔 최상위힙과 구조가 같음.

Shared Pool의 힙으로부터 메모리를 할당 받으려면 shared pool 래치를 먼저 획득해야함 -> 실패하면 latch: shared pool이벤트를 대기
shared pool 래치는 전체 인스턴스에 하나만 존재. 필요한 메모리를 할당받는 전체 과정동안 보유해야 한다.


Library Cache 구조
Library Cache - SQL문의 수행과 관련된 모든 정보들을 관리하는 영역
메모리구조 = 해시 테이블 -> 버킷 -> 체인 -> 핸들 -> 오브젝트(Library Cache Object = LCO)
핸들은 실제 LCO에 대한 메타정보 및 포인터 역할을 하며 LCO가 실제 정보를 담고 있다.
Library Cache Object가 담고 있는 내용 중 주요 내용
Dependency Table : 현재 LCO가 의존하는 LCO 들에 대한 정보. 가령 SQL 문장은 해당 문장이 참조하는 테이블, 뷰 등에 대해 의존성을 지닌다.
Child Table : 현재 LCO의 자식 LCO들에 대한 정보. 프로시저, 테이블과 같이 유일한 이름이 부여되는 LCO는 자식을 지니지 않는다. 오라클은 프로시저나 테이블과 같은 객체에 대해서는 스키마명을 항상 같이 저장하기 때문에 유일성이 보장된다. 하지만 SQL문장의 경우에는 SQL 텍스트 자체가 이름으로 사용되기 때문에 유일성이 보장되지 않는다. 따라서 오라클은 SQL 텍스트를 이름으로 갖는 부모 LCO를 생성하고 실제 SQL 커서에 대한 정보는 자식 LCO에 저장한다. 가령 두 개의 다른 스키마 A, B에서 텍스트는 동일하지만, 실제로 참조하는 객체는 다른 SQL 문장을 수행한 경우, 오라클은 SQL텍스트에 해당하는 부모 LCO와 스키마 A가 수행한 SQL커서에 해당하는 자식 LCO, 스키마 B가 수행한 SQL 커서에 해당하는 자식 LCO, 총 세 개의 LCO를 생성한다. 이 경우 V$SQLAREA.VERSION_COUNT 값은 3이 된다. 자식 LCO는 Library Cache 영역의 익명 리스트에 저장된다.
Data Blocks : LCO가 포함하는 실제 정보를 저장하는 청크 영역들에 대한 포인터 정보. 가령 SQL 커서의 경우 SQL 문장, 실행계획, 실행문맥정보 등은 특정 메모리 영역(청크)에 보관되고, 이들 청크의 주소값이 LCO의 Data Blocks 영역에 관리된다.
alter session set events 'immediate trace name library_cache level 10'; 라이브러리 캐쉬 덤프 뜨는 명령어

 

SQL 수행
1. 새로운 SQL문장 수행 요청 -> 문법체크 권한체크 -> library cache 래치 획득(경합시 latch: library cache 대기 이벤트) -> Library Cache 영역에 동일한 SQL문장, 즉 동일한 LCO가 존재하는지 확인 -> 존재시 바로 8번 단계로 넘어간다 = 소프트파싱 || 파싱 요청이 있을 때마다 parse count(total) 통계값을 증가시킨다.
2. 존재하지 않으면 -> shared pool 래치 획득(경합시 latch: shared pool 대기 이벤트) -> 가장 적절한 크기의 프리 청크를 프리리스트에서 찾는다. 프리 청크가 확보될 때까지 계속 shared pool 래치를 보유한다.
3. 최적 크기 프리 청크가 존재 않으면 -> 좀더 큰 크기의 프리 청크를 찾은뒤 이를 split하여 사용한다. -> split하고 남은 부분은 프리리스트로 등록한다.
4. 프리리스트를 모두 뒤지고도 적당한 프리청크를 찾지 못했다면 -> LRU 리스트를 탐색한다 -> LRU리스트의 청크는 Recreatable & unpinned상태이다 - 재 사용한다
5. LRU 리스트를 탐색하고도 확보 못했다면 -> Shared Pool내의 여유 메모리 공간을 추가적으로 할당
6. 여기까지 다 실패하면 ORA-4031
7. 적절한 프리 청크를 찾으면 -> SQL문장에 해당하는 Library Cache Handle에 대해 library cache lock을 Exclusive하게 획득 & LCO 정보를 생성 -> library cache lock을 Null모드로 변환 -> library cache pin을 Exclusive하게 획득 & 실행 계획 생성 || 2번~7번을 하드파싱이라 부른다. 하드파싱이 발생하면 parse count(hard)통계값을 증가시킨다. 하드파싱 과정에서 SQL 문장의 오류가 발견되면 parse count(hard) 통계값, parse count(failure)통계값 둘 모두가 동시에 증가한다
8. SQL 커서에 대해 library cache lock과 library cache pin을 Shared 모드로 획득(경합시 각각 l하고 SQL문장을 실행한다 = 실행(Execute) 단계 ||
9. 실행이 완료된 SQL커서에 대해 데이터를 페치한다 = 페치(Fetch) 단계 || 페치 단계에서는 SQL 커서에 대해 library cache lock을 Null모드로 변환하고, library cache pin은 해제

파싱 단계에서 사용된 CPU 시간과 파싱을 수행하는데 걸린 시간은 parse time cpu 통계값과 parse time elapsed 통계값에 기록된다.
파싱 과정중 래치나 락을 획득하기 위해 대기하는 시간이 길어지면 parse time elapsed 통계값이 parse time cpu 통계값보다 매우 크게 나타날 수 있다.
과도한 하드 파싱 -> 프리 청크들이 작은 크기로 많이 쪼개져 있다 -> SQL수행시 최적 크기의 프리 청크 발견 못함 -> ORA-4031 발생 || Shared Pool 단편화 현상
Shared Pool 단편화 현상 -> 프리리스트 개수가 많아짐 -> 프리리스트 탐색 시간이 증가 -> shared pool 래치를 보유하는 시간이 길어짐 -> shared pool 래치 경합 발생
ORA-4031응급조치 : alter system flush shared_pool명령 - Shared Pool을 flush함 - 연속된 프리 청크 병합됨 - 적절한 크기의 프리 청크가 발견될 가능성 높아짐 -> 하지만 다른 문제가 발생할 수 있음을 유념
파싱 발생시 마다 library cache 래치 획득해야함 -> 경합 가능성 높음
=> 그래서 오라클서 library cache 래치 경합 줄이기 위해 제공해주는 것들
1. PL/SQL 블록 내에서 반복 수행되는 SQL문은 pinned되어서 소프트파싱조차 없이 실행된다.
2. 세션 내부에 LCO의 위치를 캐싱할 수 있다 = 세션 커서 캐싱: 한 세션 내에서 세 번 이상 수행된 SQL 커서들의 위치와 SQL 텍스트를 PGA 영역에 저장한다 -> 소프트 파싱은 여전히 발생하지만 Library Cache 메모리 구조를 탐색할 필요 없이 바로 LCO 위치를 찾아간다 -> library cache 래치 보유시간 단축 -> library cache 래치 경합 줄어듬

 

트랜잭션과 OWI
트랜잭션 개요
DML수행시 오라클 내부 작업 순서
1. 해당 트랜잭션에 대해 언두 세그먼트 할당|| 온라인 상태의 언두 세그먼트 중 하나를 우선적으로 사용 -> 언두 세그먼트의 선택은 랜덤하게 이루어짐 -> 다른 트랜잭션이 사용중이면 3번까지 재시도 -> 실패시 오프라인 상태의 언두를 온라인화하여 사용 -> 이것도 실패하면 새로운 언두 세그먼트 생성 -> 이것도 실패하면 다른 트랜잭션에의새 사용중인 언두 세그먼트 중 가장 사용량 적은 것을 사용 || 언두 세그먼트 확보 못하면 확보될 때까지 enq: US - contention 대기 이벤트
2. 언두 세그먼트를 할당 받으면, 언두 세그먼트 헤어데 트랜잭션 테이블 슬롯을 생성
3. 트랜잭션 테이블을 생성하고 나면 TXID(Transaction ID)를 생성 -> 현재 트랜잭션에 할당 || TXID는 V$TRANSACTION뷰의 XIDUSN, XIDSLOT, XIDSQN세 컬럼 - 트랜잭션 테이블의 정확한 위치를 가리킴
4. 트랜잭션 대상 블록을 버퍼 캐시로 옮김 -> 블록 헤더의 ITL에 트랜잭션 엔트리를 등록 -> ITL에 엔트리 등록할 공간 부족시 확보할때까지 enq: TX - allocate ITL entry 대기 이벤트
5. 변경할 블록의 변경 정보는 PGA에 체인지 벡터라는 이름으로 저장됨 -> 보통 하나의 로우 변경시 각각 언두 헤더 블록(체인지 벡터 #1), 언두 블록(체인지 벡터#2), 데이터 블록(체인지 벡터 #3) 라는 체인지 벡터들이 생성됨 -> PGA의 체인지 벡터를 리두버퍼로 복사함 - 이 과정에 redo copy 래치, redo allocation 래치, redo writing 래치 획득해야함. 실패시 latch: redo copy, latch: redo allocation, latch: redo writing 대기 이벤트
6. Before Image에 대한 정보를 언두 블록이 기록 & 데이터 블록 변경 -> 변경된 데이터 블록은 더티상태가 됨 -> 변경된 데이터 블록에 대한 CR블록이 버퍼 캐시에 생성됨 || 만약 다른 트랜잭션에 의해 변경 중인 로우라면, 해당 트랜잭션 종료시까지 enq: TX - row lock contention 대기 이벤트
7. 커밋시 -> 트랜잭션에 SCN할당 -> 커밋 정보는 리두 버퍼에 저장
8. 트랜잭션 테이블에 커밋되었음을 기록 -> 락을 포함한 모든 리소스 점유 해제
9. 리두 버러의 내용이 리두로그 파일에 기록됨 -> 변경된 블록이 데이터 파일로 기록됨

트랜잭션과 블록 덤프
select rowid, dbms_rowid.rowid_relative_fno(rowid) as fno, dbms_rowid.rowid_block_number(rowid) as blkno from test where rownum = 1; - 특정 로우의 파일 번호와 블록 번호를 얻어내는 구문
alter system dump datafile 1 block 55818; - 특정 파일의 특정 블록을 덤프하는 명령
블록 덤프 받은 파일로 부터 확인할 수 있는 내용
ITL정보로부터
- lck = 트랜잭션에 의해 변경된 row수
- Flag = 해당 값이 "C"혹은 "U" 라면 커밋 됐다는 것
- scn = 커밋된 이후에는 scn값이 생긴다
- Uba - 현재 트랜잭션이 가장 최근에 사용한 언두 블록과 언두 레코드의 위치 = 언두블록DBA + Seq# + Record#

block_row_dump 영역으로부터
- lb = Lock Byte - 값이 있다면 lock된상태. 락을 건 ITL번호가 기록된다 - 해당 값은 커밋이 발생해도 정리되지 않으며, 나중에 Delayed block cleanout이 발생하거나, 다른 프로세스에 의해 블록이 변경되는 경우에 정리된다.

로우 레벨 락 : 로우 레벨 락은 TX 락을 이용해 구현되지만, TX 락 자체가 로우 레벨 락은 아니다.
로우 레벨 락은 [로우의 변경 여부 + 로우를 변경한 트랜잭션정보(ITL) + 언두 영역의 트랜잭션 테이블 슬록 + TX 락]의 정보들이 조합된 일종의 논리적인 락이다.


트랜잭션과 언두 헤더 덤프
언두 세그먼트의 헤더 블록을 덤프하면 트랜잭션에 해당하는 정보가 어떻게 관리되는지 확인할 수 있다.

select xidusn,xidslot,xidsqn from v$transaction where addr = (select taddr from v$session where sid = 17);
select file_id, block_id from dba_rollback_segs where segment_id = 3;
alter system dump datafile 2 block 41;


Cleanout 혹은 block cleanout - 블록에 설정된 로우 레벨 학을 해제한다는 의미 + ITL 정보(SCN, Flag, Lock Byte)가 갱신된다
Fast commit : 커밋 시점에 모든 블록에 대해 cleanout을 수행하지 않는다는 의미 -> 변경된 데이터 블록 중 버퍼 캐시에 올라와 있는 일부 블록에 대해서만 cleanout 수행 || Flag, SCN정보만 변경되고 lock byte는 변경되지않음. lock byte는 트랜잭션에 의해 변경된 모든 로우에 저장되기 때문 || 변경된 일부 데이터 블록들의 헤더에 대해서만 변경 작업을 수행 -> 리두데이터가 생성되지 않고 커밋 마크만이 리두에 저장됨.
Delayed block cleanout : fast commit에 의해 cleanout 되지 않은 블록들을 나중에(delayed) cleanout 처리한다는 의미 - 다음번에 해당 블록을 스캔하는 프로세스에 의해 수행된다.

커밋 수행에 의해 cleanout이 수행되면 cleanout이 발생한 블록수만큼 commit cleanouts 통계값이 증가한다
cleanout중 에러 발생시 commit cleanout failures: xxx 류의 통계값이 증가한다. 성공시에는 commit cleanouts successfully completed 통계값이 증가한다.
user commits 통계값 - 사용자가 커밋을 수행한 회수 || commit cleanouts 통계값 - 커밋에 의한 cleanout이 발생한 블록수
ITL flag의 값이 U(Upper Bound Commit): fast commit시, C(Commit): cleanout후 + lock byte값도 0이된다.

 

세그먼트와 OWI
세그먼트 개요
세그먼트 : 테이블, 인덱스, 언두, LOB등 오라클이 제공하는 모든 종류의 논리적인 공간 || 익스텐트라는 요소로 나누어짐 - 세그먼트 공간의 확장을 담당 -> 세그먼트 여유공간 없는 상태에 추가적인 insert가 발생하면 익스텐트를 추가로 할당한다. || 익스텐트는 블록이라는 연속적인 물리 공간으로 이루어짐 - 오라클의 물리적/논리적 IO의 최소단위
익스텐트에 의해 새로 할당받은 모든 공간을 다 사용하고 있는 것이 아니기 때문에 - 사용된 공간과 사용하지 않은 공간을 구분하는 표시 High Water Mark를 사용
Dictionary Managed Tablespace를 사용하는 경우 ST락을 획득해서 익스텐트 할당 작업 가능 (경합시 enq: ST - contention 대기 이벤트 - 9i이후로는 거의 발생 않는다고함)
HWM을 이동하는 작업시 HW 락을 통해 보호함 (경합시 enq: HW - contention 대기 이벤트)


FLM(FreeList Management) - 수동 세그먼트 공간 관리
프리리스트를 통해 세그먼트 공간 관리 - 프리리스트 : 프리 블록을 링크드 리스트 형태로 관리 + 개별 블록에 자신이 프리리스트에 존재하는지 여부와 다음 프리블록의 위치 정보를 기록
프리 블록인지 아닌지 여부는 PCTFREE, PCTUSED 속성에 의해 결정됨

세그먼트별로 기본적으로 하나의 마스터 프리리스트를 할당 -> 세그먼트 생성시 FREELISTS 속성 값에 할당된 수만큼 프로세스 프리리스트를 사용한다.
익스텐트 할당 -> 신규로 프리블록 할당 -> 우선 마스터 프리리스트에 저장됨 -> 그후 개별 프로세스가 프리 블록을 필요로할 때 마스터 프리리스트로부터 필요한 수의 프리 블록을 얻어온다.
마스터 프리리스트에 더이상 프리 블록이 없을시 ->HW락 획득 (경합시 enq: HW - Contention 대기 이벤트) -> HWM이동 -> 프리 블록 확보 | 익스텐트에 할당된 블록을 모두 사용했다면 익스텐트 추가 할당
트랜잭션 프리리스트 - 트랜잭션에 의해 프리 블록으로 변경된 블록들을 저장 - 트랜잭션 중에는 가능하면 트랜잭션 프리리스트를 사용한다.
프리리스트 개수가 작다면 - 프리리스트에서의 경합 발생 - buffer busy waits 대기 이벤트 -> 프로세스 개수를 고려하여 FREELISTS 속성값을 충분하게 지정하자
익스텐트 할당시 HWM을 얼만큼 이동시키는가 = FREELISTS x _BUMP_HIGHWATER_MARK_COUNT || 너무 적게 이동시키면 자주 HWM를 이동시켜야하고 HW 래치에 경합발생 가능성 높으니 FREELISTS를 높게잡자

프리 리스트 그룹 - 하나의 프리리스트 그룹은 독립적인 마스터 프리리스트와 프로세스 프리리스트를 가지며, 하나의 세그먼트가 여러 개의 프리리스트 그룹을 포함한다.
RAC에서는 노드별로 다른 프리리스트 그룹을 사용하는 식으로 활용 가능
프리리스트 그룹을 여러개 사용한다면 - 세그먼트 헤더 블록과 별개로 프리리스트 그룹 블록을 세그먼트에 부여 => 세그먼트 헤더 블록에 대한 경합 감소 효과

세그먼트 헤더 블록의 위치는 DBA_SEGMENTS뷰로부터
FLM방식 테이블의 세그먼트 헤더 블록을 덤프시 - > 마스터 프리리스트는 프리리스트를 이루는 머리블록(lhd) 꼬리블록(ltl)의 위치 값을 가지고 있다.
데이터 블록의 헤더 정보에서 flg의 값이 "0"이면 프리 블록, "-"이면 프리 블록이 아닌것


ASSM(Automatic Segment Space Management) - 자동 모드의 세그먼트 공간 관리
9i부터 추가된 기능 - 프리리스트 관련 속성 지정할 필요없이 오라클이 자동으로 관리해줌 -> 내부 메커니즘조차 상세히 알 필요없다는 코멘트
각 블록의 상태를 비트맵 값으로 관리
bmb : 비트맵 블록
프리 블록의 관리 뿐 아니라 HWM의 이동 또한 내부 알고리즘에 의해 자동으로 이루어짐 -> 직접 제어 불가
ASSM에서는 하나의 세그먼트에 대해 두개의 HWM을 관리한다. LHWM이하 블록은 모두 포맷된 상태, HHWM이상의 블록은 포맷되지 않은상태 , 둘 사이의값은 믹스
ASSM사용하면 죄다 알아서 관리해서 편리 - 동시에 많은 프로세스가 프리 블록 사용하며 비트맵 블록 자체의 정보를 변경시 비트맵 블록에 대한 buffer lock경합이 발생할 수도 있긴 하다~
이경우 buffer busy waits 대기 이벤트

 

I/O와 OWI
I/O 개요
I/O작업 레이어
1. 어플리케이션 레이어 : select/insert/update/delete/truncate
2. 오라클 메모리 레이어 : Buffer cache, PGA
3. 오라클 세그먼트 레이어 : Datafile, tempfile, Tablespace, Segmen
4. OS/디바이스 레이어 : Asynch I/O, Direct I/O, Raw device, RAID, . . .
I/O성능 문제 원인을 파악하려 할시에는 1 -> 2 -> 3 -> 4의 순서를 따라야함

어플리케이션 레이어 (Application Layer)
어플리케이션을 효과적으로 구현하여 불필요한 I/O를 최소화하자
-> Parallel Query, Parallel DML, Nologging, Direct load, Direct read, Analytical Function, Cluster, IOT, Partitioning, Bitmap Index

오라클 메모리 레이어 (Oracle Memory Layer)
버퍼 캐시를 활용함으로써 물리적 I/O를 줄여나간다
-> LRU 알고리즘, Buffer Pinning, Multiple buffer pool, 다중 블록 사이즈, direct path I/O

오라클 세그먼트 레이어 (Oracle Segment Layer)
-> 익스텐트 할당과 해제에 의한 ST락 경합, LMT ASSM 관리방식, 파티션 테이블

OS/디바이스 레이어 (Device Layer)
비동기 I/O사용을 권장
불가능하다면 OS차원에서 Direct I/O를 사용하도록 -> 이 경우 OS의 버퍼캐시를 거치지 않으므로 로디바이스와 작동 방식이 비슷하게된다.
어플리케이션이 비효율적 I/O를 유발한다면, 어플리케이션에 대한 튜닝이 우선시 하게 된다. 실제 I/O성능이 느릴때만 I/O시스템 성능 개선 시도를 하도록.
RAID사용 - RAID10 이나 RAID 0+1 권장
파일들을 물리적으로 분리시켜 디스크간의 경합을 줄이도록한다.


Direct path I/O
SGA를 거치지 않고 PGA에 데이터를 올린다.
언제쓰나
1. 정렬 작업 위해 정렬 세그먼트를 읽고 쓰는 경우. direct path read temp, direct path write temp 이벤트를 대기한다.
2. Paraller Query를 위해 데이터 파일을 읽는 경우 이 경우 direct path read 이벤트를 대기한다.
3. PDML이나 CTAS를 위해 데이터파일을 쓰는 경우 이 경우 direct path write 이벤트를 대기한다.
4. NOCACHE 속성으로 생성된 LOB 세그먼트를 읽고 쓰는 경우. direct path read(lob), direct path write(lob) 대기 이벤트
5. I/O 시스템이 데이터를 읽어서 오라클에 반환하는 속도보다 훨씬 빠른속도로 버퍼를 요구할 때. 이 경우 오라클 성능 개선을 위해 readahead I/O(이 후에 읽을 것으로 판단되는 데이터를 미리 한꺼번에 읽는 I/O 작업을 말함)를 이용한다. 이 경우 direct path read 이벤트를 대기한다.

Direct path I/O와 관련된 통계 값
physical reads : 디스크에서 읽은 블록 수. Direct path I/O 여부와 무관하게 물리적인 읽기 작업이 발생할 경우에는 항상 증가한다.
physical raeds direct : Direct path I/O를 통해 읽은 블록 수. LOB 데이터에 대한 Direct path I/O는 포함하지 않는다.
physical raeds direct(lob) : LOB 데이터를 Direct path I/O를 통해 읽는 블록 수
physical writes : 디스크에 기록한 블록 수. Direct path I/O 여부와 무관하게 물리적인 쓰기 작업이 발생할 경우에는 항상 증가한다.
physical writes direct : Direct path I/O를 통해 기록한 블록 수. LOB 데이터에 대한 Direct path I/O는 포함하지 않는다.
physical writes direct(lob) : LOB 데이터를 Direct path I/O를 통해 기록한 블록 수
sort(disk) : 디스크를 이용한 정렬 작업 회수. 디스크를 이용한 정렬 작업이 발생할 경우에는 정렬 세그먼트에 대해 Direct path I/O를 사용한다.
sort(memory) : 메모리를 이용한 정렬 작업 회수

물리적인 읽기 작업 중 버퍼 캐시를 경유한 읽기 작업(conventional path I/O) = physical reads (physical reads direct + physical reads direct (lob))

Direct path I/O에 성능 문제가 있다면 I/O시스템 성능과 직접적 관련이 있을 가능성 크다 -> I/O 자체의 성능을 개선시키는데 초점을 맞추도록.


리두와 OWI
리두 개요
리두 = 복구
Write ahead rule, Log force at commit 이라는 두가지 메커니즘을 통해 복구를 보장
Write ahead rule : 데이터를 변경시키기 전에 리두를 먼저 생성 -> DML시 버퍼 캐시 저장전, 리두 버퍼 저장부터함
Log force at commit : 커밋시 관련 리두 레코드를 리두 로그 파일에 저장해야지만 커밋이 종료된다

리두에 저장되는 변동 내용
- 언두 세그먼트 헤더, 언두 블록, 데이터 세그먼트 헤더, 데이터 블록
데이터에 대한 리두는 커밋된 데이터를 보호하고, 언두에 대한 리두는 롤백된 데이터를 보호한다

리두 생성하지 않는 방법
- Direct load operation (SQL*Loader, insert /* + append */)
- create table . . . , alter table . . . with nologging option
- create index . . . , alter index . . . with nologging option
- create materialized view . . . , alter materialized view . . . with nologging option
- Nocache + nologging 옵션의 LOB 데이터
이러한 작업도 딕셔너리 정보가 변경될 경우에는 해당 정보에 한해서 리두가 생성된다

임시테이블에 대해서는 리두가 생성되지 않는다 -> 언두데이터와 언두에대한 리두는 생성된다 : 인스턴스 장애에 대한 복구는 불필요하지만, 트랜잭션 단위의 복구는 해야 하므로

10000건 insert시 언두 리두 어떻게 생성되는가
영구 테이블에 대한 DML : 언두55/리두112생성됨
영구 테이블 + Direct load operaion : 언두1/리두7 생성됨 -> 실제로 생성되지 않은 것
임시 테이블에 대한 DML : 언두55/리두57 -> 언두생성 + 언두에대한 리두만 생성된 것


리두 버퍼(Redo Buffer)
데이터를 변동하는 모든 프로세스는 리두 버퍼에 변동사항을 기록해야한다
오라클은 리두 레코드라는 단위로 데이터의 변동사항을 저장한다 -> 리두 버퍼는 리두 레코드를 연속된 형태로 저장 -> 버퍼가 꽉 차면 처음부터 레코드를 순환하여 저장
리두 레코드를 리두 버퍼에 저장하는 과정
1. 데이터 변동사항을 PGA에 체인지 벡터로 -> 체인지 벡터들은 리두 레코드 포맷으로 리두 버퍼에 복사됨 -> 이 과정에 redo copy래치 획득 필요. 경합시 latch: redo copy 대기 이벤트 -> redo copy 래치는 복사 작업 전 과정동안 보유하고 있어야한다.
2. 자신의 SCN이 모든 프로세스가 지닌 SCN중 가장 높은 SCN인지 확인 -> 리두는 SCN순으로 리두 레코드를 쌓기 때문에 SCN 순서가 보장되야함
3. redo allocation 래치 획득(경합시 latch: redo allocation 대기 이벤트) -> 리두 버퍼 내에 리두 레코드를 저장할 공간 확보 -> 공간이 부족하다면 redo writing 래치를 획득하고 LGWR이 리두 버퍼 내려써서 공간 확보하도록 함 (공간이 확보될 때까지 log buffer space 대기 이벤트) -> 리두 로그 파일도 꽉 차서 로그 스위치가 발생하는 경우에는 log file switch completion 대기 이벤트 -> 공간이 확보되면 redo allocation 래치를 해제한다.
4. PGA에 생성한 체인지 벡터를 리두 레코드 형태로 리두 버퍼에 복사 -> 복사를 시작하는 시점에 로그파일이 꽉 차면 로그 스위치 되는 동안 log file switch completion 대기 이벤트. 이 과정이 끝나면 redo copy 래치를 해지한다. 리두 레코드가 리두 버퍼에 기록될 때마다 redo entries 통계값이 1 증가한다.

9i부터 shared redo strands -> 리두 버퍼의 영역을 일정 개수로 분할해서 사용 -> _LOG_PARALLELISM 값 이용 -> 오라클은 CPU/8정도를 권장 -> 개별 strand마다 개별 redo allocation 래치사용 -> 경합 감소
10g부터 shared redo strands 오라클이 동적 관리 -> _LOG_PARALLELISM_DYNAMIC=TRUE
10g부터 private redo strands -> 프로세스가 생성한 리두 데이터를 PGA아닌 Shared Pool의 private strands 영역에 저장함 -> 이 영역에 저장된 로그 데이터는 리두 버퍼 거치지 않고 바로 로그 파일에 기록됨 -> _LOG_PRIVATE_PARALLELISM=TRUE


리두 로그(Redo log)
3초 || 리두 버퍼의 1/3 또는 1MB가 찼을때 || 사용자가 커밋 또는 롤백을 수행할 때 || DBWR이 LGWR에게 쓰기 요청을 할때(Write ahead rule) -> 이 시점에 LGWR이 로그 버퍼를 리두 로그 파일에 기록한다.
리두로그파일은 리두로그블록 단위로 기록됨 - 이 크기는 OS마다 다름
동시에 여러 세션에서 커밋 요청이 오거나 PL/SQL블록에서 반복적으로 커밋을 수행하는 경우 커밋을 모아서 동시에 그룹 커밋을 하게된다.

리두로그 관련 통계값
user commits : 사용자가 수행한 커밋 회수
user rollbacks : 사용자가 수행한 롤백 회수
redo synch writes : 커밋 또는 롤백에 의해 수행된 리두 기록의 회수. 백그라운드 프로세스들에 의해서도 많은 수의 커밋이 이루어지기 때문에 시스템 레벨에서는 user commits < redo synch writes의 분포를 보이는 것이 일반적. 그룹 컴밋이 이루어 지는 경우에 user commits값은 커밋을 요청한 회수만큼 증가하지만 redo synch writes는 한번만 증가하므로 이 값을 해석할때 주의가 필요하다.
redo synch time : redo synch writes에 의해 소요된 시간 (1/100초)
redo writes : LGWR이 리두 기록을 수행한 전체 회수
redo size : 시스템에서 생성된 리두 데이터의 크기
redo blocks written : 리두 로그 파일에 기록된 리두 로그 블록의 수
redo entries : 리두 엔트리가 리두 버퍼에 복사된 회수
redo log space requests : 프로세스가 로그 버퍼에 리두 레코드를 기록하여는 시점에 로그 파일이 꽉차서 로그 스위치를 요청한 회수
redo log space wait time : redo log space requests에 의해 소요된 시간(1/100초)

전체 크기의 1/3 또는 1MB 만큼 쓰여질때 자동으로 기록 -> 백그라운드 기록이라 부른다 -> 기준은 _LOG_IO_SIZE 파라미터에 의해 결정됨