컴퓨터 구조 운영체제 네트워크 자료구조 알고리즘 데이터베이스 내 학습
db · 18

백업 / 복구(WAL)

WAL복구백업

WAL(Write-Ahead Logging)은 데이터 파일의 변경을 디스크에 쓰기 전에 그 변경 내용을 로그에 먼저 기록하는 방식입니다. 기본 설정에서 커밋은 로그가 디스크에 닿으면 완료되고, 장애가 나도 로그를 재생해 커밋된 변경을 복구할 수 있습니다.

백업과 복구는 이 로그를 한 단계 더 활용합니다. 정기적으로 받은 베이스 백업에 보관해 둔 WAL을 이어서 재생하면 원하는 시점의 상태로 되돌릴 수 있고, 이를 PITR(Point-In-Time Recovery)이라 합니다. 이 토픽에서는 WAL의 쓰기 순서, 장애 뒤 재생 과정, PITR을 시각화로 확인합니다.

핵심 개념

WAL 원칙

데이터 페이지를 디스크에 쓰기 전에 그 변경을 설명하는 로그 레코드를 먼저 디스크에 기록합니다.

핵심 개념

체크포인트

메모리의 더티 페이지를 데이터 파일에 기록하는 시점입니다. 복구는 마지막 체크포인트가 가리키는 지점의 로그부터 재생하면 됩니다.

핵심 개념

redo (재생)

장애 뒤 재시작할 때 로그를 순서대로 다시 적용해 데이터 파일에 반영되지 못한 커밋된 변경을 복원하는 과정입니다.

핵심 개념

베이스 백업

데이터베이스 파일 전체를 복사해 둔 백업입니다. 복사 중 생긴 불일치는 WAL 재생이 바로잡아 주므로 PITR의 출발점이 됩니다.

핵심 개념

WAL 아카이브

서버가 채운 WAL 세그먼트를 안전한 곳에 계속 보관하는 것입니다. 베이스 백업 이후의 변경을 이어서 재생하려면 필요합니다.

핵심 개념

PITR

베이스 백업에 WAL을 재생하다 원하는 시점에서 멈춰 그 시점의 상태로 복구하는 방법입니다. 잘못된 DELETE 직전으로 돌아가는 것이 대표적인 용도입니다.

기본 설정에서 커밋은 로그가 디스크에 닿으면 끝납니다 — 데이터 페이지를 여기저기 쓰지 않고 로그를 순서대로 기록하면 되므로, 변경이 많아도 커밋 비용을 줄일 수 있습니다.
WAL이 내구성을 지킵니다 — 서버가 갑자기 꺼져도 커밋된 변경은 로그에 남아 있어 재시작 때 재생으로 복원됩니다.
복제는 백업이 아닙니다 — 잘못된 DELETE도 복제 서버에 그대로 전파되므로, 사람의 실수를 되돌리려면 따로 보관한 백업과 PITR이 필요합니다.
복구 범위는 보관 정책이 정합니다 — 베이스 백업의 주기와 WAL 보관 기간이 어디까지 되돌릴 수 있는지를 결정합니다.
이 시각화에서 확인할 것 — "WAL 원칙" 탭에서 로그를 먼저 쓰고 체크포인트가 데이터 파일에 나중에 반영하는 순서를, "장애 복구" 탭에서 서버가 꺼진 뒤 커밋된 T1만 결과에 남고 미커밋 T2는 보이지 않는 모습을, "PITR" 탭에서 사고 직전 시점에서 재생이 멈추는 모습을 STEP으로 확인하세요.

WAL과 백업의 배경

변경을 로그에 먼저 기록해 장애에서 복구한다는 발상은 데이터베이스 복구 이론의 기본입니다. 모한(C. Mohan) 등이 1992년에 발표한 ARIES 알고리즘이 WAL을 바탕으로 한 복구 방식을 체계적으로 정리했고, IBM DB2 등 여러 시스템에 구현되었습니다. ARIES는 로그 재생(redo)과 미완료 트랜잭션 되돌리기(undo)를 결합하고, PostgreSQL은 undo 단계 없이 MVCC 규칙으로 커밋되지 않은 변경을 보이지 않게 둡니다.

