DB/Oracle

[오라클 성능 고도화] 1. 아키텍처(2)

OIMKHOT 2022. 2. 21. 15:19

 Redo

 오라클은 Datafile과 Control file의 모든 변경사항을 Redo 로그에 기록한다. (Datafile의 변경 사항은 캐시된 블록 버퍼를 통함) Redo 로그는 "Online Redo""archived Redo" 로그로 구성되어 있다.

 

 Online Redo 로그는 리두 로그 버퍼에 버퍼링된 로그 엔트리를 기록하는 파일로 최소 두개 이상의 파일로 구성된다. 현재 Redo 로그 파일이 꽉 차면 다음 Redo 로그 파일로 로그 스위칭(log switching)이 발생하고, 이를 반복하는 라운드 로빈 방식을 사용한다.

 

 Archived Redo는 재사용되기 전(로그 스위칭 전) 리두 로그를 백업해둔 파일을 말한다.

 

Redo의 목적은 다음 세 가지이다.

 

 Database Recovery

② Cache Recovery (Instance Recovery 시 roll forward 시)

③ Fast Commit

 

 Database Recovery (Media Recovery) 

 물리적으로 디스크가 깨지는 Media Fail 시 Archived Redo 로그를 사용한 복구를 위해 사용된다. 

 

② Cache Recovery (Instance Recovery) 

 캐싱된 변경사항이 기록되지 않은 상태에서 정전과 같은 사고로 휘발될 경우(Instance Crash) 유실에 대비하기 위해 Redo를 사용한다. 시스템을 재기동하면 Commit 여부와 상관없이 셧다운 이전 상태로 되돌리기 위해 Online Redo로그를 읽어내, 마지막 체크포인트 이후부터 직전 트랜잭션까지 재현하는 roll forward를 수행한다.(Crash Recovery)

 

 이후 Undo 데이터를 이용해 커밋되지 않은 트랜잭션을 롤백하는 'Transaction Recovery'가 수행되면 커밋된 데이터를 제외하고는 제거되어 완전 동기화 상태가 된다.

 

③ Fast Commit

 Dirty 버퍼를 디스크에 기록하는 작업은 Random 액세스 방식으로 매우 느린 반면, 로그는 Append 방식으로 기록하기 때문에 매우 빠르다. 트랜잭션이 발생할 때 마다 디스크에 기록하는 것이 아니라 Append 방식으로 로그 파일에만 기록하고, 메모리와 디스크 간 블록의 동기화는 적절한 수단(DBWR, Checkpoint)을 이용에 Batch 방식으로 일괄 수행하는 것을 의미한다.

 

 즉, 디스크에는 실제로 기록되지 않았지만, Redo 로그 메커니즘을 믿고 커밋 완료 판단을 내리는 것을 Fast Commit이라 한다. Instance Crash가 발생하더라도 Redo를 이용한 Recovery가 가능하기 때문이다.

 

 Fast Commit은 오라클만의 기능은 아니며 OLTP 환경을 염두에 둔 DBMS의 공통 메커니즘이다. 오라클만의 특징적인 기능은 'Delayed 블록 클린아웃' 이다. 

 

 오라클은 로우 Lock을 다른 DBMS처럼 Lock 매니저와 같은 프로세스를 통해 관리하지 않고 레코드의 속성을 변환시킴으로써 Lock을 구현하는데, 이러한 방식 때문에 커밋을 위해 Lock 해제를 위해서는 일일이 갱신된 블록을 찾아다녀야 한다.

 

 따라서 Redo에 Append 방식으로 기록한다 하더라도 빠르게 커밋을 처리할 수 없기 때문에 커밋 시점에 Undo 세그먼트 헤더의 트랜잭션 테이블에만 커밋 정보를 기록하고, 실제 블록 클린아웃(Dirty 블록 커밋 정보를 기록하고, Lock을 해제하는 작업)은 나중에 수행하도록 한다.

 

 Redo 레코드는 Redo 로그 버퍼(메모리)에 먼저 기록되고 일정 시점마다 LGWR 프로세스가 Redo로그 버퍼의 내용을 Redo 로그 파일에 기록한다. 여기서 일정 시점은 다음과 같다.

 

 3초마다 DBWR 프로세스의 신호를 받을 때

  DBWR는 Dirty 버퍼를 기록하기 전 로그 버퍼를 Redo 로그 파일에 기록하도록 LGWR에게 신호를 보낸다.

  (Instance Crash가 일어날 경우 Redo 로그를 모두 반영하여 Recovery를 수행하는데, 만약 Redo에 기록되지 않은 채로 디스크에만 쓰이게 되면 복구된 커밋되지 않은 트랜잭션이 커밋된 결과가 되기 때문이다.)

