복구와 원자성(Recovery and Atomicity)
failure에도 불구하고 원자성을 보장하기 위해서,
데이터베이스를 변경하기 전에 먼저 안정 저장 장치(stable storage)에 데이터베이스 변경과 관련된 정보를 기록해야 한다.
그중 가장 널리 사용하는 방법은 로그 기반 복구(log-based recovery)이다.
- 로그 기반 복구(log-based recovery)
로그(log)는 안정 저장 장치에 유지되고, 연속된 로그 레코드(log record)로 구성되며, 데이터베이스의 모든 갱신 작업을 기록한다.
트랜잭션 $T_{i}$가 시작할 때, 로그 레코드 <$T_{i}$, start>가 써진다(write).
트랜잭션 $T_{i}$가 write(X) 연산을 실행하기 전에, 로그 레코드 <$T_{i}, X, V_{1}, V_{2}$>가 써진다.
($V_{1}$: 쓰기 연산 전의 X의 값, $V_{2}$: X에 쓰일 값)
트랜잭션 $T_{i}$가 끝날 때, 로그 레코드 <$T_{i}$, commit>이 써진다.
- 데이터베이스 변경
1. 지연 갱신(deferred modification) 기법
트랜잭션이 commit되는 시점까지 데이터베이스 수정을 미룬다.
복구(recovering)를 단순화 시킨다.
모든 갱신한 데이터 항목에 대한 로컬 사본을 유지하는 데 오버헤드가 발생한다.
2. 즉시 갱신(immediate modification) 기법
트랜잭션이 수행되는 도중 데이터베이스를 수정한다.
갱신 로그 레코드는 데이터베이스 항목에 쓰이기 전에 쓰여야 한다.
갱신된 데이터 블록의 안정 저장 장치로의 출력은 트랜잭션이 commit 되기 전후로 어느 시간에나 발생할 수 있다.
블록이 출력되는 순서는 블록이 작성된 순서와 다를 수 있다.
- 동시성 제어와 복구
만일 동시성 제어 기법이 트랜잭션 $T_{1}$이 변경한 데이터 항목 X를 $T_{1}$이 commit 하기 전에 다른 트랜잭션 $T_{2}$가 변경하는 것을 허용한다고 하면,
$T_{1}$ 실행 취소를 위해 X를 이전 값으로 복구하면, $T_{2}$의 변경 사항이 사라진다.
이런 상황을 방지하기 위해서 복구 알고리즘은 한 트랜잭션이 수정한 데이터 항목은 그 트랜잭션이 commit 하거나 abort 되기 전까지는 다른 트랜잭션이 수정할 수 없도록 해야 한다.
이 요구 조건을 지키기 위해서 트랜잭션이 수정하는 모든 데이터 항목은 exclusive lock을 획득하고, 그 트랜잭션이 commit 하기 전까지 lock을 해제하지 않아야 한다. (strict two-phase locking)
- 트랜잭션 commit
트랜잭션의 마지막 로그 레코드인 commit 로그 레코드가 안정 저장 장치에 기록되면, 트랜잭션이 commit 했다고 한다.
트랜잭션에 의해 수행된 쓰기는 트랜잭션이 commit 할 때까지 버퍼에 있을 수 있으며, 나중에 출력될 수 있다.
- 로그를 이용한 트랜잭션 undo와 redo
로그 레코드 <$T_{i}, X, V_{1}, V_{2}$>의 undo는 이전 값 $V_{1}$을 X에 쓴다.
로그 레코드 <$T_{i}, X, V_{1}, V_{2}$>의 redo는 새로운 값 $V_{2}$을 X에 쓴다.
트랜잭션의 undo와 redo는 다음과 같다.
undo($T_{i}$):
트랜잭션 $T_{i}$가 갱신한 모든 데이터 항목을 이전 값으로 되돌린다.
데이터 항목 X가 이전 값으로 복원될 때마다, 로그 레코드 <$T_{i}, X, V$>가 기록된다.
트랜잭션의 undo 연산을 완료하면, 로그 레코드 <$T_{i}, abort$>가 기록된다.
redo($T_{i}$):
트랜잭션 $T_{i}$가 갱신한 모든 데이터 항목의 값을 새로운 값으로 설정한다.
redo 연산 시에는 로그 레코드가 기록되지 않는다.
failure에 의해서 복구가 발생할 때,
트랜잭션 $T_{i}$는 로그가 로그 레코드 <$T_{i}, start$>를 포함하지만 <$T_{i}, commit$>나 <$T_{i}, abort$>를 포함하지 않을 때 undo가 발생한다.
트랜잭션 $T_{i}$는 로그가 로그 레코드 <$T_{i}, start$>를 포함하고 <$T_{i}, commit$>나 <$T_{i}, abort$>를 포함할 때 redo가 발생한다.
예시)
(a)의 경우, <$T_{0}, start$>를 포함하지만 <$T_{0}, commit$>나 <$T_{0}, abort$>를 포함하지 않으므로 undo가 발생한다.
(b)의 경우, <$T_{0}, start$>를 포함하고 <$T_{0}, commit$>을 포함하므로 redo($T_{0}$)가 발생하고,
<$T_{1}, start$>를 포함하지만 <$T_{1}, commit$>나 <$T_{1}, abort$>를 포함하지 않으므로 undo($T_{1}$)가 발생한다.
(c)의 경우, <$T_{0}, start$>를 포함하고 <$T_{0}, commit$>을 포함하므로 redo($T_{0}$)가 발생하고,
<$T_{1}, start$>를 포함하고 <$T_{1}, commit$>을 포함하므로 redo($T_{1}$)가 발생한다.
- 검사점(checkpoint)
시스템에 failure가 발생하면 로그를 참조하여 redo 해야 할 트랜잭션과 undo 해야 할 트랜잭션을 결정해야 한다.
이를 위해서 로그 전체를 탐색해야 하지만 로그 전체를 탐색하는 것은 다음의 두 가지 문제를 발생시킨다.
1. 검색에 너무 많은 시간이 소비된다.
2. 이미 데이터베이스에 갱신한 트랜잭션을 불필요하게 redo 할 수 있다.
이러한 부하를 줄이기 위한 방법으로 검사점(checkpoint) 기법이 있다.
검사점 생성은 다음과 같이 이루어진다.
1. 현재 메인 메모리 상에 존재하는 모든 로그 레코드를 안정 저장 장치에 출력한다.
2. 변경된 모든 버퍼 블록(dirty block)을 디스크에 출력한다.
3. 로그 레코드 <checkpoint L>을 안정 저장 장치에 출력한다. 이때 L은 검사점을 생성하는 시점에 동작 중이던 트랜잭션의 목록이다.
검사점을 생성하는 동안 모든 갱신 작업은 허용되지 않는다.
검사점 기법을 사용한 복구는 다음과 같다.
시스템 failure가 발생한 후에 시스템은 로그에서 마지막 <checkpoint L> 레코드를 찾는다.
redo와 undo 연산은 L에 해당하는 트랜잭션과 <checkpoint L> 레코드 이후에 시작한 트랜잭션에 대해서 수행한다.
이러한 트랜잭션 집합을 T라고 하면,
1. T에 포함된 트랜잭션 $T_{k}$에 대해 로그 레코드 <$T_{k}, commit$> 또는 <$T_{k}, abort$>가 없다면, undo($T_{k}$)가 발생한다.
2. T에 포함된 트랜잭션 $T_{k}$에 대해 로그 레코드 <$T_{k}, commit$> 또는 <$T_{k}, abort$>가 있다면, redo($T_{k}$)가 발생한다.
'전공 > 데이터베이스' 카테고리의 다른 글
[데이터베이스] 동시성 제어 - (1) (0) | 2021.12.12 |
---|---|
[데이터베이스] 복구 시스템 - (3) (0) | 2021.12.12 |
[데이터베이스] 복구 시스템 - (1) (0) | 2021.12.06 |
[데이터베이스] 트랜잭션 - (2) (0) | 2021.12.06 |
[데이터베이스] 트랜잭션 - (1) (0) | 2021.12.06 |