WAL과 로그 기반 복구
DBMS는 언제든 장애가 발생할 수 있습니다. 정전, 디스크 오류, 프로세스 충돌 등의 상황에서도 커밋된 트랜잭션의 데이터는 반드시 보존되어야 하고, 커밋되지 않은 트랜잭션의 데이터는 깔끔하게 제거되어야 합니다. 이 요구를 충족시키는 핵심 기술이 WAL(Write-Ahead Logging)입니다. 데이터를 변경하기 전에 반드시 로그를 먼저 디스크에 기록하라는 단순하면서도 강력한 원칙이 현대 데이터베이스 복구 메커니즘의 토대입니다.
WAL 없이 데이터베이스를 운영한다면, 장애 발생 시 이 데이터가 커밋된 건가, 아닌 건가?를 알 수 없게 됩니다. COMMIT을 실행했지만 데이터가 디스크에 반영되기 전에 전원이 꺼지면, 그 트랜잭션의 변경은 영원히 사라집니다. WAL은 이 문제를 해결합니다.
WAL 원칙
WAL의 핵심은 두 가지 규칙입니다.
규칙 1 (Undo Rule)
데이터 페이지를 디스크에 기록하기 전에,
해당 변경의 로그 레코드가 먼저 디스크에 기록되어야 한다.
→ 장애 시 Undo(롤백)가 가능하도록 보장
규칙 2 (Redo Rule)
트랜잭션을 커밋하기 전에,
해당 트랜잭션의 모든 로그 레코드가 디스크에 기록되어야 한다.
→ 장애 시 Redo(재적용)가 가능하도록 보장WAL 처리 순서
트랜잭션이 데이터를 변경할 때의 내부 처리 순서를 따라가 봅시다.
UPDATE accounts SET balance = 200 WHERE id = 1; (기존값 100)
① 로그 버퍼에 변경 기록 작성
[LSN=1001, T1, UPDATE, accounts.id=1, balance: 100→200]
② 데이터 버퍼(메모리)에 변경 적용
버퍼 풀의 해당 페이지에서 balance를 200으로 변경
→ 이 페이지는 "Dirty Page"가 됨 (메모리 ≠ 디스크)
③ COMMIT 시: 로그 버퍼를 디스크에 flush (fsync)
로그 파일에 COMMIT 레코드까지 기록 완료
→ 이 시점에서 "트랜잭션 커밋 완료"를 클라이언트에 응답
④ 데이터 페이지는 나중에 디스크에 기록 (Lazy Write / Checkpoint)
→ 즉시 쓰지 않아도 됨! 로그가 있으므로 복구 가능이 순서에서 핵심은 ③번입니다. 데이터가 아직 디스크에 기록되지 않았더라도, 로그가 디스크에 있으면 장애 후 Redo로 데이터를 복원할 수 있습니다. 반대로 ④번에서 데이터가 디스크에 기록되지 않은 채 장애가 발생해도 문제없습니다.
WAL의 성능 이점
왜 데이터를 바로 디스크에 안 쓸까요? 로그는 순차 쓰기(Sequential Write)이고, 데이터는 랜덤 쓰기(Random Write)이기 때문입니다.
로그 쓰기 (순차)
┌────────────────────────────────────┐
│████████████████████████████████████│ → 파일 끝에 연속으로 추가
└────────────────────────────────────┘
* 디스크 헤드 이동 최소
* HDD에서도 100+ MB/s 가능
* SSD에서는 더욱 빠름
데이터 쓰기 (랜덤)
┌────────────────────────────────────┐
│█ ██ █ █ █ ██ █ █ █ ██ █ │ → 다양한 테이블의 다양한 페이지
└────────────────────────────────────┘
* 디스크 헤드가 계속 이동
* HDD에서 1~10 MB/s 수준
* SSD에서도 순차 쓰기보다 느림예를 들어 10건의 UPDATE가 서로 다른 테이블의 서로 다른 페이지에 흩어져 있다면, 데이터를 직접 쓰려면 10번의 랜덤 I/O가 필요합니다. 하지만 WAL을 쓰면 로그 파일에 10건의 레코드를 순차적으로 쓰는 것으로 충분합니다. 실제 데이터 페이지의 디스크 기록은 나중에 모아서 처리합니다.
No-Force / Steal 정책
WAL과 함께 사용되는 버퍼 관리 정책을 이해하면 복구 메커니즘의 전체 그림이 보입니다.
Force vs No-Force (커밋 시)
Force: 커밋 시 변경된 데이터 페이지를 즉시 디스크에 기록
No-Force: 커밋 시 로그만 디스크에 기록, 데이터는 나중에
→ No-Force가 성능상 유리 (대부분의 DBMS가 채택)
→ No-Force이므로 Redo가 필요
Steal vs No-Steal (롤백 전)
Steal: 커밋 전에도 변경된 데이터 페이지를 디스크에 기록 가능
No-Steal: 커밋 전까지 변경된 페이지를 디스크에 기록하지 않음
→ Steal이 메모리 효율적 (대부분의 DBMS가 채택)
→ Steal이므로 Undo가 필요
대부분의 DBMS: Steal + No-Force → Undo + Redo 모두 필요| 정책 | 설명 | 필요한 복구 동작 |
|---|---|---|
| No-Force | 커밋돼도 데이터가 디스크에 없을 수 있음 | Redo 필요 |
| Steal | 커밋 안 된 데이터가 디스크에 있을 수 있음 | Undo 필요 |
로그 레코드 구조
Redo/Undo 로그에는 각 변경에 대한 레코드가 기록됩니다. DBMS마다 구체적인 구조는 다르지만, 공통적으로 포함하는 정보가 있습니다.
┌──────────┬──────┬───────┬──────────┬──────────┬─────────┬────────┐
│ LSN │ TxID │ 연산 │ 대상 │ 이전 값 │ 이후 값 │ PrevLSN│
├──────────┼──────┼───────┼──────────┼──────────┼─────────┼────────┤
│ 1001 │ T1 │ BEGIN │ - │ - │ - │ - │
│ 1002 │ T1 │ UPDATE│ A.bal │ 100 │ 200 │ 1001 │
│ 1003 │ T2 │ BEGIN │ - │ - │ - │ - │
│ 1004 │ T1 │ UPDATE│ B.bal │ 500 │ 400 │ 1002 │
│ 1005 │ T2 │ UPDATE│ C.stock │ 50 │ 49 │ 1003 │
│ 1006 │ T1 │ COMMIT│ - │ - │ - │ 1004 │
│ 1007 │ T2 │ UPDATE│ D.price │ 1000 │ 900 │ 1005 │
│ 1008 │ T2 │ ABORT │ - │ - │ - │ 1007 │
└──────────┴──────┴───────┴──────────┴──────────┴─────────┴────────┘각 필드의 역할을 설명합니다.
LSN (Log Sequence Number): 로그 레코드의 고유 식별자입니다. 단조 증가하는 값으로, 이벤트의 발생 순서를 나타냅니다. 데이터 페이지에도 "이 페이지에 마지막으로 적용된 로그의 LSN"이 기록되어 있어, 복구 시 어떤 로그까지 적용되었는지 판단할 수 있습니다.
TxID: 트랜잭션 식별자입니다. 같은 트랜잭션의 로그 레코드를 묶어서 추적할 수 있습니다.
이전 값 (Before Image): 변경 전의 값입니다. Undo(롤백) 시 이 값으로 되돌립니다.
이후 값 (After Image): 변경 후의 값입니다. Redo(재적용) 시 이 값으로 설정합니다.
PrevLSN: 같은 트랜잭션의 이전 로그 레코드를 가리킵니다. Undo 시 역방향으로 추적하는 연결 리스트 역할을 합니다.
물리적 로깅 vs 논리적 로깅
물리적 로깅 (Physical Logging):
"페이지 42, 오프셋 128의 바이트를 0x64에서 0xC8로 변경"
→ 정확한 물리적 위치와 값을 기록
→ 복구가 단순하고 빠름
→ 로그 크기가 클 수 있음
논리적 로깅 (Logical Logging):
"employees 테이블의 id=1인 행의 salary를 200으로 변경"
→ SQL 수준의 논리적 연산을 기록
→ 로그 크기가 작음
→ 복구가 복잡할 수 있음 (인덱스 변경 등)
물리적-논리적 혼합 (Physiological Logging):
"페이지 42에서 id=1인 행의 salary를 200으로 변경"
→ 페이지 단위로 물리적 + 페이지 내에서 논리적
→ 대부분의 현대 DBMS가 이 방식 사용 (ARIES)ARIES 복구 알고리즘
ARIES(Algorithm for Recovery and Isolation Exploiting Semantics)는 IBM에서 개발한 복구 알고리즘으로, 현대 대부분의 DBMS가 채택하고 있는 표준적인 복구 방법입니다.
ARIES의 세 가지 핵심 원칙은 WAL(Write-Ahead Logging), Repeating History During Redo, Logging Changes During Undo입니다. 이 원칙에 따라 세 단계로 진행됩니다.
1. WAL: 데이터 변경 전에 반드시 로그를 먼저 기록
2. Repeating History: Redo 시 커밋 여부와 무관하게 모든 변경을 재적용
3. Logging Undo: Undo 작업 자체도 CLR로 기록하여 멱등성 보장1단계: 분석 (Analysis Phase)
마지막 체크포인트부터 로그 끝까지 순방향으로 읽으며, 장애 시점에 어떤 트랜잭션이 활동 중이었는지와 어떤 데이터 페이지가 Dirty(메모리에서 변경되었지만 디스크에 반영되지 않음)였는지를 파악합니다.
입력: 체크포인트 레코드 + 이후 로그
출력
1. Active Transaction Table (ATT)
→ 장애 시점에 커밋되지 않은 트랜잭션 목록
→ 이들은 Undo 대상
2. Dirty Page Table (DPT)
→ 디스크에 반영되지 않은 페이지 목록과 각 페이지의 첫 변경 LSN
→ Redo의 시작점 결정에 사용2단계: Redo (Redo Phase)
분석 단계에서 결정된 시작점부터 로그 끝까지 순방향으로 읽으며, 모든 변경을 재적용합니다. 커밋 여부와 관계없이 모든 로그 레코드를 재적용합니다. 이를 Repeating History라 합니다.
시간 →
체크포인트 ────────────────────── 장애 시점
│ │
│ T1: UPDATE COMMIT │
│ T2: UPDATE UPDATE │ ← 커밋 안 됨
│ T3: UPDATE COMMIT UPDATE │ ← 커밋 후 추가 변경
│ │
└────── Redo: 전부 재적용 ───────┘
왜 커밋 안 된 것도 Redo하나?
→ 물리적 일관성을 먼저 복원하기 위해
→ Undo 단계에서 커밋 안 된 것을 깔끔하게 제거Redo 시에는 각 데이터 페이지의 pageLSN(이 페이지에 마지막으로 적용된 로그의 LSN)을 확인합니다. 로그 레코드의 LSN이 pageLSN보다 크면 아직 적용 안 된 변경이므로 Redo를 수행합니다. 이미 적용된 변경은 건너뜁니다(멱등성).
3단계: Undo (Undo Phase)
분석 단계에서 파악된 미완료 트랜잭션(ATT에 있는 트랜잭션)의 변경을 역방향으로 되돌립니다.
미완료 트랜잭션 T2의 로그
LSN=1003: BEGIN
LSN=1005: UPDATE C.stock 50→49
LSN=1007: UPDATE D.price 1000→900
Undo 순서 (역방향)
1. LSN=1007: D.price를 900→1000으로 복원
→ CLR(Compensation Log Record) 기록: "D.price를 1000으로 되돌림"
2. LSN=1005: C.stock을 49→50으로 복원
→ CLR 기록: "C.stock을 50으로 되돌림"
3. T2의 Undo 완료 → End 레코드 기록CLR(Compensation Log Record)은 Undo 작업 자체를 기록하는 로그입니다. Undo 도중에도 장애가 발생할 수 있으므로, Undo를 얼마나 진행했는지 기록해두어야 합니다. CLR 덕분에 Undo는 반복 수행해도 동일한 결과를 보장합니다(멱등성).
ARIES 복구 전체 흐름
장애 발생!
[1단계: 분석]
체크포인트 이후 로그 스캔
→ 커밋된 트랜잭션: {T1, T3}
→ 미완료 트랜잭션: {T2, T4}
→ Dirty 페이지: {P5, P12, P30}
[2단계: Redo]
DPT의 최소 LSN부터 로그 끝까지 순방향 재적용
→ T1, T2, T3, T4의 모든 변경을 재적용
→ 장애 직전 메모리 상태를 복원
[3단계: Undo]
미완료 트랜잭션 {T2, T4}의 변경을 역방향으로 되돌림
→ T4의 마지막 변경부터 역순으로 Undo
→ T2의 마지막 변경부터 역순으로 Undo
→ 각 Undo마다 CLR 기록
[복구 완료]
→ 커밋된 T1, T3의 변경은 보존
→ 미완료 T2, T4의 변경은 제거
→ 데이터베이스가 일관된 상태로 복원체크포인트 (Checkpoint)
체크포인트는 메모리의 변경된 데이터(Dirty Pages)를 디스크에 기록하는 시점입니다. 체크포인트의 주된 목적은 복구 시간을 단축하는 것입니다. 체크포인트가 수행되면 해당 시점 이전의 로그는 Redo에서 건너뛸 수 있으므로, 체크포인트 간격이 짧을수록 장애 후 복구 시간이 줄어듭니다. 그러나 체크포인트 자체도 디스크 I/O를 발생시키므로 너무 빈번하면 정상 운영 성능이 저하됩니다. 운영 환경에서는 이 트레이드오프를 적절히 조정해야 합니다.
체크포인트가 필요한 이유
체크포인트 없이
DB 시작 ─────────────────────────────── 장애
│← 로그의 처음부터 끝까지 Redo →│
→ 수시간~수일치 로그를 전부 재적용
→ 복구 시간이 매우 길어짐
체크포인트 사용
DB 시작 ──┬──────┬──────┬──── 장애
CP1 CP2 CP3
│←Redo→│
→ 마지막 체크포인트(CP3) 이후만 Redo
→ 복구 시간 대폭 단축체크포인트의 종류
Sharp Checkpoint (전체 체크포인트)
1. 새로운 트랜잭션 시작 차단
2. 진행 중인 트랜잭션이 모두 끝날 때까지 대기
3. 모든 Dirty 페이지를 디스크에 기록
4. 체크포인트 레코드를 로그에 기록
→ 체크포인트 이전의 모든 변경이 디스크에 반영됨
→ 서비스 중단 발생 (비실용적)
Fuzzy Checkpoint (퍼지 체크포인트)
1. 체크포인트 시작 시점의 Dirty Page Table과 Active Transaction Table 기록
2. Dirty 페이지를 백그라운드에서 점진적으로 디스크에 기록
3. 트랜잭션 중단 없이 수행
→ 서비스 영향 최소화
→ 대부분의 상용 DBMS가 사용Fuzzy Checkpoint에서는 체크포인트 시점에 모든 Dirty 페이지가 디스크에 기록되는 것이 아닙니다. 따라서 복구 시 체크포인트의 DPT를 참조하여 Redo의 시작점을 결정해야 합니다.
DBMS별 체크포인트 설정
파라미터
FAST_START_MTTR_TARGET = 30 → 목표 복구 시간 30초
→ Oracle이 자동으로 체크포인트 빈도를 조절
프로세스
DBWR (Database Writer): Dirty 페이지를 디스크에 기록
CKPT (Checkpoint Process): 체크포인트 수행, 컨트롤 파일 업데이트
LGWR (Log Writer): 로그 버퍼를 디스크에 기록
수동 체크포인트
ALTER SYSTEM CHECKPOINT;파라미터:
checkpoint_timeout = 5min → 최소 5분마다 체크포인트
max_wal_size = 1GB → WAL 크기가 1GB 초과 시 체크포인트
checkpoint_completion_target = 0.9 → 체크포인트 기간 분산
프로세스:
Checkpointer: 체크포인트 수행
Background Writer: Dirty 페이지를 점진적으로 기록
수동 체크포인트:
CHECKPOINT;파라미터:
innodb_max_dirty_pages_pct = 75 → Dirty 페이지 비율 75% 초과 시
innodb_io_capacity = 200 → 초당 I/O 작업 수
innodb_adaptive_flushing = ON → 적응적 flush
프로세스:
Page Cleaner Thread: Dirty 페이지를 디스크에 기록로그 버퍼와 디스크 기록 시점
로그는 먼저 메모리의 로그 버퍼에 기록되고, 특정 시점에 디스크에 기록(flush)됩니다.
1. 트랜잭션이 COMMIT할 때 (필수 — WAL 규칙 2)
2. 로그 버퍼가 1/3 이상 찼을 때
3. 3초마다 주기적으로
4. DBWR이 Dirty 페이지를 기록하기 전에 (WAL 규칙 1)synchronous_commit = on → COMMIT 시 로그를 디스크에 즉시 flush
느리지만 데이터 손실 없음 (기본값)
synchronous_commit = off → COMMIT 시 로그를 즉시 flush하지 않음
빠르지만 최근 트랜잭션 손실 가능
(최대 wal_writer_delay 기간만큼)로그 기반 시점 복구 (PITR)
WAL 로그를 활용하면 특정 시점으로 데이터베이스를 복구할 수 있습니다. 이를 PITR(Point-In-Time Recovery)이라 합니다.
시간 →
──────┬────────────────┬──────┬──── 현재
전체 백업 장애 요구하는
(09:00) (14:00) 복구 시점
(13:55)
1. 09:00 전체 백업을 복원
2. 09:00~13:55 사이의 WAL 로그를 순서대로 적용 (Redo)
3. 13:55 이후의 변경은 적용하지 않음
→ 13:55 시점의 데이터베이스 상태로 복원!-- recovery.conf (PostgreSQL 12 이전)
-- postgresql.conf (PostgreSQL 12+)
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2024-03-15 13:55:00'-- RMAN에서 시점 복구
RMAN> RUN {
SET UNTIL TIME "TO_DATE('2024-03-15 13:55:00', 'YYYY-MM-DD HH24:MI:SS')";
RESTORE DATABASE;
RECOVER DATABASE;
}PITR은 잘못된 DELETE나 DROP TABLE 같은 인적 오류에서 복구할 때 매우 유용합니다. 실수하기 직전 시점으로 되돌릴 수 있기 때문입니다.
로그 관련 장애 유형과 복구
트랜잭션 장애
개별 트랜잭션의 실패(제약 위반, 교착 상태 등)로 인한 롤백입니다. Undo 로그를 사용하여 해당 트랜잭션의 변경만 되돌립니다. 다른 트랜잭션에는 영향을 주지 않습니다. 트랜잭션 장애는 정상적인 운영 중에도 빈번하게 발생하며, DBMS가 자동으로 처리합니다. 개발자가 명시적으로 ROLLBACK 명령을 실행하는 경우도 트랜잭션 장애의 일종으로, 동일하게 Undo 로그를 사용하여 변경을 되돌립니다.
시스템 장애 (Instance Crash)
정전, DBMS 프로세스 충돌, OS 충돌 등으로 인스턴스가 비정상 종료된 경우입니다. 인스턴스 재시작 시 ARIES 3단계 복구가 자동으로 수행됩니다. 디스크의 데이터 파일은 손상되지 않았으므로 로그만으로 복구 가능합니다. 시스템 장애 후 복구에 소요되는 시간은 체크포인트 간격에 의해 결정됩니다. 체크포인트가 자주 수행될수록 Redo 범위가 줄어들어 복구가 빠릅니다.
미디어 장애 (Disk Failure)
디스크 물리적 손상으로 데이터 파일이 손실된 경우입니다. 전체 백업 + 아카이브 로그를 사용하여 PITR로 복구합니다. 이 경우에는 로그 파일이 별도의 디스크나 아카이브 스토리지에 보존되어 있어야 합니다. 미디어 장애는 데이터 파일 자체가 손상되므로 가장 심각한 장애 유형입니다. 디스크 RAID 구성과 정기적인 전체 백업이 미디어 장애 대비의 핵심입니다.
┌──────────────────┬────────────────────┬──────────────────┬───────────────┐
│ 장애 유형 │ 복구 방법 │ 자동/수동 │ 데이터 손실 │
├──────────────────┼────────────────────┼──────────────────┼───────────────┤
│ 트랜잭션 │ Undo 로그로 롤백 │ 자동 (DBMS) │ 없음 │
│ 시스템(인스턴스) │ ARIES 3단계 복구 │ 자동 (재시작 시) │ 없음 │
│ 미디어(디스크) │ 백업 + PITR │ 수동 (DBA 개입) │ 가능성 있음 │
└──────────────────┴────────────────────┴──────────────────┴───────────────┘아카이브 로그 vs 온라인 로그
DBMS의 로그 파일은 관리 모드에 따라 복구 가능 범위가 크게 달라집니다.
운영 환경에서 적절한 모드를 선택하는 것은 DBA의 핵심 업무 중 하나입니다.
NOARCHIVELOG 모드
Redo 로그 파일이 순환적으로 덮어쓰기됨
→ 시스템 장애 복구: 가능 (온라인 로그로)
→ PITR: 불가능 (과거 로그가 덮어씌워졌으므로)
→ 개발/테스트 환경에서 주로 사용
ARCHIVELOG 모드
Redo 로그가 꽉 차면 아카이브 스토리지로 복사 후 재사용
→ 시스템 장애 복구: 가능
→ PITR: 가능 (아카이브 로그를 순차 적용)
→ 운영 환경에서 필수-- 현재 모드 확인
SELECT LOG_MODE FROM V$DATABASE;
-- ARCHIVELOG 모드로 변경 (MOUNT 상태에서)
ALTER DATABASE ARCHIVELOG;로그 설계 실무 고려사항
✅ 로그 파일을 데이터 파일과 다른 디스크에 배치
→ 동시 I/O 분산, 미디어 장애 시 로그 보존
✅ 로그 파일 다중화 (Multiplexing)
→ Oracle: 여러 로그 그룹 멤버
→ PostgreSQL: archive_command로 여러 위치에 복사
✅ 아카이브 로그 보관 정책 수립
→ 일별, 주별 순환 또는 클라우드 스토리지 활용
→ 법적 보존 기간 고려 (금융: 5~10년)
✅ 로그 버퍼 크기 적절히 설정
→ Oracle: LOG_BUFFER
→ PostgreSQL: wal_buffers
→ 너무 작으면 빈번한 flush, 너무 크면 메모리 낭비
✅ 체크포인트 간격 조정
→ 짧으면: 복구 빠름, 평상시 I/O 부하 증가
→ 길면: 복구 느림, 평상시 I/O 부하 감소요약 정리
| 개념 | 설명 |
|---|---|
| WAL | 데이터 변경 전 로그 먼저 기록하는 원칙 |
| Redo 로그 | 변경 후 값 기록, 커밋된 트랜잭션 복원용 |
| Undo 로그 | 변경 전 값 기록, 미완료 트랜잭션 롤백용 |
| LSN | 로그 레코드 고유 번호, 적용 여부 판단 기준 |
| ARIES | 분석→Redo→Undo 3단계 표준 복구 알고리즘 |
| 체크포인트 | Dirty 페이지 디스크 기록, 복구 시간 단축 |
| CLR | Undo 작업 자체를 기록, Undo 멱등성 보장 |
| PITR | 로그 기반 특정 시점 복구 |
| No-Force/Steal | 대부분의 DBMS 버퍼 정책, Redo+Undo 필요 근거 |
| ARCHIVELOG | 아카이브 로그 모드, PITR을 위한 필수 설정 |
| 로그 다중화 | 로그 파일을 여러 디스크에 복사하여 안전성 확보 |
WAL은 단순한 기법이 아니라 데이터베이스의 내구성(Durability)을 보장하는 근본 원리입니다. COMMIT 응답을 받은 클라이언트는 이 변경은 영구적이다라고 신뢰할 수 있는데, 그 신뢰의 기반이 바로 WAL입니다. 로그가 디스크에 기록되었으므로 어떤 장애가 발생해도 Redo를 통해 변경을 복원할 수 있기 때문입니다. ARIES 알고리즘은 이 WAL 원칙 위에서 분석, Redo, Undo의 3단계를 체계적으로 수행하여, 시스템 장애 후에도 데이터베이스를 일관된 상태로 확실하게 복원합니다.
다음 절에서는 Oracle의 RMAN과 실무 백업 전략을 다루겠습니다.