본문으로 건너뛰기
김신건의 로그

[DB] WAL: Write-Ahead Log, crash recovery, replication 의 토대

· 수정 · 📖 약 3분 · 901자/단어 #wal #durability #recovery #replication #database
WAL, Write-Ahead Log, redo log, binlog, journal, checkpoint, fsync, group commit, LSN, log sequence number, ARIES

정의

Write-Ahead Log (WAL) = 데이터 변경 전 로그를 먼저 디스크에 기록. 모든 모던 RDB / KV / 분산 시스템의 내구성 기반.

핵심 약속: 로그가 디스크에 있으면 → 데이터도 복구 가능.

왜 WAL?

flowchart LR
    Op["UPDATE row"] --> Decide{"쓰는 순서"}
    Decide -->|"WAL first"| Safe["안전: crash 시 replay"]
    Decide -->|"Data first"| Unsafe["위험: 부분 쓰기 후 crash = 불일치"]

데이터 페이지를 디스크에 쓰는 건 비쌈. 로그는 sequential append 라 빠름. commit = 로그 fsync 만 보장하면 crash 후에도 복구 가능.

일반 흐름

sequenceDiagram
    autonumber
    participant C as Client
    participant App as DB Process
    participant WAL as WAL Buffer
    participant Disk as Disk
    participant Page as Page Buffer

    C->>App: UPDATE x SET v = 42
    App->>WAL: 로그 record append
    App->>Page: page in memory 변경 (dirty)
    Note over App: COMMIT 요청
    App->>WAL: fsync WAL
    Disk-->>App: ack
    App-->>C: COMMIT OK
    Note over App: 백그라운드에서<br/>checkpointer 가 dirty page → disk
    Page-.->Disk: 나중에 flush

LSN (Log Sequence Number)

WAL 내 각 레코드의 단조 증가 번호. 모든 WAL 기반 DB 의 핵심 좌표.

flowchart LR
    WAL["WAL 파일"] --> L1["LSN 0001<br/>BEGIN tx=1"]
    WAL --> L2["LSN 0015<br/>UPDATE x=42 tx=1"]
    WAL --> L3["LSN 0032<br/>COMMIT tx=1"]
    WAL --> L4["LSN 0045<br/>BEGIN tx=2"]
    L3 --> Ckpt["Checkpoint LSN<br/>(이 이전 WAL 회수 가능)"]
DBLSN 이름형식
PostgreSQLLSNX/YYYYYYYY (hex, 64bit)
MySQL InnoDBLog Sequence Number64bit int
SQLiteWAL frame counterframe index
-- PostgreSQL: 현재 WAL 위치
SELECT pg_current_wal_lsn();
SELECT pg_walfile_name(pg_current_wal_lsn());

-- MySQL: 현재 binlog 위치
SHOW MASTER STATUS;

Crash Recovery: ARIES 알고리즘

Algorithms for Recovery and Isolation Exploiting Semantics (Mohan et al., 1992) - 모든 모던 RDB 의 복구 기반.

flowchart TB
    Crash["Crash 발생"] --> Phase1["1. Analysis Phase<br/>마지막 checkpoint 이후 로그 스캔<br/>트랜잭션 상태 + dirty page 복원"]
    Phase1 --> Phase2["2. Redo Phase<br/>모든 변경 재적용 (committed + uncommitted)"]
    Phase2 --> Phase3["3. Undo Phase<br/>uncommitted 트랜잭션 롤백"]
    Phase3 --> Ready[("서비스 가능")]
단계의미핵심 원리
Analysis마지막 checkpoint 이후 로그 스캔. 트랜잭션/dirty page 상태 복원winner (committed) / loser (uncommitted) 분류
Redo모든 변경 재적용 (committed + uncommitted 둘 다)“steal + no-force” 정책 기반
Undouncommitted 트랜잭션 역순 롤백CLR (Compensation Log Record) 기록

IMPORTANT

ARIES 는 steal + no-force 정책: commit 전 dirty page 도 disk 에 쓸 수 있음 (steal), commit 시 dirty page 강제 flush 안 함 (no-force). 이 덕분에 buffer pool 을 자유롭게 운용.