PostgreSQL은 WAL을 크래시 복구뿐 아니라 복제와 PITR에도 사용합니다. 백업에는 SQL 문으로 데이터를 내보내는 논리 백업(pg_dump)과 파일을 복사하는 물리 백업(pg_basebackup)이 있고, PITR은 물리 백업과 WAL 아카이브를 이용합니다.

숫자로 확인하기

장애 직전 — T1이 x를 2로 바꿔 커밋했고 T2는 y를 9로 바꿨지만 커밋하지 못했습니다. 데이터 파일은 아직 x = 1, y = 5입니다.
복구 결과 — 복구가 끝나면 커밋 기록이 있는 T1만 남아 x = 2, y = 5가 됩니다. 미커밋 T2의 y = 9는 보이지 않습니다.
PITR 결과 — t = 0의 베이스 백업(x = 1, y = 5)에 t = 4까지 WAL을 재생하면 x = 3, y = 9이고, 끝까지 재생하면 t = 5의 DELETE로 x가 사라져 y = 9만 남습니다.
코드 확인 — 파이썬 모델은 복구 결과 {'x': 2, 'y': 5}를, 자바 모델은 목표 4에서 {x=3, y=9}를, 끝까지 재생하면 {y=9}를 출력합니다.

자주 하는 오해

커밋하면 데이터 파일에 바로 쓰인다 — 커밋은 로그가 디스크에 닿으면 끝나고, 데이터 파일은 나중에 체크포인트(와 백그라운드 라이터)가 반영합니다.
복제가 있으면 백업이 필요 없다 — 복제는 실수로 지운 데이터도 그대로 따라 지웁니다. 복제는 가용성에, 백업은 되돌리기에 쓰는 것입니다.
백업은 받아 두기만 하면 된다 — 복구를 실제로 해 보지 않은 백업은 쓸 수 있는지 확신하기 어렵습니다. 정기적으로 복구를 시험하는 것이 좋습니다.
WAL이 있으면 어느 시점으로든 복구할 수 있다 — 베이스 백업 이후의 WAL이 끊기지 않고 보관되어 있어야 하고, 보관 기간 밖의 시점으로는 갈 수 없습니다.
복구하면 커밋하지 않은 변경도 살아난다 — 복구가 끝나면 커밋된 변경만 결과에 보이고, 커밋하지 못한 변경은 보이지 않습니다.

면접에서 이렇게 물어요

면접 질문

WAL의 핵심 원칙은?

데이터 파일을 변경하기 전에 변경 내용을 먼저 로그에 기록한다는 것입니다. 로그는 해당 데이터 페이지보다 먼저 디스크에 있어야 합니다.

면접 질문

WAL 덕분에 가능한 것은?

장애 뒤 로그를 재생(redo)해 커밋된 변경을 복구할 수 있습니다. 데이터 파일에 반영되지 못한 커밋 내용도 로그에는 남아 있기 때문입니다.

면접 질문

PITR이란?

베이스 백업과 WAL 아카이브로 원하는 특정 시점의 상태로 복구하는 방식입니다. 잘못된 DELETE 직전으로 되돌리는 것이 대표적인 사용 예입니다.

면접 질문

논리 백업과 물리 백업은 어떻게 다른가요?

논리 백업은 SQL 등으로 데이터를 내보내 이식성이 좋지만 복원 때 SQL을 다시 실행하고 인덱스를 다시 만들어야 하고 PITR에는 쓸 수 없습니다. 물리 백업은 파일을 복사하므로 PITR이 가능하지만 같은 메이저 버전과 머신 아키텍처에서 클러스터 전체 단위로만 복원합니다.

활용 분야

장애 복구 — 서버 비정상 종료 뒤 재시작만으로 커밋된 데이터가 복원됩니다.
사람의 실수 되돌리기 — 잘못된 DELETE나 UPDATE 직전 시점으로 PITR합니다.
복제 서버 구성 — WAL을 다른 서버로 보내 대기 서버를 최신 상태로 유지합니다.

함께 보면 좋은 토픽

트랜잭션 — 커밋과 내구성(ACID의 D)을 봅니다.
복제 — WAL을 전달해 다른 서버에 복사하는 방식을 봅니다.
MVCC — 복구 뒤 미커밋 변경이 보이지 않는 이유와 이어집니다.
CAP 이론 — 동기 복제와 가용성의 균형을 이어서 봅니다.

