데이터베이스 장애와 복구, 데이터 접근 구조
Failure Classification(장애 분류)
데이터베이스 시스템에서 발생할 수 있는 장애(failure)는 크게 세 가지로 분류된다.
- Transaction failure(트랜잭션 장애)
- Logical errors: 트랜잭션 내부 오류로 인해 완료되지 못하는 경우
- System errors: 데드락(deadlock) 등 시스템이 트랜잭션을 강제로 종료해야 하는 경우
- System crash(시스템 크래시)
- 전원 장애, 하드웨어/소프트웨어 오류 등으로 시스템이 중단되는 현상
- Fail-stop assumption: 비휘발성 저장장치의 데이터는 시스템 크래시로 손상되지 않는다고 가정
- 데이터베이스 시스템은 무결성(integrity) 체크를 통해 데이터 손상 방지
- Disk failure(디스크 장애)
- 디스크 자체의 물리적 손상으로 일부 또는 전체 데이터가 소실
- 체크섬(checksum) 등으로 장애 감지
Recovery Algorithms(복구 알고리즘)
트랜잭션 Ti이 계좌 A에서 B로 $50을 이체한다고 가정하면,
- 두 가지 업데이트:
- A에서 50 차감
- B에 50 추가
트랜잭션의 업데이트는 반드시 데이터베이스에 기록되어야 한다.
- 장애가 발생하기 전, 두 업데이트 모두 반영되거나
- 장애 발생 시, 트랜잭션 전체가 무효화되어야 함(atomicity 보장)
복구 알고리즘의 두 단계
- 정상 트랜잭션 처리 중 장애 복구에 필요한 정보 기록
- 장애 발생 후, 데이터베이스를 일관성(consistency), 원자성(atomicity), 지속성(durability) 상태로 복구
Data Access(데이터 접근)
- Physical blocks: 디스크에 존재하는 블록
- Buffer blocks: 메인 메모리에 임시로 존재하는 블록
디스크와 메인 메모리 간 데이터 이동은 두 연산으로 수행된다.
- input(B): 디스크의 블록 B를 메인 메모리로 복사
- output(B): 메인 메모리의 버퍼 블록 B를 디스크에 저장(해당 물리 블록을 덮어씀)
트랜잭션 규칙
- read(X)는 최초 접근 시 반드시 수행(이후에는 로컬 복사본 사용)
- write(X)는 트랜잭션 커밋 전 언제든 실행 가능
Example of Data Access(데이터 접근 예시)
- Buffer Block A, Buffer Block B가 메모리와 디스크 사이에서 이동
- 각 트랜잭션 T1, T2는 자신의 work area(작업영역, private workspace, local buffer)를 가짐
- read(X): 데이터 X를 작업영역 x1에 복사
- write(Y): 작업영역의 y2 값을 버퍼 블록 Y에 반영
- output(B): 버퍼 블록 B를 디스크에 기록
Recovery and Atomicity(복구와 원자성)
Atomicity(원자성)를 보장하기 위해, 데이터베이스 시스템은 장애(failure)가 발생해도 트랜잭션의 모든 연산이 전부 반영되거나, 전혀 반영되지 않아야 한다.
이를 위해 데이터베이스를 직접 수정하지 않고, 먼저 변경 내용을 안정 저장장치(stable storage)에 기록하는 방식이 사용된다.
Log-based Recovery Mechanisms(로그 기반 복구 메커니즘)
- 데이터베이스 시스템은 log-based recovery mechanism(로그 기반 복구 메커니즘)을 주로 사용한다.
- 트랜잭션의 변경 사항을 로그(log)에 먼저 기록한 뒤, 실제 데이터베이스에 반영한다.
- 장애 발생 시, 로그를 이용해 데이터베이스를 일관된 상태로 복구할 수 있다.
- 로그 기반 복구의 핵심 개념과 실제 복구 알고리즘은 이후에 상세히 다룬다.
Shadow-copy, Shadow-paging(섀도우 카피, 섀도우 페이징)
- Shadow-copy와 shadow-paging은 로그 기반 복구의 대안으로 제안된 방식이다.
- 이 방식들은 실제 데이터베이스를 직접 수정하지 않고, 데이터베이스의 복사본(shadow copy)을 만들어 새로운 변경 내용을 반영한다.
Shadow-copy 방식 예시 (copy on write)
- (a) 업데이트 전: db-pointer가 기존 데이터베이스(Old copy of database)를 가리킨다.
- (b) 업데이트 후: 새로운 데이터베이스 복사본(New copy of database)을 생성하고, db-pointer가 이 복사본을 가리키도록 변경한다. 기존 데이터베이스는 더 이상 사용하지 않으므로 삭제될 수 있다.
Shadow Paging(섀도우 페이징)
Shadow paging은 log-based recovery(로그 기반 복구)의 대안으로, 트랜잭션이 직렬(serial)로 실행되는 환경에서 유용하다.
- 트랜잭션 실행 동안 current page table(현재 페이지 테이블)과 shadow page table(섀도우 페이지 테이블) 두 개를 유지한다.
- shadow page table은 비휘발성 저장장치에 저장되어, 트랜잭션 실행 전 데이터베이스 상태를 복구할 수 있다.
- shadow page table은 트랜잭션 실행 중 절대 변경되지 않는다.
- 트랜잭션 시작 시 두 테이블은 동일하다. 실행 중에는 current page table만 데이터 접근에 사용된다.
페이지 갱신 방식
- 어떤 페이지를 처음으로 수정하려 할 때,
- 해당 페이지를 새로운 미사용 블록에 복사
- current page table이 이 복사본을 가리키도록 변경
- 실제 업데이트는 복사본에서 수행
쉐도우 페이징 예시
- sample page table(샘플 페이지 테이블) 그림에서, shadow page table과 current page table이 같은 페이지들을 가리킨다.
- page 4를 수정하면, current page table만 새로운 블록을 가리키게 되고, shadow page table은 기존 블록을 그대로 가리킨다.
트랜잭션 커밋 절차
- 메인 메모리의 모든 변경된 페이지를 디스크에 기록(flush)
- current page table을 디스크에 기록
- shadow page table 포인터를 current page table로 갱신
- 디스크의 고정 위치에 shadow page table 포인터를 저장
- 포인터만 갱신하면 트랜잭션 커밋 완료
- 장애 발생 시 별도의 복구 과정 없이, shadow page table을 이용해 즉시 새로운 트랜잭션 실행 가능
- current/shadow page table에서 참조되지 않는 페이지는 가비지 컬렉션(garbage collection) 필요
장점과 단점
장점
- 로그 기록이 필요 없으므로 log-based scheme 대비 오버헤드가 적다
- 복구 과정이 매우 단순하다
단점
- 페이지 테이블 전체를 복사하는 비용이 매우 크다
- 페이지와 페이지 테이블을 모두 디스크에 flush해야 하므로 커밋 오버헤드가 높다
- 데이터가 단편화(fragmentation)된다(수정된 페이지가 디스크에 흩어짐)
- 트랜잭션 종료마다 이전 버전의 데이터가 남아 가비지 컬렉션 필요
- 동시성(concurrency) 지원이 어렵고, 확장성이 떨어진다
Log-Based Recovery(로그 기반 복구)
Log-based recovery는 데이터베이스의 트랜잭션 처리에서 장애 발생 시 데이터 일관성과 원자성(atomicity)을 보장하기 위한 핵심 메커니즘이다.
로그(log)는 데이터베이스의 모든 변경 내역을 기록하는 로그 레코드(log record)들의 시퀀스(sequence)이다.
로그는 항상 안정 저장장치(stable storage)에 보관된다.
로그 기록 방식
- 트랜잭션 Ti가 시작하면, <Ti, start> 로그 레코드를 남긴다.
- Ti가 write(X) 연산을 수행하기 전에 <Ti, X, V1, V2> 형태의 로그 레코드를 기록한다. 여기서 V1은 X의 이전 값(old value), V2 는 X에 쓸 새로운 값(new value)이다.
- 트랜잭션이 종료되면 <Ti commit> 로그 레코드를 기록한다.
로그 기반 복구의 두 가지 방식
- Immediate database modification(즉시 데이터베이스 수정)
- 커밋되지 않은 트랜잭션의 변경 내용도 버퍼나 디스크에 바로 기록할 수 있다.
- 단, 반드시 해당 update log record가 디스크에 먼저 기록되어야 한다.
- 데이터 블록의 디스크 기록 시점은 트랜잭션 커밋 전/후 모두 가능하다.
- Deferred database modification(지연 데이터베이스 수정)
- 트랜잭션 커밋 시점에만 변경 내용을 버퍼/디스크에 반영한다.
- 복구가 단순해지지만, 로컬 복사본 유지 오버헤드가 있다.
Transaction Commit(트랜잭션 커밋)
- 트랜잭션의 commit log record가 안정 저장장치에 기록되어야 트랜잭션이 커밋된 것으로 간주한다.
- 트랜잭션의 모든 로그 레코드는 커밋 로그 이전에 반드시 기록되어야 한다.
- 트랜잭션이 커밋되어도, 실제 데이터베이스 블록의 기록(output)은 나중에 일어날 수 있다.
Immediate Database Modification Example(즉시 데이터베이스 수정 예시)
Output
- Bc는 T1 커밋 전에 디스크에 기록 될 수 있다.
- Ba는 T0 커밋 이후에 디스크에 기록되는 등 순서가 바뀌어도 된다. (복구할 때는 로그들의 순서를 보기 때문이다)
이 구조는 로그가 항상 데이터베이스 변경보다 먼저 기록되어야 하며,
장애 발생 시 로그를 이용해 데이터베이스를 복구할 수 있음을 보여준다.
Concurrency Control and Recovery(동시성 제어와 복구)
동시성 환경에서 모든 트랜잭션은 단일 디스크 버퍼와 단일 로그를 공유한다.
하나의 버퍼 블록은 여러 트랜잭션에 의해 데이터 아이템이 갱신될 수 있다.
- 트랜잭션 격리 보장: 트랜잭션 Ti가 어떤 아이템을 수정하면, Ti가 커밋(commit) 또는 중단(abort)되기 전까지 다른 트랜잭션은 해당 아이템을 수정할 수 없다. 즉, 커밋되지 않은 트랜잭션의 변경 내용은 다른 트랜잭션에서 볼 수 없어야 한다.
- Strict two-phase locking(엄격 2단계 잠금): 갱신된 데이터 아이템에 대해 배타적(exclusive) 잠금을 획득하고, 트랜잭션 종료 시까지 잠금을 유지하면 위 조건을 만족할 수 있다.
- 로그 기록의 interleaving(섞임): 서로 다른 트랜잭션의 로그 레코드는 로그 내에서 섞여서 기록될 수 있다.
Undo and Redo Operations(Undo/Redo 연산)
- Undo(Ti)
- undo(Ti): 트랜잭션 Ti가 갱신한 모든 데이터 아이템을 마지막 로그 레코드부터 거꾸로(old value로) 복구한다.
- 각 데이터 아이템 X를 old value로 복원할 때, 특별한 로그 레코드 <Ti, X, V>를 남긴다.
- 트랜잭션 undo가 끝나면 <Ti abort> 로그 레코드가 기록된다.
- Redo(Ti)
- redo(Ti): 트랜잭션 Ti가 갱신한 모든 데이터 아이템을 처음 로그 레코드부터 순서대로 new value로 재적용한다.
- redo 시에는 별도의 추가 로그 기록이 필요 없다.
Recovering from Failure(장애 복구)
- 트랜잭션 Ti는 로그에 <Ti start>는 있지만 <Ti commit> 또는 <Ti abort>가 없으면 undo 대상이다.
- <Ti start>와 <Ti commit> 또는 <Ti abort>가 모두 있으면 redo 대상이다.
Immediate DB Modification Recovery Example(즉시 데이터베이스 수정 복구 예시)
세 시점의 로그 예시와 복구 동작:
- (a) undo(T0):
- B는 2000, A는 1000으로 복구
- 로그 <T0, B, 2000>, <T0, A, 1000>, <T0 abort> 기록
- (b) redo(T0) and undo(T1):
- A, B는 950, 2050으로 복구(redo)
- C는 700으로 복구(undo)
- 로그 <T1, C, 700>, <T1 abort> 기록
- (c) redo(T0) and redo(T1):
- A, B는 950, 2050
- C는 600
- 둘다 redo이기때문에 로그 기록이 되진 않는다.
추가 장애 복구 상황
- 만약 트랜잭션 Ti가 이미 undo되어 <Ti abort> 로그가 기록된 후 또 장애가 발생하면, 복구 시 Ti는 redo 대상이 된다.
- redo 과정에서는 Ti의 모든 연산(심지어 undo된 연산까지 포함) 전체를 다시 적용한다. 이를 repeating history라 하며, 비효율적으로 보이지만 복구 절차를 단순화한다.
이 구조는 로그 기반 복구에서 undo/redo가 어떻게 동작하며, 장애 발생 시 트랜잭션 상태에 따라 어떤 복구 절차가 적용되는지 보여준다.
Checkpoints(체크포인트)
체크포인트(checkpoint)는 로그 기반 복구에서 복구 성능을 향상시키기 위한 핵심 기법이다.
시스템이 오랜 시간 동작하면 로그가 매우 길어져, 장애 발생 시 전체 로그를 모두 처리하는 것은 비효율적이다.
이미 디스크에 반영된 트랜잭션까지 불필요하게 redo하는 문제도 발생한다.
체크포인트 수행 절차
- 메인 메모리에 존재하는 모든 로그 레코드를 안정 저장장치(stable storage)에 출력(output).
- 수정된 모든 버퍼 블록을 디스크에 output.
- <checkpoint L> 로그 레코드를 기록. 여기서 L은 체크포인트 시점에 활성(active) 상태인 모든 트랜잭션의 리스트.
- 체크포인트 수행 중에는 모든 업데이트가 일시 중단된다.
체크포인트 이후 복구 절차
- 복구 시에는 체크포인트 이전에 종료된(커밋/중단된) 트랜잭션은 모두 무시할 수 있다.
- 최근의 <checkpoint L> 로그 레코드를 찾고, L에 포함된 트랜잭션과 체크포인트 이후에 시작된 트랜잭션만 redo/undo 대상이다.
- 체크포인트 이전에 커밋 또는 abort된 트랜잭션은 이미 모든 변경 내용이 디스크에 반영되어 있으므로 추가 작업이 필요 없다.
Undo를 위한 추가 로그 스캔
- undo 연산이 필요한 경우, L에 포함된 각 트랜잭션의 <Ti start> 로그 레코드를 찾을 때까지 로그를 더 거슬러 올라가야 할 경우도 있다.
체크포인트 예시
위 그림 예시에서
- T1: 체크포인트 이전에 종료 → 무시 가능(이미 디스크 반영)
- T2, T3 : 체크포인트 시점에 활성 → redo 필요
- T4 : 체크포인트 이후 시작, 장애 시점에 미완료 → undo 필요
이처럼 체크포인트는 복구 시 처리해야 할 로그의 범위를 대폭 줄여주고,
불필요한 redo/undo 작업을 방지하여 데이터베이스 복구 효율을 높여준다.
댓글