Steal / No-force 정책

정책stealno-stealforceno-force
의미commit 전 page 를 disk 에 써도 됨안 됨commit 시 모든 dirty page flush안 해도 됨
ARIESstealno-force
장점buffer pool 자유 관리commit latency 최소
복구 필요Undo 필요 (uncommitted 가 disk 에)Redo 불필요Redo 필요

Checkpoint

sequenceDiagram
    Checkpointer->>BufferPool: 모든 dirty page 식별
    loop dirty pages
        Checkpointer->>Disk: flush dirty page
    end
    Checkpointer->>WAL: checkpoint record 기록
    Note over WAL: checkpoint LSN 이전 WAL 회수 가능

checkpoint = recovery 시작점. 자주 하면 recovery 빠름 + I/O 부담. 드물게 하면 I/O 적음 + recovery 길음.

-- PostgreSQL checkpoint 튜닝
checkpoint_timeout = 15min       -- checkpoint 간격
max_wal_size = 4GB               -- WAL 최대 크기 (초과 시 강제 checkpoint)
checkpoint_completion_target = 0.9  -- checkpoint I/O 를 interval 의 90% 분산

-- 수동 checkpoint
CHECKPOINT;

fsync 의 비용과 Group Commit

sequenceDiagram
    T1->>DB: COMMIT
    T2->>DB: COMMIT
    T3->>DB: COMMIT
    DB->>DB: WAL buffer 모음
    DB->>Disk: 한 번의 fsync (3 트랜잭션 동시)
    DB-->>T1: OK
    DB-->>T2: OK
    DB-->>T3: OK

Group commit: 동시 commit 들을 한 fsync 로 묶음. throughput 큰 향상.

정책의미손실
fsync = on매 commit fsync0
synchronous_commit = offWAL 버퍼만, fsync 지연~수 ms
fsync = offfsync 안함crash 시 손실 가능

CAUTION

fsync = off벤치마크 외 절대 금지. crash 시 DB 복구 불가 가능.

Physical vs Logical WAL

Physical WALLogical WAL
단위page 단위 변경행 단위 변경
크기큼 (페이지 전체)작음
복구1:1 page restore행 단위 apply
용도primary → replica (streaming)CDC, 필터링 복제
PG streaming replicationPG logical replication, Debezium
-- PostgreSQL 논리적 복제 슬롯
SELECT pg_create_logical_replication_slot('myslot', 'pgoutput');
SELECT * FROM pg_logical_slot_get_changes('myslot', NULL, NULL);

DB별 WAL 구현 비교

flowchart TB
    WAL["WAL 개념"] --> PG["PostgreSQL WAL<br/>physical + logical<br/>streaming replication"]
    WAL --> MY["MySQL InnoDB<br/>Redo Log (crash recovery)<br/>Binlog (replication)<br/>Undo Log (MVCC)"]
    WAL --> SQ["SQLite WAL<br/>journal_mode=WAL<br/>WAL file + shm"]
    WAL --> MG["MongoDB<br/>WiredTiger Journal<br/>oplog (replica set)"]
DBWAL 이름특징
PostgreSQLWALphysical + logical 분리. streaming replication
MySQL InnoDBredo log + undo log + binlog3 종 분리. binlog 로 replication
SQLitejournal / WAL modejournal_mode=WAL. WAL + shm 파일
RedisAOF옵션. append-only file
LMDB(copy-on-write, no WAL)다른 모델
RocksDBWALLSM-tree 에 WAL 조합
etcd / CockroachDBRaft logconsensus + WAL

MySQL InnoDB 구현 차이

MySQL 은 WAL 이 세 가지:

flowchart LR
    TX["트랜잭션"] --> Redo["Redo Log<br/>(InnoDB crash recovery)"]
    TX --> Undo["Undo Log<br/>(MVCC + rollback)"]
    TX --> Binlog["Binlog<br/>(replication + PITR)"]
    Redo & Binlog -->|"XA 2PC"| Commit["COMMIT 완료"]
  • Redo + Binlog 를 2PC (two-phase commit) 로 원자 커밋 → split-brain 방지.
  • innodb_flush_log_at_trx_commit = 1 (기본) = Redo fsync per commit.
  • sync_binlog = 1 = Binlog fsync per commit.

