9. Snapshot too old

조회 수 5618 추천 수 0 2013.08.21 12:33:43
balto *.204.128.206

(정의) Snapshot : 디스크에서 읽어온 데이터 블록의 버퍼캐시 블록에 대하여 갱신이 일어날 때 복사본으로(CR 블록)이며

최종블록 복사본은 Current 블록이라 한다.


(정의) Snapshot too old 발생원인

(1) 데이터를 읽어 내려가다가 쿼리 SCN이후에 변경된 블록을 만나(이미 앞서 읽었던 블록을 다시 방문하는 경우 일 수도 있음) 과거 시점으로 롤백한 'Read Consistent' 이미지를 얻으려고 하는데, UNDO 블록이 다른 트랜잭션에 의해 이미 재사용돼 필요한 Undo 정보를 얻을 수 없는 경우.

(2) 커밋된 트랜잭션 테이블 슬롯이 다른 트랜잭션에 의해 재사용돼 커밋 정보를 확인할 수 없는 경우.


▣ 시나리오

(1) UNDO 실패 시나리오

- 예제(트랜잭션 T1)

SELECT /*+ ordered use_nl(b) */ A.고객ID, NVL(SUM(과금액), 0) 과금액,

NVL(SUM(수납액), 0) 수납액, NVL(SUM(과금액),0)-NVL(SUM(수납액),0) 미납액

FROM 과금 A, 수납 B

WHERE A.과금년월 = :과금년월

AND A.과금유형 = :과금유형

AND A.고개ID = B.고객ID(+)

AND A.과금년월 = B.수납년월(+)

GROUP BY A.고객ID

① T1이 SCN 123시점에 시작

② T2에 의해 홍길동 고객의 수납액이 10,000원에서 20,000원으로 변경되고 나서 커밋되었다. 블록 SCN은 129로 변경

③ T3에 의해 홍길동 고객의 수납액 변경내역(수납액 10,000 - 20,000원)을 담은 UNDO블록이 다른 트랜잭션에 의해 재사용

④ T1이 홍길동 고객의 수납액이 담긴 블록에 도달했을 때, 블록 SCN이 자신의 쿼리 SCN 123보다 129임을 확인하고, 변경된 UNDO레코드를 찾으려고 ITL엔트리에 기록된 UBA를 읽어 UNDO세그먼트 블록을 찾아감

⑤ UNDO블록은 이미 다른 트랜잭션에 의해 재사용된 상태이므로 ORA-1555 에러를 발생시키며 진행을 멈춘다.


(2) 블록 클린아웃 실패

대량 업데이트 후에 커밋된 트랜잭션 T1은 변경했던 블록들을 모두 클린아웃하지 않은 상태에서 자신이 사용하던 트랜잭션 슬롯을 Free상태로 변경하고 트랜잭션을 완료 한다.

T2는 트랜잭션 테이블 블록을 재사용.

오라클이 Delayed 블록 크린 아웃을 시도

- 다행히 찾고자 하는 트랜잭션에 대한 커밋 정보가 UNDO블록에 남아있다면 현재 읽고자 하는 블록의 정확한 커밋 SCN을 가지고 블록 클린아웃을 수행할 수 있다.

- 하지만 UNDO 레코드를 뒤졌는데 그마저도 덮어 쓰이고 있는 상태라면 "Snapshot too old"에러가 발생. (거의 발생하지 않는다.)

오라클이 트랜잭션 슬롯이 필요해지면 커밋 SCN이 가장 낮은 트랜잭션 슬롯부터 재사용하고, 기록돼 있던 커밋 SCN을 UNDO세그먼트 헤더에 '최저 커밋 SCN'을 기록해 둔다.

트랜잭션 슬롯이 재사용되고 나면 그 슬롯을 사용하던 이전 트랜잭션의 정확한 커밋 SCN을 확인하는 것은 불가능해지지만 UNDO SEGMENT 헤더에 기록돼 있는 '최저 커밋 SCN' 이전에 커밋 되었다는 사실만큼은 짐작할 수 있으므로 이 SCN을 블록 ITL 엔트리에 커밋 SCN으로서 기록함으로써 클린아웃을 마무리 하고, 블록 SCN도 변경한다.

Delayed 블록 클린아웃에 의해 Snapshot too old가 발생하는 원인은, '최저 커밋 SCN'이 쿼리 SCN보다 높아질 정도로 갑자기 트랜잭션이 몰리는데 있다.


(3) Snapshot too old 회피 방법

과거버전에서는 UNDO 세그먼트를 수동으로 관리했으므로 Snapshot too old 에러 발생을 최소화하기 위해 System Application특성에 맞는 UNDO 튜닝을 DBA가 했음

9i부터 AUM(Automatic Undo Management)이 도입돼 UNDO segment 크기와 개수를 오라클이 동적으로 자동 조절해 주기 때문에 Snapshot too old 에러가 발생할 가능성도 줄었음

Snapshot too old에러 발생 가능성을 줄이기 위한 Application 측변에서의 솔루션

1. 불필요하게 커밋을 자주 수행하지 않는다.

2. fetch across commit 형태의 프로그램 작성을 피해 다른 방식으로 구현한다. ANSI 표준에 따르면 커밋 이전에 열려 있던 커서는 더는 Fetch하면 안 된다. 다른 방식으로 구현하기 어렵다면 커밋 횟수를 줄여본다.

3. 트랜잭션이 몰리는 시간대에 오래 걸리는 쿼리가 같이 수행되지 않도록 시간을 조정한다.

4. 큰 테이블을 일정 범위로 나누어 읽고 단계적으로 실행할 수 있도록 코딩한다. Snapshot too old 발생 가능성을 줄일 뿐 아니라 문제가 발생했을 때 특정 부분부터 다시 시작할 수도 있어 유리하다. 물론 그렇게 해도 읽기 일관성에 문제가 없을때에만 적용해야 한다.

5. 오랜 시간에 걸쳐 같은 블록을 여러번 방문하는 Nested Loop형태의 조인문 또는 인덱스를 경유한 테이블 액세스를 수반하는 프로그램이 있는지 체크하고, 이를 회피할 수 있는 방법(조인 메소드 변경, Full Table Scan등)을 찾는다.

6. 소트 부하를 감수하더라도 order by등을 강제로 삽입해 소트연산이 발생하도록 한다. 많은 데이터를 오랜 시간에 걸쳐 Fetch하는 동안 Undo 정보를 지속적으로 참조하기 때문에 문제가 발생하는 것이므로, 서버 내에서 빠르게 데이터를 읽어 Temp 세그먼트에 저장하는 데에만 성공하면 이후에는 같은 블록을 아무리 재방문하더라도 더는 에러가 발생할까 걱정하지 않아도 된다.

7. 만약 delayed 블록 클린아웃에 의해 Snapshot too old가 발생하는 것으로 의심되면 대량 업데이트 후에 곧바로 해당 테이블에 대해 Full Scan 하도록 쿼리를 날리는 것도 하나의 해결방법이 될 수 있다. 인덱스 블록에서 문제가 발생한다고 되면 인덱스 리프 블록을 모두 스캔하도록 쿼리한다.