PostgreSQL WAL이란?
WAL(Write-Ahead Log)은 PostgreSQL이 데이터 변경을 디스크에 기록하는 핵심 메커니즘이다. 모든 변경 사항은 실제 데이터 파일에 반영되기 전에 WAL 파일에 먼저 기록된다. 이를 통해 장애 발생 시 WAL을 재생(replay)하여 데이터를 복구할 수 있다.
WAL은 단순한 복구 도구를 넘어 스트리밍 복제, 논리 복제, 포인트 인 타임 복구(PITR)의 기반이기도 하다. 이 글에서는 WAL 구조, 설정 튜닝, 물리 복제와 논리 복제의 차이, 그리고 운영 모니터링까지 심화하여 다룬다.
WAL 동작 원리
┌─────────────────────────────────────────────┐
│ PostgreSQL │
│ │
│ 1. BEGIN; UPDATE ... │
│ ↓ │
│ 2. 변경 내용을 WAL Buffer에 기록 │
│ ↓ │
│ 3. COMMIT 시 WAL Buffer → WAL 파일(디스크) │
│ ↓ (fsync) │
│ 4. COMMIT 완료 응답 │
│ ↓ │
│ 5. Checkpointer가 주기적으로 │
│ 데이터 파일에 반영 (비동기) │
└─────────────────────────────────────────────┘
WAL 파일 구조:
pg_wal/
├── 000000010000000000000001 (16MB 세그먼트)
├── 000000010000000000000002
├── 000000010000000000000003
└── archive_status/
핵심 원리: COMMIT 시 WAL만 디스크에 쓰면(순차 쓰기) 되므로, 데이터 파일(랜덤 쓰기)보다 훨씬 빠르다. 데이터 파일은 Checkpoint 시 비동기로 반영한다.
WAL 핵심 설정
# postgresql.conf
# WAL 레벨: minimal → replica → logical
wal_level = replica
# - minimal: 복구만 가능
# - replica: 스트리밍 복제 지원 (기본값)
# - logical: 논리 복제 지원 (가장 많은 정보 기록)
# WAL 세그먼트 크기 (initdb 시 결정, 기본 16MB)
# --wal-segsize=64 (대량 쓰기 워크로드에서 증가 고려)
# WAL 버퍼 크기
wal_buffers = 64MB # shared_buffers의 1/32, 최소 64kB
# 동기 커밋
synchronous_commit = on
# on: COMMIT 시 WAL 디스크 쓰기 보장 (안전, 느림)
# off: WAL 쓰기를 비동기로 (빠름, 최대 ~600ms 데이터 유실 가능)
# remote_apply: 복제본까지 적용 완료 보장
# Checkpoint 설정
checkpoint_timeout = 10min # Checkpoint 주기
checkpoint_completion_target = 0.9 # 전체 시간의 90%에 걸쳐 쓰기 분산
max_wal_size = 4GB # 이 크기 초과 시 강제 Checkpoint
min_wal_size = 1GB # 최소 유지 WAL 크기
# WAL 압축
wal_compression = lz4 # PostgreSQL 15+, WAL 크기 절감
synchronous_commit 옵션 비교
| 설정 | WAL 디스크 | 복제본 전달 | 성능 | 안전성 |
|---|---|---|---|---|
| on | ✅ | – | 보통 | 높음 |
| off | 비동기 | – | 빠름 | 낮음 |
| remote_write | ✅ | OS 캐시 | 느림 | 높음 |
| remote_apply | ✅ | 적용 완료 | 가장 느림 | 최고 |
WAL 아카이빙과 PITR
Point-In-Time Recovery(PITR)는 특정 시점으로 데이터베이스를 복원하는 기능이다. WAL 아카이빙을 설정하면 WAL 세그먼트가 재활용되기 전에 별도 위치에 보관된다.
# WAL 아카이빙 활성화
archive_mode = on
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
# 또는 pgBackRest, WAL-G 사용
# archive_command = 'wal-g wal-push %p'
# PITR 복구 절차
# 1. base backup 복원
pg_basebackup -D /var/lib/postgresql/data_restore
-h primary -U replicator -Fp -Xs -P
# 2. recovery.signal 생성 및 설정
touch /var/lib/postgresql/data_restore/recovery.signal
# 3. postgresql.conf에 복구 시점 지정
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2026-04-04 14:30:00+09'
recovery_target_action = 'promote'
# 또는 특정 트랜잭션까지:
# recovery_target_xid = '12345678'
# 4. PostgreSQL 시작 → WAL 재생 → 지정 시점에서 멈춤
pg_ctl start -D /var/lib/postgresql/data_restore
스트리밍 복제 (물리 복제)
스트리밍 복제는 Primary의 WAL을 실시간으로 Standby에 전송하여 동일한 데이터 복사본을 유지한다.
# ── Primary 설정 ──
# postgresql.conf
wal_level = replica
max_wal_senders = 10 # 동시 복제 연결 수
wal_keep_size = 1GB # 복제 지연 시 유지할 WAL
hot_standby = on
# pg_hba.conf - 복제 접속 허용
host replication replicator 10.0.0.0/24 scram-sha-256
# 복제 전용 사용자 생성
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'secret';
# ── Standby 구성 ──
# 1. Base Backup으로 데이터 복사
pg_basebackup -h primary-host -U replicator
-D /var/lib/postgresql/data -Fp -Xs -P -R
# -R: standby.signal + primary_conninfo 자동 생성
# 2. 자동 생성된 설정 확인
# postgresql.auto.conf:
# primary_conninfo = 'host=primary-host user=replicator password=secret'
# 3. Standby 시작
pg_ctl start -D /var/lib/postgresql/data
# → WAL 수신 시작, 읽기 전용 쿼리 가능
동기 복제
# Primary postgresql.conf
synchronous_standby_names = 'FIRST 1 (standby1, standby2)'
# FIRST 1: standby1, standby2 중 먼저 응답하는 1대와 동기
# ANY 2: 아무 2대 이상 응답 시 COMMIT 확인
synchronous_commit = remote_apply
# remote_apply: Standby에서 적용 완료까지 대기
# → 읽기 쿼리를 Standby로 분산해도 최신 데이터 보장
# Standby 식별 (각 Standby의 postgresql.conf)
primary_conninfo = 'host=primary-host user=replicator application_name=standby1'
논리 복제 (Logical Replication)
물리 복제가 바이트 수준 복사라면, 논리 복제는 변경 사항을 SQL 수준으로 전달한다. 특정 테이블만 복제하거나, 서로 다른 PostgreSQL 버전 간 복제가 가능하다.
# Publisher (원본) 설정
wal_level = logical # 필수!
# Publication 생성
CREATE PUBLICATION my_pub FOR TABLE users, orders;
-- 또는 전체 테이블:
-- CREATE PUBLICATION my_pub FOR ALL TABLES;
-- 특정 컬럼만:
CREATE PUBLICATION partial_pub FOR TABLE users (id, name, email);
-- 필터 조건 (PostgreSQL 15+):
CREATE PUBLICATION active_pub FOR TABLE users WHERE (status = 'active');
# ── Subscriber (대상) ──
# 테이블 스키마는 미리 생성해야 함
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=publisher-host dbname=mydb user=replicator password=secret'
PUBLICATION my_pub
WITH (copy_data = true); -- 기존 데이터도 복사
-- 복제 상태 확인
SELECT * FROM pg_stat_subscription;
SELECT * FROM pg_subscription_rel;
물리 복제 vs 논리 복제
| 항목 | 물리 복제 | 논리 복제 |
|---|---|---|
| 복제 단위 | 전체 클러스터 | 테이블 단위 선택 |
| Standby 쓰기 | 읽기 전용 | 쓰기 가능 |
| 버전 호환 | 동일 메이저 버전 | 다른 버전 가능 |
| DDL 복제 | 자동 | 수동 적용 필요 |
| 지연 영향 | 대량 쓰기 시 지연 | 디코딩 오버헤드 |
| 주요 용도 | HA, 읽기 분산 | 데이터 통합, 마이그레이션 |
복제 슬롯 (Replication Slot)
복제 슬롯은 Subscriber가 아직 소비하지 않은 WAL을 삭제하지 않도록 보장한다.
-- 물리 복제 슬롯
SELECT pg_create_physical_replication_slot('standby1_slot');
-- 논리 복제 슬롯 (Subscription 생성 시 자동 생성)
SELECT pg_create_logical_replication_slot('my_slot', 'pgoutput');
-- 슬롯 상태 확인
SELECT slot_name, slot_type, active,
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes
FROM pg_replication_slots;
-- ⚠️ 비활성 슬롯은 WAL이 무한 누적! 디스크 가득 참
-- 사용하지 않는 슬롯 반드시 삭제
SELECT pg_drop_replication_slot('unused_slot');
-- 슬롯 안전장치 (PostgreSQL 15+)
max_slot_wal_keep_size = 10GB # 슬롯당 최대 WAL 유지량
운영 모니터링
-- 복제 지연 확인 (Primary에서)
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes,
reply_time
FROM pg_stat_replication;
-- Standby에서 지연 확인
SELECT now() - pg_last_xact_replay_timestamp() AS replay_lag;
-- WAL 생성 속도
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0') AS total_wal_bytes;
-- Checkpoint 통계
SELECT * FROM pg_stat_bgwriter;
-- checkpoints_timed: 정상 Checkpoint
-- checkpoints_req: 강제 Checkpoint (max_wal_size 초과)
-- → checkpoints_req가 많으면 max_wal_size 증가 필요
-- WAL 디렉토리 크기
SELECT pg_size_pretty(sum(size))
FROM pg_ls_waldir();
Prometheus 모니터링 쿼리
# postgres_exporter 메트릭
# 복제 지연 (초)
pg_replication_lag_seconds
# WAL 수신 지연 (바이트)
pg_stat_replication_pg_wal_lsn_diff
# Alertmanager 규칙
groups:
- name: postgresql-replication
rules:
- alert: ReplicationLagHigh
expr: pg_replication_lag_seconds > 30
for: 5m
labels:
severity: warning
annotations:
summary: "PostgreSQL replication lag > 30s"
- alert: InactiveReplicationSlot
expr: pg_replication_slots_active == 0
for: 1h
labels:
severity: critical
annotations:
summary: "Inactive replication slot - WAL accumulating"
Failover 자동화
# Patroni (가장 널리 쓰이는 HA 솔루션)
# patroni.yml
scope: mydb-cluster
name: node1
restapi:
listen: 0.0.0.0:8008
etcd3:
hosts: etcd1:2379,etcd2:2379,etcd3:2379
bootstrap:
dcs:
synchronous_mode: true
postgresql:
parameters:
wal_level: replica
max_wal_senders: 10
synchronous_commit: "remote_apply"
postgresql:
listen: 0.0.0.0:5432
data_dir: /var/lib/postgresql/data
authentication:
replication:
username: replicator
password: secret
# Patroni가 자동으로:
# - Leader 선출 (etcd 기반)
# - Standby 프로모션 (Failover)
# - 이전 Primary를 Standby로 재구성
# - 복제 슬롯 관리
마무리
PostgreSQL WAL은 데이터 안전성과 고가용성의 기반이다. 모든 변경이 WAL에 먼저 기록되므로 장애 시 데이터 유실 없이 복구할 수 있고, 이 WAL 스트림을 활용하여 실시간 복제와 PITR을 구현한다.
운영 핵심 체크리스트:
- wal_level: 복제 필요 시 replica 이상, 논리 복제는 logical
- Checkpoint 튜닝: checkpoints_req가 많으면 max_wal_size 증가
- 복제 슬롯 관리: 비활성 슬롯은 반드시 삭제, max_slot_wal_keep_size 설정
- 복제 지연 모니터링: pg_stat_replication + Prometheus 알럿
PostgreSQL 파티셔닝 실전 심화에서 대량 데이터 관리와 결합하면 WAL 생성량 제어에 도움이 되고, PostgreSQL pg_stat_statements 분석으로 WAL 생성이 많은 쿼리를 식별할 수 있다.