본문 바로가기
스터디/오라클 성능고도화 원리와 해법1

CH1.오라클 아키텍처 - 8.블록 클린아웃, 9.Snapshot too old

by 취미툰 2019. 12. 21.
반응형

8.블록 클린아웃

블록 클린아웃트랜잭션에 의해 설정된 로우 lock을 해제하고 블록 헤더에 커밋정보를 기록하는 오퍼레이션입니다. 오라클에 로우 단위 lock은 레코드의 속성 (lock byte)로 관리되며 이는 로우 헤더로부터 블록 헤더에 있는 ITL 엔트리를 가리키는 포인터입니다. 사용자가 트랜잭션을 커밋하면 블록 클린아웃까지 완료해야 완전한 커밋이라고 할 수있는데, 일일이 블록을 찾아다니며 클린아웃을 수행하려면 시간이 오래 걸릴수 밖에 없습니다. 그래서 대량의 갱신 작업이 있고 나서는 커밋정보를 트랜잭션 테이블에만 기록하고 빠르게 커밋을 끝내버립니다. 그렇다면 언제 블록을 클린아웃 할까요? 나중에 해당 블록이 처음 액세스되는 시점입니다. 항상 이방식으로 작동하는 것은 아니며 delayed 블록 클린아웃과 커밋클린아웃(fast 블록 클린아웃) 두가지 매커니즘을 사용합니다.

(1)Delayed 블록 클린아웃
트랜잭션이 갱신한 블록개수가 총 버퍼 캐시 블록 개수의 1/10을 초과할때 사용하는 방식입니다. 커밋 이후 해당 블록을 액세스하는 첫 번째 쿼리에 의해 클린아웃이 이루어 지며 아래와 같은 작업을 수행합니다.
-ITL 슬롯에 커밋 정보 저장
-레코드에 기록된 Lock Byte 해제
-Online Redo에 Logging
블록을 읽는 과정에서 Active 상태의 블록, 즉 다른 트랜잭션이 발생시킨 변경사항에 대한 커밋정보가 아직 ITL 슬롯에 기록되지 않았다면 읽기전에 먼저 블록 클린아웃을 시도합니다. ITL 슬롯에 기록된 트랜잭션ID를 이용해 Undo 세그먼트 헤더에 있는 트랜잭션 테이블 슬롯을 찾아가 트랜잭션의 현재 상태를 확인하고 커밋된 트랜잭션이라면 이를 ITL슬롯에 반영하고 로우 Lock정보를 해제해 블록을 클린아웃시킵니다. 블록 클린아웃을 위한 갱신내용도 Redo에 로깅하며 블록 SCN도 변경합니다.

(2)커밋 클린아웃(fast 블록 클린아웃)
만약 모든 클린아웃을 Delayed 블록 클린아웃방식으로 처리한다면 select 시에 블록을 클린아웃하는 일이 빈번히 발생합니다. 블록 클린아웃도 쓰기 작업이므로 Current블록에 작업을 수행해야 합니다. RAC환경에서는 Exclusive모드의 Current블록을 요청하게 됩니다.
트랜잭션이 갱신한 블록 개수가 버퍼 캐시 블록 개수의 1/10을 초과하지 않을 때는 커밋 시점에 곧바로 블록 클린아웃을 수행합니다.(버퍼 캐시에서 밀려나면서 데이터파일에 기록되었거나, 다른 트랜잭션에 의해 사용 중인 블록은 놔둠으로써 Delayed 블록 클린아웃 방식을 따르도록 합니다) 다만, 이 경우에도 커밋시점에는 불완전한 형태의 클린아웃을 수행하며 해당 블록을 갱신하는 다음 트랜잭션에 의해 완전한 클린아웃이 이루어집니다.
즉, 커밋시점에는 ITL슬롯에 커밋정보만 저장하고 로우 헤더에 기록된 Lock Byte는 해제하지 않습니다. 커밋 시점에 이미 완전한 커밋 정보가 ITL슬롯에 기록되어 있기 때문에 이후 CR모드 읽기 시 커밋 여부와 커밋 SCN을 확인하려고 트랜잭션 테이블을 조회하지 않아도 되며, Lock Byte를 무시하고 그대로 블록을 읽습니다. Lock Byte를 해제하지 않고 그대로 두는 이유는 로깅을 수행하지 않기 위해서입니다. 로깅시점을 뒤로 미루는 것이고 그리고 나서 해당 블록을 갱신하려고 Current 모드로 읽는 시점에(ITL슬롯이 필요해지기 때문)비로소 Lock Byte를 해제하고 완전한 클린아웃을 수행합니다. 그리고 Online Redo에 내역을 로깅합니다.

9.Snapshot too old(ORA-01555)

두가지 상황으로 발생하는 것을 정리할 수 있습니다.
(1) Undo 실패
데이터를 읽어 내려가다가 쿼리 SCN이후에 변경된 블록을 만나 과거 시점으로 롤백한 Read consistent이미지를 얻으려고하는데, Undo 블록이 다른 트랜잭션에 의해 이미 재사용되어 필요한 Undo 정보를 얻을 수 없을때입니다. Undo 세그먼트가 너무 작다는 신호일 수 있습니다.