② 로그 버퍼의 1/3이 차거나 Redo 레코드량이 1MB를 넘을 때

③ 커밋 또는 롤백 시

 

 Fast Commit의 핵심 메커니즘은 인데, 커밋 시 Redo 정보가 디스크 상에 저장됐음을 확인되어야 한다(Log Force at Commit).

 는 대량 트랜잭션 처리 시 한꺼번에 메모리에서 디스크로 쓰기 작업량이 몰리는 것을 대비해 주기적인 Dirty 버퍼 해소를 위해 구현된 부차적 기능이다. (작업량이 몰리면 Fast Commit 조차 느려질 수 있다.)

 

Fast Commit은 다음과 같은 순서로 이루어진다.

 

Commit → <1> [ Redo Log Buffer에 Commit 레코드 기록 ] → <2> [ Online Redo log에 저장(LGWR) ]
→ Commit 완료 반환 → 디스크에 기록(DBWR) → Redo log 백업(Archive)

 <1> 버퍼 캐시의 블록 버퍼 갱신 전 먼저 Redo 로그 버퍼에 기록해야 하고, <2> DBWR가 버퍼 캐시의 Dirty 블록을 디스크에 기록 전 LGWR가 Rodo 로그 파일에 모두 기록이 되어야 한다. 이를 'Write Agead Logging'이라 한다.

 

 <2>까지 이루어 지면, Instance Crash가 발생하더라도 Redo 로그로 현재 상태까지의 Recovery를 보장하기 때문이다.

 

* log file sync wait event

 LGWR 프로세스가 로그 버퍼를 Redo 로그 파일에 기록할 때까지 서버 프로세스가 대기 중

 

 

 


 Undo

 Undo 세그먼트는 구조적으로 데이터를 저장하는 일반 테이블 세그먼트와 다르지 않다. Extent 단위로 확장되며, 빠른 R/W를 위해 Undo 블록을 버퍼 캐시에 캐싱하고, Recovery를 위해 Redo 로그에 로깅하는 같은 메커니즘으로 관리된다.

 

 세그먼트에 저장되는 내용이 다른데, 각 트랜잭션 별로 Undo 세그먼트를 할당해 주고(두 개 이상 트랜잭션이 하나의 Undo 세그먼트 공유 가능) 테이블과 인덱스에 변경사항이 발생하면 Undo 레코드 단위로 블록에 기록한다.

 

 Undo의 목적은 다음 세 가지이다.

 

 Transaction Rollback

Transaction Recovery (Instance Recovery 시 rollback 단계, Redo의 roll-forward와 구분하자)

 Read Consistency (읽기 일관성)

 

① Transaction Rollback

 롤백 시 사용한다.

 

② Transaction Recovery 

 Instance Crash 발생 후 Redo를 이용해 roll forward가 완료되면, 커밋되지 않은 데이터까지 모두 복구되는데, 커밋되지 않은 트랜잭션을 롤백하기 위해 Undo를 사용한다.

(Redo roll forward(커밋되지 않은 데이터까지 모두 Recovery) → Undo rollback (커밋되지 않은 데이터 rollback))

 

③ Read Consistency (읽기 일관성)

 튜닝에 있어 중요한 요소이므로 다음 절에서 계속 서술한다.

 

 