참고 자료

공식 문서 — PostgreSQL: WAL Configuration
공식 문서 — PostgreSQL: SQL Dump
공식 문서 — PostgreSQL: pg_basebackup
논문 — Mohan, C., Haderle, D., Lindsay, B., Pirahesh, H., Schwarz, P., "ARIES: A Transaction Recovery Method Supporting Fine-Granularity Locking and Partial Rollbacks Using Write-Ahead Logging", ACM Transactions on Database Systems 17(1), 1992

SQL — PostgreSQL의 WAL 설정과 PITR 준비

SHOW wal_level;                              -- WAL에 기록하는 정보의 양 (기본 replica)
SHOW synchronous_commit;                    -- on: 커밋 때 WAL을 디스크에 기록하고 응답
SELECT pg_current_wal_lsn();               -- 현재 WAL 쓰기 위치
CHECKPOINT;                                 -- 체크포인트를 바로 실행 (보통은 자동)

-- postgresql.conf: WAL 아카이브 켜기
-- archive_mode = on
-- archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'

-- 셸: 베이스 백업
-- pg_basebackup -D /backup/base -Ft -z -P

-- 복구할 때 (postgresql.conf)
-- restore_command = 'cp /archive/%f %p'
-- recovery_target_time = '2026-10-06 14:30:00'
-- 데이터 디렉터리에 recovery.signal 파일을 만들고 서버를 시작

PostgreSQL은 모든 변경을 WAL에 먼저 기록하며, synchronous_commit이 on이면 커밋 때 WAL을 디스크에 기록한 뒤 응답합니다. CHECKPOINT는 더티 페이지를 데이터 파일에 쓰는 체크포인트를 즉시 실행하며 superuser 또는 pg_checkpoint 롤만 실행할 수 있고, 평소에는 서버가 자동으로 실행합니다. 동기 대기 서버를 설정하면 on은 그 서버의 기록까지 기다립니다. 시각화의 WAL 원칙 탭이 보여 주는 순서입니다. PITR을 쓰려면 archive_mode와 archive_command로 WAL을 보관하고, pg_basebackup으로 베이스 백업을 받아 둡니다. 복구할 때는 베이스 백업을 복원한 뒤 restore_command로 보관한 WAL을 가져오고 recovery_target_time으로 멈출 시점을 정해, recovery.signal 파일을 만들고 서버를 시작합니다(PostgreSQL 12 이상). 경로와 시각은 환경마다 다르고, 자세한 절차는 PostgreSQL 문서를 따라야 합니다.

Python — WAL과 장애 복구 모델

class Db:
    def __init__(self):
        self.disk = {'x': 1, 'y': 5}          # 데이터 파일
        self.wal = []                         # 디스크의 WAL
        self.mem = dict(self.disk)            # 메모리 버퍼
        self.ckpt = 0                         # 마지막 체크포인트 이후 WAL 시작 위치

    def write(self, tx, key, value):
        self.wal.append((tx, 'SET', key, self.mem[key], value))   # 로그를 먼저 기록
        self.mem[key] = value                                     # 그 다음 메모리 페이지 변경

    def commit(self, tx):
        self.wal.append((tx, 'COMMIT'))       # 커밋 기록이 디스크에 닿으면 커밋 완료

    def checkpoint(self):
        self.disk = dict(self.mem)            # 더티 페이지를 데이터 파일에 기록
        self.ckpt = len(self.wal)

    def crash_and_recover(self):
        self.mem = dict(self.disk)            # 메모리는 사라지고 데이터 파일만 남음
        committed = {r[0] for r in self.wal[self.ckpt:] if r[1] == 'COMMIT'}
        for r in self.wal[self.ckpt:]:        # 체크포인트 이후 WAL 재생(redo)
            if r[1] == 'SET' and r[0] in committed:
                self.mem[r[2]] = r[4]
        return self.mem

db = Db()
db.write('T1', 'x', 2)
db.commit('T1')                               # T1 커밋 (데이터 파일에는 아직 x = 1)
db.write('T2', 'y', 9)                        # T2는 커밋하지 못한 채 장애
print('장애 직전:', db.mem, '데이터 파일:', db.disk)
print('복구 결과:', db.crash_and_recover())
print('WAL:', db.wal)
# 장애 직전: {'x': 2, 'y': 9} 데이터 파일: {'x': 1, 'y': 5}
# 복구 결과: {'x': 2, 'y': 5}
# WAL: [('T1', 'SET', 'x', 1, 2), ('T1', 'COMMIT'), ('T2', 'SET', 'y', 5, 9)]

