4.2 WAL과 체크포인트
WAL(Write-Ahead Log)은 PostgreSQL의 내구성·복구·복제 모두의 기반입니다. 모든 데이터 변경은 데이터 페이지를 디스크에 쓰기 전에 WAL에 먼저 기록됩니다. crash 시 마지막 체크포인트 이후의 WAL을 재생해 일관된 상태를 복원합니다. WAL의 작성·flush·체크포인트가 협력해 동작하는 방식을 봅니다.
WAL 작성 → flush → 체크포인트 흐름
flowchart LR
BE["backend"]
WB["WAL buffers<br/>(shared memory)"]
PGWAL["pg_wal/<br/>16MB 세그먼트 파일"]
ARCH["archive 저장소<br/>(S3, NFS, ...)"]
REPL["standby"]
BE -- "WAL record append" --> WB
WB -- "fsync (commit·timer)" --> PGWAL
PGWAL -- "archive_command" --> ARCH
PGWAL -- "streaming" --> REPL
classDef be fill:#fef3c7,stroke:#b45309,color:#78350f
classDef mem fill:#ede9fe,stroke:#6d28d9,color:#3b0764
classDef disk fill:#d1fae5,stroke:#047857,color:#064e3b
classDef ext fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
class BE be
class WB mem
class PGWAL disk
class ARCH,REPL ext
WAL의 단위
| 단위 | 크기 |
|---|---|
| WAL record | 가변. 변경된 행 1개, 페이지 1개 등 단위 |
| WAL page | 8KB (--with-wal-blocksize) |
| WAL segment | 16MB 기본 (--wal-segsize로 변경) |
| LSN (Log Sequence Number) | 64-bit. WAL 안의 한 바이트 오프셋 |
LSN은 텍스트로 0/3000028 같은 형식. 0/3000028 < 0/3000050처럼 비교 가능합니다.
SELECT pg_current_wal_lsn();
-- pg_current_wal_lsn
-- --------------------
-- 0/3A1F8E0트랜잭션 commit 시 동작
sequenceDiagram
participant Backend
participant SB as shared_buffers
participant WB as WAL buffers
participant Disk
Backend->>SB: 데이터 페이지 갱신 (dirty bit on)
Backend->>WB: WAL record append
Note over Backend: COMMIT 시점
Backend->>WB: COMMIT record append
Backend->>Disk: WAL fsync (pg_wal에 안전하게 기록)
Disk-->>Backend: fsync OK
Backend-->>Backend: 사용자에게 COMMIT 응답
Note over SB,Disk: 데이터 페이지는<br/>나중에 checkpointer/bgwriter가 flush
핵심: COMMIT 응답은 WAL이 디스크에 안전히 들어간 시점에 보냅니다. 데이터 페이지는 아직 메모리에만 있어도 OK — crash가 나면 WAL 재생으로 복원되니까.
full_page_writes
체크포인트 직후 첫 변경되는 페이지는 변경 부분이 아닌 페이지 전체를 WAL에 기록합니다.
| 이유 | torn page 방어 — OS/디스크가 8KB를 부분만 쓰고 죽으면 페이지가 망가지는 사고 방지 |
SHOW full_page_writes; -- on (기본, 운영 권장)trade-off: WAL 양이 늘어남(특히 체크포인트 직후). pgBackRest/Barman 같은 백업 도구도 full page를 기대합니다.
full_page_writes = off는 운영 금지. 미세한 성능 이득을 위해 일관성을 포기하는 설정합니다. 일부 가이드에서 “특정 파일시스템에서만 OK"라고 하지만 운영 환경에서는 켜고 시작합니다.체크포인트
체크포인트는 “이 시점 이전의 변경은 모두 데이터 파일에 안전히 들어갔다"는 마커를 만들고, 그 시점 이전의 WAL을 안전하게 잘라낼 수 있게 합니다.
sequenceDiagram
participant CP as checkpointer
participant SB as shared_buffers
participant Data as 데이터 파일
participant WAL
Note over CP: timed·wal_size·CHECKPOINT 명령으로 트리거
CP->>WAL: CHECKPOINT 시작 record
CP->>SB: 모든 dirty page 식별
loop 각 dirty page
CP->>Data: 페이지 flush (fsync 묶음)
end
CP->>WAL: CHECKPOINT 완료 record
Note over CP,WAL: 그 이전의 WAL 세그먼트는 archive 후 재활용 가능
체크포인트 트리거
| 트리거 | 설정 |
|---|---|
| 시간 기반 | checkpoint_timeout 기본 5분 |
| WAL 크기 기반 | max_wal_size 기본 1GB (PG 9.5+) |
| 명시 명령 | CHECKPOINT; (슈퍼유저, 또는 pg_basebackup 등) |
| 종료 시 | pg_ctl stop 또는 클러스터 정지 |
max_wal_size에 도달하기 전에 체크포인트가 일어나도록 checkpoint_timeout이 잡혀 있어야 합니다. 반대로 timeout 안에 max_wal_size를 채우면 그건 “체크포인트가 충분히 자주 안 일어나고 있다“는 신호입니다.
checkpoint_completion_target
체크포인트는 dirty page를 한꺼번에 fsync하지 않습니다. checkpoint_completion_target(기본 0.9, PG 11+) 만큼 시간에 걸쳐 분산해 씁니다.
checkpoint_timeout = 5min
checkpoint_completion_target = 0.9
→ 체크포인트가 4.5분에 걸쳐 천천히 flush이래서 체크포인트의 I/O가 한 점에 몰리지 않고 평탄해집니다.
체크포인트 진단
SELECT * FROM pg_stat_bgwriter;핵심 컬럼:
| 컬럼 | 의미 |
|---|---|
checkpoints_timed | 시간 기반 체크포인트 횟수 |
checkpoints_req | WAL 기반(요청) 체크포인트 횟수 — 이게 timed보다 크면 max_wal_size 키워야 |
checkpoint_write_time / checkpoint_sync_time | 평균 쓰기·sync 시간 |
PG 17부터 pg_stat_checkpointer 뷰로 더 정밀하게 추적할 수 있습니다.
권장 출발 설정:
checkpoint_timeout = 15min
max_wal_size = 8GB
checkpoint_completion_target = 0.9쓰기 부하 큰 시스템은 max_wal_size를 더 키워(16~64GB) 체크포인트 빈도를 낮춥니다.
WAL 세그먼트와 pg_wal
새 트랜잭션이 들어와 WAL이 늘어나면 16MB 세그먼트 파일이 생성됩니다.
파일 이름: <timeline_id>_<segment_high>_<segment_low> 24자리 16진수. 예: 00000001000000000000003A.
ls -lh $PGDATA/pg_wal/
# 00000001000000000000003A
# 00000001000000000000003B ...보관 정책
- 체크포인트 시 더 이상 필요 없는 세그먼트는 재사용 또는 삭제
archive_mode = on이면archive_command로 외부 복사 성공 후에만 삭제- replication slot이 잡고 있으면 삭제 불가 — 잊혀진 슬롯이
pg_wal/을 가득 채우는 사고가 흔하다
pg_wal이 차오를 때
| 원인 | 확인 |
|---|---|
archive_command 실패 | pg_stat_archiver의 last_failed_time |
| replication slot이 hold | pg_replication_slots 의 wal_status='reserved'/'extended' |
| 매우 큰 트랜잭션이 commit 안 됨 | pg_stat_activity의 오래된 active xid |
max_wal_size가 너무 작아 한 번에 잘림 | 임시적 — 보통 자기 정상화 |
pg_wal/을 수동으로 절대 지우지 말 것. 한 파일만 삭제해도 클러스터가 부팅 불가 상태가 됩니다. 정리 도구는 pg_archivecleanup, pgBackRest stanza-update, replication slot 삭제만 사용합니다.동기 commit과 비동기 commit
다음 절(4.4)에서 자세히 보지만, 한 줄 요약:
synchronous_commit = on(기본): commit 응답 전 WAL fsync 완료 대기 — 안전, 약간 느림synchronous_commit = off: commit 응답 즉시, WAL fsync는 백그라운드 — 빠르지만 직전 트랜잭션 손실 가능
정리
- 모든 변경은 데이터 파일 전에 WAL에 기록
- COMMIT의 진짜 비용은 WAL fsync
full_page_writes = on이 표준 — torn page 방어- 체크포인트는 시간(
checkpoint_timeout)·크기(max_wal_size)·명령으로 트리거 checkpoint_completion_target으로 체크포인트 I/O를 평탄화pg_stat_bgwriter/pg_stat_checkpointer로 점검pg_wal/은 archive·replication slot과 깊이 얽혀 있습니다. 수동 삭제 절대 금지
다음 절(4.3)에서는 UPDATE의 성능을 좌우하는 HOT 업데이트와 index-only scan을 봅니다.