7장에서는 트랜잭션에는 ACID 라는 특성을 다뤘다.
ACID의 'D' 는 지속성(Durability)으로, 트랜잭션이 완료되었다는 통지를 사용자가 받은 시점부터 그 결과는 영속화되어 결과를 잃어버리지 않는 것을 말한다.
이는 서버나 OS의 비정상적인 종료 등의 시스템 장애에도 대응 할 수 있다는 뜻이다.
지속성을 지키기 위해 어떤 방법을 사용하는지 알아보자.
대부분 DBMS에선 디스크(HDD, SSD 등)에 데이터를 보존한다.
지속성을 실현하기위해 '쓰기'를 전부 '동기화 쓰기'로 하면 좋겠지만, 데이터베이스의 쓰기는 디스크의 임의 장소에 무작위로 액세스 하여 쓰기를 실행하기 때문에 실용적이지 않다.
그래서 지속성과 성능이 양랍하도록 구현하는데, 일반적으론 아래와 같은 방법을 사용한다.
로그 선행 쓰기(WAL, White Ahead Log)는 데이터베이스의 데이터 파일 변경을 직접 수행하지 않고, 로그로 변경 내용을 기술한 로그 레코드를 써서 동기화하는 구조이다.
WAL에는 다음과 같은 이점이 있다.
커밋 시에는 WAL에 변경 내용을 쓰기 때문에 데이터 파일의 변경 내용을 트랜잭션이 커밋됨과 동시에 동기화할 필요가 없다.
하지만 트랜잭션마다 비동기적인 쓰기를 하면 로그와 데이터 파일 간 일관성을 유지하기 어렵다.
따라서, 일반적인 DBMS는 데이터베이스 버퍼를 준비해 데이터 파일로의 입력을 데이터베이스 버퍼로 일원화해서 단순화 하고 있다.
이로인해 효율적으로 데이터의 일관성을 유지할 수 있게 된다.
갱신의 흐름은 다음과 같다.
로그 파일 역시 파일이기 때문에 파일을 작성할 때 비효율적인 동기화 쓰기를 작성하지 않고 버퍼를 사용한다.
여기서 "메모리 버퍼캐시가 휘발성이여서 로그를 남기는데 로그도 버퍼를 사용한다면 영속성을 지킬 수 없지 않을까?"는 생각이 들 수 있다.
오라클에서는 DBWR(Dirty 블록 버퍼에서 데이터파일로 보냄)와 LGWR(로그 버퍼에서 파일로 보냄) 프로세스는 주기적으로 각각 Dirty 블록과 Redo 로그버퍼를 파일에 기록한다. 또한, LGWR 프로세스는 서버 프로세스가 커밋이 발생했다고 신호를 보낼 때도 활동을 시작한다.
즉, 적어도 커밋시점에는 로그 버퍼 내용을 로그 파일에 기록한다는 뜻으로, 이를 Log Force at Commit이라고 한다.
커밋 시점에 로그 파일이 디스크에 안전하게 기록했다면, 그 시점부터 트랜잭션의 영속성은 보장된다.
TMI
Commit은 로그가 디스크에 기록됬다는 신호를 받기 전까지 작업을 진행하지 않는 Sync방식이다. 그래서 Commit은 생각보다 느리다.
WAL, 데이터베이스 버퍼, 데이터베이스 파일 3가지를 연계하여 지속성을 담보하면서 현실적인 성능으로 DBMS가 동작하고 있다.
크래시(에러 상황)가 발생한 경우 어떻게 복구하는지 알아보자.
크래시가 발생하면 다음과 같은 상태가 된다.
크래시 이후 DBMS(여기선 MySQL)는 3과 1의 체크포인트 이후 갱신 정보를 사용해 데이터베이스 파일을 크래시 시점의 커밋된 최신 상태로 수정한다.
쉽게 설명하자면 3은 체크포인트 시점까지의 갱신 정보를 가지고 있고, 1은 커밋된 트랜잭션의 정보를 가지고 있다.
따라서 로그를 사용해 디스크에 수행되지(체크포인트 되지 않은) 않은 트랜잭션을 수행한다.
이 동작을 '롤 포워드(Roll-Forwar)'라고 한다.
하지만 이와 같은 구조도 논리적인 파괴(DDL 문에 의한 테이블의 파괴 등)나 물리적인 파손(디스크 장치의 고장 등)에는 대응할 수 없다.
그렇기에 정상적으로 동작하고 있을 때, 주기적으로 백업하여 이를 이용해 복원이나 복구하는 것이 좋다.
데이터베이스를 백업하고, 장애가 발생하면 백업으로 복원한다. 하지만 단순이 백업 시점으로만 되돌릴 뿐 백업 후에 데이터베이스에서 수행한 갱신은 반영되지 않는다.
일반적인 DBMS는 실행된 갱신을 기록한 로그를 보존해서 백업한 데이터베이스에 순차 반영해 백업 이후의 임의의 시점으로 복원할 수 있다.
이처럼 특정 시점에서 데이터 변경을 포함한 복원이나 복구를 PITR(Point-in-time Recovery = 특정 시점 복구)라고 부른다.
DBMS 마다 PITR에 이용되는 로그의 이름과 특성이 다르다.
DBMS | Oracle | MySQL | PostgreSQL | DB2 | SQL Server |
---|---|---|---|---|---|
로그 이름 | REDO 로그 | 바이너리 로그 | WAL 로그 | 트랜잭션 로그 | 트랜잭션 로그 |
아카이브 지정 | ⭕ | ❌ | ⭕ | ⭕ | ⭕ |
아카이브 시 이름 | ARCHIVELOG | ❌ | WAL 아카이브 | 아카이브 로깅 | 완전 복구 모델 |
비 아카이브 시 이름 | NOARCHIVELOG | ❌ | (없음) | 순환 로깅 | 완전 복구 모델 |
PITR에 사용하는 로그는 대부분 WAL을 이용하는데, WAL을 크래시 복구에만 사용한다면 체크포인트 이전의 로그는 불필요하게 되어 해당 디스크 영역은 삭제하거나 재이용(덮어쓰기) 할 수 있다.
하지만 이렇게 된다면 PITR을 수행할 때 필요한 로그가 없는 사태가 발생하게 된다. 따라서 크래시 복구용으론 불필요한 로그도 PITR용으로 보존하기 위한 모드가 '아카이브 지정'이다.
핫 백업은 '온라인 백업'이라고도 하며 백업 대상의 데이터베이스를 정지하지 않고 가동한 채로 백업 데이터를 얻는다.
'데이터베이스의 기능'으로 백업한다.
콜 백업은 '오프라인 백업'이라고도 하며 백업 대상의 데이터베이스를 정지한 상태로 백업 데이터를 얻는다.
'OS의 기능'으로 백업한다. 데이터베이스 정지 중에는 일반적으로 데이터 저장 파일을 OS로 다루는 상태가 되므로 이를 백업해 백업 데이터를 얻는다.
논리 백업은 SQL 기반의 텍스트 형식으로 백업 데이커가 기록되고, 물리 백업은 데이터 영역을 그대로 덤프(데이터 파일이나 화면에 출력)하는 이미지로 바이너리 형식으로 기록된다.
장점
장점
풀 백업은 전체 백업이라고도 하며 데이터베이스 전체 데이터를 매일 백업하는 방식이다.
부분 백업은 우선 풀 백업을 한 후에 이후 갱신된 데이터를 백업하는 방식이다.
장점
단점
장점
단점
차등 백업(Differential)은 풀 백업 이후 갱신된 데이터를 백업한다.
증분 백업(Incremental)은 최근 백업(풀 백업에 한정되지 않음)한 이후에 갱신된 데이터를 백업한다. 증분 백업은 데이터의 양이 차등 백업보다 적지만 복원 시 모든 증분 백업을 차례로 적용해야 해서 절차가 복잡하다.
앞서 소개한 롤 포워드와 비슷하게, 아카이브(MySQL에선 바이너리 로그)를 증분 백업으로 보존하고 이를 사용해 풀 백업 시점 이후 임의 시점까지 복원하는 것을 '롤 포워드 리커버리'라고 한다.
현재의 데이터베이스 = 풀 백업한 데이터 + 풀 백업 후 얻은 모든 증분 백업
WAL의 체크포인트 이후의 갱신정보를 사용해 크래시가 일어났던 시점까지 커밋된 최신 상태로 수정하는 동작을 의미한다.