(2) 블록 클린아웃 실패
커밋된 트랜잭션 테이블 슬롯이 다른 트랜잭션에 의해 재사용되어 커밋 정보를 확인할 수 없는 경우입니다. Undo 세그먼트 개수가 적다는 신호일 수 있습니다.
대량의 업데이트 후에 커밋된 트랜잭션은 변경했던 블록들을 모두 클린아웃하지 않은 상태에서 자신이 사용하던 트랜잭션 테이블 슬롯을 Free상태로 변경하고 트랜잭션을 완료합니다. 이때부터 그 트랜잭션 테이블 슬롯은 다른 트랜잭션에 의해 재사용될 수 있습니다. 시간이 흘러 그 변경된 블록들이 읽혀야 하는 시점에 Delayed 블록 클린아웃을 위해 트랜잭션 테이블 슬롯을 찾아갔는데, 해당 슬롯이 다른 트랜잭션에 의해 이미 재사용되고 없다면 정상적인 블록 클린아웃과 일관성모드 읽기가 불가능해질 수 있습니다.
갱신 후 오랫동안 읽히지 않았던 블록이 언젠가 읽히면 예외없이 snapshot too old가 발생하지 않을까요?
아닙니다. 트랜잭션 슬롯이 필요해지면 커밋 SCN이 가장 낮은 트랜잭션 슬롯부터 재사용하는데 그 슬롯에 기록되어 있던 커밋 SCN은 undo 세그먼트 헤더에 최저 커밋 SCN으로서 기록해 둡니다. 트랜잭션 슬롯이 재사용되면 이전 트랜잭션의 정확한 커밋 SCN을 확인하는 것은 불가능하지만 최저 커밋 SCN이전에 커밋되었다는 사실을 알 수 있습니다. 따라서 아직 클린아웃되지 않은 블록을 클린아웃하려고 ITL슬롯이 가리키는 트랜잭션 테이블 슬롯을 찾아갔을 때 커밋정보가 이미 지워지고 없으면, undo 세그먼트 헤더에 있는 최저 커밋 SCN을 블록 ITL 엔트리에 커밋 SCN으로서 기록(추정된 커밋 SCN이며, ITL 슬롯 커밋 flag에 C-U-라고 기록됨)함으로써 블록 클린아웃을 마무리하고 블록 SCN도 변경합니다.
쿼리가 진행되는 시점동안에 많은 트랜잭션이 한꺼번에 몰려 최저 커밋 SCN이 갑자기 많이 증가하지만 않는다면 최저 커밋 SCN에 의해 추전된 블록 SCN은 대개 쿼리 SCN보가 작습니다. 쿼리 시작후 변경이 되지 않았다는 뜻이므로 일관성이 보장됩니다.
Delayed 블록 클린아웃에 의해 snapshot too old가 발생하는 원인최저 키밋 SCN이 쿼리 SCN보다 높아질 정도로 갑자기 트랜잭션이 몰리는데 있습니다. 실제로 이 에러를 발생시킨 블록은 훨씬 오래전 시점에 커밋된 것이거나 오랫동안 한번도 읽히지 않다가 불행히도 트랜잭션이 몰리는 시점에 읽힐때 문제가 발생합니다.

(3)Snapshot too old 회피 방법
DB관점에서는 AUM이 사용되는 9i부터는 undo를 자동으로 관리해주기떄문에 해당 에러가 발생할 가능성이 줄었습니다. DBA는 충분히 큰 Undo space를 준비해주는 것 정도입니다.
애플리케이션 관점에서는 아래와 같은 방법을 권장합니다.
1. 불필요하게 커밋을 자주 수행하지 않습니다.
2.fetch across commit 형태의 프로그램 작성을 피해 다른 방식으로 구현합니다.
3.트랜잭션이 몰리는 시간대에 오래 걸리는 쿼리가 같이 수행되지 않도록 시간을 조정합니다.
4.큰 테이블을 일정 범위로 나누어 읽고 단계적으로 실행할 수 있도록 코딩합니다.
5.오랜 시간에 걸쳐 같은 블록을 여러 번 방문하는 Nested Loop형태의 조인문 또는 인덱스를 경유한 테이블 액세스를 수반하는 프로그램이 있는지 체크하고 그것을 회피하는 방법을 찾습니다.
6. 소트 부하를 감수하더라도 order by 등을 강제삽입해 소트연산이 발생하도록 합니다. 많은 데이터를 오랜 시간에 걸쳐 fetch하는 동안 undo 정보를 지속적으로 참조하기 때문에 문제가 생기는 것이므로, 강제연산을 사용하여 undo 세그먼트보다 더 큰 작업을 만들어 Temp 테이블스페이스를 사용하여 그곳 세그먼트에 저장하게 하는것입니다. 그렇게되면 같은 블록을 아무리 재방문하더라도 더는 에러가 발생하지 않을 것입니다.
7. delayed 블록 클린아웃에 의해 snapshot too old가 발생하는것으로 의심되면 대량 업데이트 후에 곧바로 해당 테이블에 대해 full scan하도록 쿼리를 날리는 것도 좋습니다. 한번더 블록을 방문하여 클린아웃을 강제로 시키는것입니다.
만약, 인덱스 블록에서 문제가 발생한다고 판단되면인덱스 리프 블록을 모두 스캔하도록 쿼리합니다.


반응형

댓글