(1) Undo 세그먼트 트랜잭션 테이블 슬롯

 Undo 세그먼트 중 첫 번째 익스텐트, 그중 첫 번째 블록에는 Undo 세그먼트 헤더 정보가 있다.

헤더에는 '트랜잭션 테이블 슬롯'이 있고 각 슬롯은 트랜잭션 정보와 Last UBA(Undo Block Address) 등으로 구성되어 있다. 트랜잭션을 시작하려면 Undo 세그먼트에 있는 트랜잭션 테이블로부터 슬롯을 할당받아야 하고, 할당받은 슬롯에 정보를 갱신한다. 슬롯을 받지 못해 대기하면 'undo segment tx slot' 대기 이벤트가 발생한다.

 

 이제 발생하는 데이터나 인덱스 블록에 대한 변경사항은 Undo 블록에 다음과 같은 정보를 Undo 레코드로 차례대로 기록된다. 

Insert : 추가된 레코드의 rowid
Update : 변경되는 컬럼에 대한 befor image
Delete : 지워지는 로우의 모든 컬럼에 대한 berfor image

 Last UBA는 트랜잭션의 기록사항들이 가장 마지막 Undo 레코드에 추가되도록 하는 포인터 역할이다. Undo 레코드는 체인 형태로 연결돼 롤백 시 체인을 따라 거슬러 올라가며 작업을 수행한다. 

 

 인덱스가 없다면 Undo 레코드는 하나 씩 증가하지만, 있다면 인덱스 엔트리에 대한 갱신까지 포함되어 별도의 Undo 레코드가 추가로 증가된다. Insert, delete시 하나씩, update시 두 개의 레코드가 추가된다(update는 내부적으로 insert & delete로 처리).

 

 커밋되지 않으면 슬롯에는 트랜잭션의 상태가 Active로 표시되어 해당하는 Undo 블록과 트랜잭션 테이블 슬롯이 재사용되지 않지만, 커밋되면 Active에서 'committed'로 변경해 그 시점의 커밋 SCN를 트랜잭션 슬롯에 저장하고, 재사용 가능한 상태가 된다.

 

 Undo는 순차적으로 재사용(in a circular fashion)되기 때문에 커밋 후에도 상당기간 남게된다.

 

(2) 블록 헤더 ITL슬롯

 Undo 세그먼트 헤더에 트랜잭션 테이블 슬롯이 있듯, 각 데이터, 인덱스 블록에는 ITL(Interested Transaction List) 슬롯이 있다. 트랜잭션 시작을 위해 트랜잭션 테이블 슬롯을 확보하듯이, 블록에 속한 레코드 갱신을 위해서는 ITL 슬롯을 확보하고 현재 트랜잭션 ID를 기록하고 Active 상태임을 표시해야 한다. 할당받지 못하면 블로킹되었다가, 선행 트랜잭션이 커밋 또는 롤백되면 작업을 진행할 수 있게 된다.

 

 ITL 슬롯 부족을 최소화하도록 테이블이나 인덱스를 생성할 때 initrans(ITL 슬롯 할당 개수), maxtrans(모두 사용 시 해당 값만큼 슬롯 확장), pctfree(테이블 update, 인덱스 insert와 같이 레코드 추가를 대비하여 블록에 공간을 남겨놓음) 파라미터를 지정한다. ITL슬롯 부족 시 enq: TX - allocate ITL entry 대기 이벤트가 발생한다.

 

(3) Lock Byte

 레코드가 저장되는 Low 마다 헤더에 Lock Byte를 할당해 갱신 중의 트랜잭션의 ITL 슬롯 번호를 기록해둔다. (row-level Lock)

 트랜잭션 Lock (TX Lock)과 조합하여 로우 Lock을 구현한다.

-- 레코드 갱신 과정 --
 레코드 갱신 시도 → 레코드 Lock Byte 확인(Low) → 사용 중일 경우 ITL 슬롯 확인(블록 헤더)
→ 트랜잭션 테이블 슬롯 확인(Undo 세그먼트 헤더) → active일 경우 TX Lock

  low-level Lock은 오라클의 특징이다.