데이터베이스 내의 다양한 데이터 항목들은 수없이 다양한 저장 매체에 저장되고 액세스 된다. 저장 매체는 다음 3개의 카테고리로 분류할 수 있다.
이 중에서 안정 저장 매체는 복구 알고리즘에서 중요한 역할을 수행한다.
안정 저장 장치를 구현하기 위해서는 먼저 필요한 정보를 여러 비휘발성 저장 매체(주로 디스크)에 중복해 저장해야 한다. 또 필요로 하는 정보가 데이터 전송 실패로 인해 손상되지 않는 것을 보장하기 위해 통제된 방법으로 데이터를 갱신해야 한다.
RAID 시스템은 데이터 전송 중에 한 디스크의 실패가 발생해도 데이터의 손실이 발생하지 않도록 보장해준다. 그러나 RAID 시스템은 화재나 홍수 같은 재난으로 발생하는 데이터 손실은 보호해 줄 수 없다. 이러한 재난에 대비하여 별도의 장소에 보관용 백업 테이프를 저장해둔다. 그러나 테이프를 쉬지 않고 계속해서 별도의 장소로 이동할 수 없기 때문에, 가장 최근 테이프를 이동한 이후 발생한 갱신 내용은 그런 재난으로 잃어버릴 수도 있다.
좀더 안전한 시스템은 로컬 디스크 시스템에 블록을 저장함과 동시에 각 블록의 사본을 원격 사이트에 보관하고 컴퓨터 네트워크를 통해 갱신하는 방식의 시스템이다. 블록은 로컬 저장 장치에 기록되는 동시에 원격 시스템에 기록되기 때문에 한 번 기록 연산이 종료되면 재난이 발생해도 결과가 보존된다.
메모리와 디스크 저장 장치 사이의 블록 전송 결과는 다음과 같다.
데이터 전송 실패(data transfer failure)가 발생하면 시스템은 이를 탐지하고 복구 프로시저를 호출해 블록을 일관성 있는 상태로 되돌려야 한다. 이를 위해 시스템은 각 논리적 데이터베이스 블록에 대해 두 개의 물리적 블록을 유지해야 한다. 기록 연산은 아래와 같이 실행된다.
블록이 기록되는 도중 시스템 실패가 발생하는 경우 두 블록의 내용이 일치하지 않는 상황이 발생할 수 있다. 복구 시에는 각 블록에 대해 두 개 사본을 모두 검사해야 한다.
복구 중에 모든 대응되는 블록의 쌍을 비교하는 것은 비용이 매우 많이 드는 일이다. 이 비용을 작은 양의 비휘발성 RAM을 사용해 어느 블록에 기록 중이었는지 저장해둠으로써 크게 감소시킬 수 있다. 복구 시에는 오직 기록 작업이 진행되었던 블록만 비교하면 된다.
데이터베이스 시스템은 비휘발성 저장 장치(보통 디스크)에 저장되며 전체 데이터베이스의 일부분만 메모리에 상주한다. 데이터베이스는 블록(block)이라 불리는 고정 길이 저장 단위로 분할된다. 블록은 디스크 사이의 데이터 전송 단위이며 여러 개의 데이터 항목을 가진다.
트랜잭션은 디스크에서 메인 메모리로 정보를 입력하며 다시 그 정보를 디스크에 기록한다. 입력과 기록의 연산은 블록 단위로 이뤄진다. 디스크 상의 블록을 물리적 블록(physical block)이라고 하고 메인 메모리에 임시적으로 있는 블록을 버퍼 블록(buffer block)이라고 한다. 블록이 임시로 있게 되는 메모리 영역을 디스크 버퍼(disk buffer)라고 부른다.
메인 메모리와 디스크 사이의 블록 이동은 아래 두 가지 연산에 의해 발생한다.
개념적으로, 각 트랜잭션 는 자신이 액세스하고 갱신할 모든 데이터 항목들의 사본이 저장될 개인 작업 영역을 가진다. 트랜잭션이 초기화될 때 이 작업 영역의 시스템에 의해 만들어지며 트랜잭션이 커밋되거나 취소될 때 제거된다. 트랜잭션 의 작업 영역 안에 있는 데이터 항목 X를 라고 하자. 트랜잭션 는 자신의 작업 영역과 시스템 버퍼 사이에 데이터를 주고받음으로써 데이터베이스 시스템과 상호 작용한다. 다음 두 가지 연산을 통해 데이터를 전송한다.
두 연산 모두 디스크로부터 메인 메모리로 블록 전송을 요구하지만 메인 메모리로부터 디스크로의 블록 전송을 요구하지는 않는다.
하지만 버퍼 관리자가 다른 목적으로 인해 메인 메모리의 공간을 필요로 하거나 데이터베이스 시스템이 갱신 내용을 디스크 상의 B에 반영하기를 원할 때 버퍼 블록은 결국 디스크에 기록된다. 데이터베이스 시스템이 버퍼 B에 ouput(B)를 실행하는 것을 강제 기록(force-output)이라고 한다.
트랜잭션이 데이터 항목 X에 처음으로 액세스하고자 할 때 반드시 read(X)를 실행해야 한다. 그런 다음 시스템은 X에 대한 모든 갱신을 에 수행한다. 트랜잭션은 X의 변경 사항을 데이터베이스에 반영하기 위해 언제든지 write(X)를 실행할 수 있다. 마지막 갱신 작업이 끝난 후에는 반드시 write(X)를 수행해야 한다.
X가 위치한 블록 버퍼 에 대한 연산 output()는 write(X)가 실행된 후 즉각적으로 실행될 필요는 없다. 왜냐하면 X가 위치하는 가 현재 아직 실행 중인 데이터 항목들을 포함하고 있을 수도 있기 때문이다. 따라서 실제적인 기록은 이후에 일어난다. 그러므로 만약 write(X) 연산이 실행되었지만 아직 output()가 실행되기 전에 시스템 실패가 일어나면 X의 새로운 값은 디스크에 기록되지 않았고 따라서 갱신 내용을 잃게 된다.
초기 값이 각각 $1000와 $2000인 A계좌와 B계좌 간에 A에서 B로 $50의 이체가 있는 트랜잭션 를 생각해보자. 트랜잭션 를 실행하는 도중 output()는 실행되고 output()가 실행되기 전에 시스템 오류가 발생했다고 가정하자. 메모리의 내용이 손실되었기 때문에 트랜잭션의 상황을 알 수는 없다.
시스템이 재시작된 후는 트랜잭션 의 원자성이 깨진 상태이다. 데이터베이스의 상태를 조사해 장애 이전에 어떤 블록이 출력되었으며 어떤 블록이 그렇지 못했는지 알아낼 방법이 없다.
에 의해 행해진 데이터베이스 변경 내용은 모두 적용되던지 아니면 전혀 변경되지 않던지 둘 중 하나여야 한다. 그러나 가 데이터베이스에 다수의 변경 사항을 적용하는 경우 여러 개의 기록 연산이 필요할 것이며 이들 변경 사항 중 일부는 이미 변경되었고 일부는 오류로 인해 반영되지 못했을 것이다.
원자성을 보장하기 위해서는 먼저 데이터에비스 자체에 변경을 하기 전에 안정 저장 장치에 데이터베이스 변경과 연관된 정보를 기록해야 한다. 이 정보는 커밋된 트랜잭션에 의해 수행된 변경 사항이 데이터베이스에 모두 반영되는 것을 보장해준다. 또한, 이 정보는 트랜잭션의 변경 사항이 데이터베이스에 반영되지 않도록 해준다.
로그는 로그 레코드의 시퀀스이며 데이터베이스의 모든 갱신 작업을 기록한다. 로그 레코드에는 몇 가지 종류가 있다. 갱신 로그 레코드(update log record)는 한 번의 데이터베이스 기록을 기술하며 아래의 필드를 갖는다.
트랜잭션이 기록 연산을 수행할 때마다 데이터베이스가 변경되기 전에 기록 연산을 위한 로그 레코드가 생성되고 로그에 추가되어야 한다. 일단 로그 레코드가 존재하면 바로 언제든 데이터베이스에 변경 내용을 기록할 수 있다. 또한 데이터베이스 상에 이미 기록된 변경 사항을 로그 레코드 상의 이전 값을 이용하여 취소(undo)할 수도 있다.
시스템과 디스크 실패로부터의 복구를 위해 로그 레코드를 사용하기 위해서는 로그가 안정 저장 장치에 있어야 한다. 이제부터 모든 로그 레코드는 생성되자마자 안정 저장 장치 상에 있는 로그의 마지막에 기록된다고 가정하자.
트랜잭션이 데이터베이스를 수정한다는 것은 트랜잭션이 디스크 버퍼 또는 디스크 자체를 갱신하는 것을 말한다.
모든 데이터베이스 변경은 반드시 로그 레코드를 생성한 후 수행되어야 하기 때문에 시스템은 데이터 항목의 old value와 new value를 사용할 수 있다. 이 덕분에 시스템은 undo와 redo 명령을 적절히 사용할 수 있다.
만일 동시성 제어 방법이 트랜잭션 이 변경한 데이터 항목 X를 이 커밋되기 전에 다른 트랜잭션 가 수정하는 것을 허용한다면 X의 값을 복구해 을 되돌릴 경우 의 수정사항이 사라지게 된다. 이와 같은 상황을 방지하기 위해 복구 알고리즘은 한 트랜잭션이 수정한 데이터 항목은 이 트랜잭션이 커밋되거나 취소되기 전까지는 다른 트랜잭션이 수정할 수 없도록 해야 한다. 이를 위해서는 수정하는 모든 데이터 항목에 대해 exclusive lock을 획득하고 트랜잭션이 커밋되기 전까지 lock을 해제하지 않아야 한다.
트랜잭션의 마지막 로그 레코드인 커밋 로그 레코드가 안정 저장 장치에 기록되면 트랜잭션이 커밋되었다고 한다. 이 시점에서는 이전의 모든 레코드들이 이미 안정 저장 장치에 기록되어 있다. 따라서 시스템 장애가 발생해도 트랜잭션에서 발생한 갱신 작업들을 다시 수행할 수 있는 충분한 정보를 갖게 된다. 만일 로그 레코드 <, commit>이 기록되기 전에 시스템이 실패했다면, 트랜잭션 는 롤백되어야 한다.
시스템 장애가 발생한 이후 시스템은 원자성을 보장하기 위해 로그를 통해 어떤 트랜잭션을 재실행하고 어떤 트랜잭션을 되돌릴지 결정한다.
시스템에 장애가 발생하면 로그를 참조하여 재실행되어야 할 트랜잭션과 취소되어야 할 트랜잭션을 결정해야 한다. 이 결정을 위해 원칙적으로는 로그 전체를 탐색해야 하지만 로그 전체를 탐색할 경우 다음과 같은 두 가지 문제가 있다.
이러한 종류의 부하를 줄이기 위해 checkpoint를 사용한다.
checkpoint가 실행되는 동안에는 버퍼 블록이나 로그 레코드를 기록하는 등의 어떠한 갱신도 허용되지 않는다.
정상 상황(즉, 시스템 장애로부터의 복구가 아닌)에서의 트랜잭션 롤백을 살펴보자. 트랜잭션 의 롤백은 다음과 같이 수행된다.
실패 이후에 데이터베이스 시스템이 재시작될 때 다음 두 가지 단계를 거쳐 복구가 이뤄진다.
redo 단계가 끝나면 undo-list는 미완료 트랜잭션, 즉 실패 이전에 커밋되거나 롤백을 완료하지 못한 트랜잭션을 갖게 된다.
undo 단계가 끝나면 정상적인 트랜잭션 처리를 다시 시작할 수 있다.
그림 16.5에는 정상 작업에서의 로그와 복구시의 작업 예제를 보여준다. 그림의 로그에서 시스템 실패 이전에 은 커밋되었고 트랜잭션 는 롤백되었다. 의 롤백 중에 데이터 항목 B의 값이 어떻게 복구되었는지, 또한 checkpoint 레코드에 active 트랜잭션인 과 이 있는 것을 볼 수 있다.
실패로부터 복구할 때에 redo 단계에서 시스템은 마지막 checkpoint 이후의 모든 작업을 재실행한다. 이 단계에서 undo-list는 과 으로 초기화된다. 은 commit 로그를 발견하게 되면 가장 먼저 제거되고 는 start 로그를 발견하는 때에 추가된다. 트랜잭션 는 abort 로그를 만나면 undo-list에서 제거되고 만 undo-list에 남게된다. undo 단계에서는 로그를 마지막부터 역방향으로 탐색한다. A를 갱신하는 로그를 발견하면 A의 이전 값이 복구되고 redo 전용 로그가 로그에 기록된다. 의 start레코드를 발견하면 의 abort 레코드가 추가된다. undo-list에 트랜잭션이 더 이상 없기 때문에 undo 단계가 끝나고 복구가 완료된다.
Database System Concepts 16장