SQLite WAL 구현

sequenceDiagram
    participant W as Writer
    participant WAL as -wal file
    participant SHM as -shm file
    participant DB as .db file

    W->>WAL: append changed pages
    SHM->>SHM: update WAL index (readers 공유)
    Note over W: commit = WAL fsync
    W->>DB: checkpoint (WAL -> DB, 1000 frames 기본)

WAL → Replication

flowchart LR
    Primary --> WAL[("WAL")]
    WAL -->|streaming| R1["Replica 1"]
    WAL -->|streaming| R2["Replica 2"]
    WAL -->|archive| Archive[("S3 archive")]
    Archive -->|PITR| Restore["Point-in-Time Recovery"]
  • Physical replication: WAL 그대로 전송
  • Logical replication: 행 단위 변경 (필터링 / 변환 가능)
  • Point-in-Time Recovery: archive + WAL = 임의 시점 복원

자세한 건 postgresql / mysql-innodb 참고.

fsync 만 = 안전?

flowchart LR
    App["App"] -->|write| OS["OS Page Cache"]
    OS -->|fsync| Disk["Disk Cache"]
    Disk -->|"Battery / FUA"| Platter["Persistent Storage"]

Disk cache 까지가 전제. power loss 시 disk cache 손실battery-backed cache 또는 SSD 의 power-loss-protection (PLP) 필요.

흔한 함정

WARNING

  1. fsync 안 함 + 빠르다고 자랑 = 사실 D (Durability) 가 사라진 상태. 벤치마크 무의미.
  2. WAL archive 보관 부족 = PITR 시 복원 시점 없음. 최소 3-7일 보관.
  3. Checkpoint 너무 자주 = I/O spike. PG checkpoint_timeout, max_wal_size 튜닝.
  4. Group commit 안 켬 (MySQL) = throughput 1/N. innodb_flush_log_at_trx_commit + binlog group commit.
  5. LSN 불일치로 replica 깨짐 = MySQL GTID 또는 PG streaming replication 슬롯 관리 필요.
  6. WAL 세그먼트 파일 수동 삭제 = PITR / replica 연결 끊김. pg_archivecleanup 등 도구 사용.

관련 위키

이 글의 용어 (6개)
[DB Internals] MVCC: Multi-Version Concurrency Controldatabase-internals
정의 MVCC (Multi-Versioning Concurrency Control) 는 트랜잭션 간 격리를 여러 버전의 row 로 구현. 동시 read / write 가 락 없이…
[DB] MySQL / InnoDB: clustered index, redo log, MVCCdatabase-internals
정의 MySQL + InnoDB (기본 스토리지 엔진) 의 clustered index + redo log 기반 행 기반 RDBMS. 웹 / SaaS 의 가장 흔한 backbon…
[DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성database-internals
정의 PostgreSQL 은 오픈소스 ORDBMS. 1986 UC Berkeley POSTGRES 의 후예. MVCC, 확장 가능 타입, JSONB, full-text searc…
[DB] SQLite: 단일 파일, WAL 모드, edge / mobiledatabase-internals
정의 SQLite 는 세계에서 가장 많이 배포된 DB (모든 스마트폰, 브라우저, OS). 서버 없이 라이브러리, 단일 파일, zero-config. embedded + edge…
[Redis] Persistence: RDB / AOF / Hybriddatabase-internals
정의 Redis Persistence 는 in-memory 데이터를 디스크 로 지속화하는 메커니즘. 세 가지 옵션: 1. RDB (Redis Database file): 주기적 …
[Redis] Replication & Sentinel: 비동기 복제와 자동 Failoverdatabase-internals
정의 Redis Replication 은 primary (master) → replica (slave) 방향의 비동기 단방향 복제. 읽기 부하 분산 과 고가용성 의 토대. Red…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기