[DB] WAL: Write-Ahead Log, crash recovery, replication 의 토대
정의
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 회수 가능)"]
| DB | LSN 이름 | 형식 |
|---|---|---|
| PostgreSQL | LSN | X/YYYYYYYY (hex, 64bit) |
| MySQL InnoDB | Log Sequence Number | 64bit int |
| SQLite | WAL frame counter | frame 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” 정책 기반 |
| Undo | uncommitted 트랜잭션 역순 롤백 | 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 정책
| 정책 | steal | no-steal | force | no-force |
|---|---|---|---|---|
| 의미 | commit 전 page 를 disk 에 써도 됨 | 안 됨 | commit 시 모든 dirty page flush | 안 해도 됨 |
| ARIES | steal | no-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 fsync | 0 |
synchronous_commit = off | WAL 버퍼만, fsync 지연 | ~수 ms |
fsync = off | fsync 안함 | crash 시 손실 가능 |
CAUTION
fsync = off 는 벤치마크 외 절대 금지. crash 시 DB 복구 불가 가능.
Physical vs Logical WAL
| Physical WAL | Logical WAL | |
|---|---|---|
| 단위 | page 단위 변경 | 행 단위 변경 |
| 크기 | 큼 (페이지 전체) | 작음 |
| 복구 | 1:1 page restore | 행 단위 apply |
| 용도 | primary → replica (streaming) | CDC, 필터링 복제 |
| 예 | PG streaming replication | PG 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)"]
| DB | WAL 이름 | 특징 |
|---|---|---|
| PostgreSQL | WAL | physical + logical 분리. streaming replication |
| MySQL InnoDB | redo log + undo log + binlog | 3 종 분리. binlog 로 replication |
| SQLite | journal / WAL mode | journal_mode=WAL. WAL + shm 파일 |
| Redis | AOF | 옵션. append-only file |
| LMDB | (copy-on-write, no WAL) | 다른 모델 |
| RocksDB | WAL | LSM-tree 에 WAL 조합 |
| etcd / CockroachDB | Raft log | consensus + 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
- fsync 안 함 + 빠르다고 자랑 = 사실 D (Durability) 가 사라진 상태. 벤치마크 무의미.
- WAL archive 보관 부족 = PITR 시 복원 시점 없음. 최소 3-7일 보관.
- Checkpoint 너무 자주 = I/O spike. PG
checkpoint_timeout,max_wal_size튜닝. - Group commit 안 켬 (MySQL) = throughput 1/N.
innodb_flush_log_at_trx_commit+ binlog group commit. - LSN 불일치로 replica 깨짐 = MySQL GTID 또는 PG streaming replication 슬롯 관리 필요.
- WAL 세그먼트 파일 수동 삭제 = PITR / replica 연결 끊김. pg_archivecleanup 등 도구 사용.
관련 위키
- postgresql, mysql-innodb, sqlite
- mvcc
- redis-persistence (Redis 의 AOF)
- redis-replication
이 글의 용어 (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…
이 개념을 다룬 위키 페이지 (8)
- wiki[AWS] EBS vs Instance Store: 영속 vs 임시 스토리지
- wiki[DB] Cassandra: LSM-tree, quorum, eventually consistent
- wiki[DB Internals] CHAR / VARCHAR / TEXT / JSONB: 문자열 타입 비교
- wiki[DB] MySQL / InnoDB: clustered index, redo log, MVCC
- wiki[DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성
- wiki[DB] SQLite: 단일 파일, WAL 모드, edge / mobile
- wiki[DB] Transaction Isolation Levels: 완벽 가이드
- wiki[Search] ElasticSearch 기본 원리: Inverted Index, Segment, Lucene
💬 댓글