WAL의 순서를 옮긴 장난감 모델입니다. write는 로그를 먼저 쓰고 메모리를 바꾸며, commit은 커밋 기록을 남깁니다. T1이 커밋하고 T2가 커밋하지 못한 채 장애가 나면 메모리의 x = 2, y = 9는 사라지고 데이터 파일에는 옛 값 x = 1, y = 5만 남습니다. 복구는 체크포인트 이후의 WAL을 재생하되 이 모델은 설명을 위해 커밋 기록이 있는 T1만 반영하므로 x = 2, y = 5가 됩니다. 실제 PostgreSQL은 로그를 모두 재생한 뒤 커밋되지 않은 변경을 MVCC 규칙으로 보이지 않게 둡니다. 시각화의 장애 복구 탭과 같은 결과입니다. 실제 데이터베이스는 페이지 단위의 로그, 일련번호(LSN), 동시에 실행되는 여러 트랜잭션을 함께 다루므로 훨씬 복잡합니다.

Java — 베이스 백업에 WAL을 재생하는 PITR 모델

record Wal(int t, String op, String key, Integer val) {}

static Map<String, Integer> restore(Map<String, Integer> base, List<Wal> archive, int target) {
    Map<String, Integer> db = new TreeMap<>(base);          // 베이스 백업을 복원
    for (Wal w : archive) {
        if (w.t() > target) break;                          // 복구 목표 시점에서 멈춤
        if (w.op().equals("SET")) db.put(w.key(), w.val());
        else db.remove(w.key());
    }
    return db;
}

Map<String, Integer> base = Map.of("x", 1, "y", 5);       // t = 0의 베이스 백업
List<Wal> archive = List.of(
    new Wal(1, "SET", "x", 2), new Wal(3, "SET", "y", 9),
    new Wal(4, "SET", "x", 3), new Wal(5, "DELETE", "x", null));
System.out.println("t=4까지: " + restore(base, archive, 4));   // t=4까지: {x=3, y=9}
System.out.println("끝까지: " + restore(base, archive, 8));      // 끝까지: {y=9}

PITR의 핵심을 옮긴 모델입니다. 베이스 백업(x = 1, y = 5)을 복원한 뒤 WAL을 시각 순서대로 재생하되 목표 시점을 넘으면 멈춥니다. 목표를 t = 4로 하면 x = 3, y = 9가 되어 t = 5의 DELETE가 반영되지 않은 사고 직전 상태가 되고, 끝까지 재생하면 x가 지워진 {y=9}가 됩니다. 시각화의 PITR 탭이 보여 주는 결과입니다. 실제 PITR은 WAL 레코드를 파일 단위로 아카이브에서 가져와 순서대로 재생하며, 시각 외에 트랜잭션 번호나 위치 같은 값으로도 멈출 지점을 정할 수 있습니다. 이 코드는 개념 설명용입니다.

Q1

WAL(Write-Ahead Logging)의 핵심 원칙은?

"Ahead" — 먼저 기록한다는 뜻입니다.
WAL은 데이터 페이지를 디스크에 쓰기 전에 그 변경 내용을 로그에 먼저 기록합니다. 장애가 나도 로그를 보고 변경을 다시 적용하거나 취소할 수 있습니다.

Q2

WAL 덕분에 가능해지는 것은?

디스크에 반영되지 못한 커밋 내용은 로그에는 남아 있습니다.
커밋된 변경은 WAL에 먼저 기록되어 있으므로, 서버가 비정상 종료돼도 재시작 시 로그를 재생해 커밋된 내용을 복구할 수 있습니다.

Q3

PITR(Point-In-Time Recovery)이란?

"잘못된 DELETE 실행 직전 시각으로 되돌리기"가 대표적인 사용 예입니다.
정기적인 베이스 백업에 이후의 WAL 로그를 이어서 재생하되, 원하는 시점에서 멈추면 그 시점의 상태로 복구할 수 있습니다. 사람의 실수로 인한 데이터 손실 복구에 유용합니다.