PostgreSQL WAL·복제 운영 심화

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 생성이 많은 쿼리를 식별할 수 있다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux