PostgreSQL 백업/복구 Part 3 — pg_basebackup과 WAL 아카이빙
Pg_dump는 PITR을 직접 지원하지 않는다. 프로덕션 재해 복구의 핵심은 Base Backup과 WAL 아카이빙의 조합이다. Base Backup은 특정 시점의 스냅샷이고, WAL 아카이빙은 그 이후의 시간을 복구 가능하게 만드는 기록이다. pg_basebackup의 옵션 선택이 복구 가능성을 좌우하며, PostgreSQL 17+의 네이티브 증분 백업(--incremental)은 대형 DB 백업 비용을 낮추는 중요한 변화다.
시리즈 구성
- Part 1 — 백업의 기초와 전략
- Part 2 — 논리적 백업 — pg_dump & pg_dumpall 실전 활용
- Part 3 — 물리적 백업 — pg_basebackup과 WAL 아카이빙 (현재 편)
- Part 4 — PITR(Point-in-Time Recovery) 구현 가이드 (연재 예정)
- Part 5 — 엔터프라이즈 백업 도구 비교 — pgBackRest vs Barman vs WAL-G (연재 예정)
- Part 6 — 백업 자동화 & 모니터링, 그리고 복구 훈련 (연재 예정)
목차
- 들어가며 — 물리적 백업이 필요한 이유
- WAL — 물리적 백업의 핵심 개념
- 전체 아키텍처 — Base Backup + WAL 아카이빙
- WAL 아카이빙 설정
- pg_basebackup — 물리적 베이스 백업
- 증분 백업 — PostgreSQL 17+
- 관련 파라미터 튜닝 가이드
- WAL 아카이빙 모니터링
- 물리적 백업 자동화 스크립트
- 논리 vs 물리 — 언제 무엇을 쓸까
- 마치며
1. 들어가며 — 물리적 백업이 필요한 이유
Part 2에서 pg_dump의 강점과 한계를 살펴봤다. 논리적 백업은 이식성과 선택적 복구에 탁월하지만, 프로덕션 재해 복구의 핵심 요구사항인 PITR(Point-in-Time Recovery)을 직접 지원하지 않는다.
물리적 백업이 반드시 필요한 상황을 생각해보자.
"오늘 오후 2시에 누군가 중요 테이블을 DROP했습니다. 마지막 pg_dump 백업은 어제 새벽 2시입니다. 12시간치 데이터가 손실됐습니다."
물리적 백업과 WAL 아카이빙 조합이 있었다면, 오후 1시 59분 시점으로 정확하게 복구할 수 있다. 이것이 PITR이며, 물리적 백업의 존재 이유다.
2. WAL — 물리적 백업의 핵심 개념
WAL(Write-Ahead Log)은 PostgreSQL이 데이터 파일에 변경을 적용하기 전 모든 변경 사항을 먼저 기록하는 트랜잭션 로그다. 클러스터 데이터 디렉토리의 pg_wal/ 서브디렉토리에 기본 16MB 단위 세그먼트 파일로 저장된다.
WAL의 역할은 두 가지다.
충돌 안전성(Crash Safety): 시스템이 비정상 종료됐을 때 WAL을 재생해 데이터베이스를 일관된 상태로 복원한다.
PITR의 기반: Base Backup에 WAL을 순서대로 재생하면 백업 이후 임의의 시점으로 데이터베이스를 복구할 수 있다.
3. 전체 아키텍처 — Base Backup + WAL 아카이빙
프로덕션 물리 백업 전략은 두 구성 요소의 조합으로 이루어진다.
Base Backup은 특정 시점의 데이터 디렉토리 전체 스냅샷이다. 이것만으로는 백업 시점 이후의 변경을 복구할 수 없다.
WAL 아카이빙은 Base Backup 이후 발생한 모든 변경 사항을 보존한다. 두 요소가 결합될 때 비로소 PITR이 가능해진다.
4. WAL 아카이빙 설정
4.1 postgresql.conf 필수 파라미터
WAL 아카이빙은 기본적으로 비활성화 상태다. 활성화하려면 서버 재시작이 필요하므로, 서비스 오픈 전에 반드시 설정해야 한다.
# postgresql.conf
# WAL 레벨: replica 이상이어야 아카이빙 및 PITR 지원
# minimal 설정 시 archive_mode 활성화 불가
wal_level = replica
# 아카이빙 활성화 (서버 재시작 필요)
archive_mode = on
# WAL 파일을 아카이브 디렉토리로 복사하는 명령
# %p: 아카이빙할 WAL 파일의 전체 경로
# %f: WAL 파일명
archive_command = 'test ! -f /var/lib/postgresql/archive/%f && cp %p /var/lib/postgresql/archive/%f'
# WAL 세그먼트가 가득 차지 않아도 강제 아카이빙하는 타임아웃 (초)
# 낮출수록 최신 WAL이 더 빨리 아카이빙되어 RPO가 단축됨
archive_timeout = 300
# WAL 크기 설정
max_wal_size = 4GB
min_wal_size = 1GB
wal_compression = on
archive_mode는 서버 재시작이 필요하지만, archive_command는 pg_reload_conf() 또는 SIGHUP으로 재시작 없이 변경할 수 있다. 아카이빙을 임시로 중단해야 할 때는 archive_mode를 끄는 것보다 archive_command를 수정하는 방식이 운영상 더 유연하다.
4.2 archive_mode: on vs always
| 모드 | 동작 |
|---|---|
on | 프라이머리에서만 아카이빙 |
always | 스탠바이 서버에서도 아카이빙. 스트리밍 복제로 받은 WAL도 아카이빙 |
스탠바이 서버가 아카이브 스토리지에 더 가깝거나, 프라이머리 장애 시에도 WAL 아카이빙을 이어가야 하는 환경에서는 always가 유용하다.
4.3 archive_command의 exit code 규칙
archive_command가 exit code 0을 반환해야만 PostgreSQL이 해당 WAL 파일을 아카이빙 성공으로 간주하고 삭제·재활용을 허용한다. non-zero 반환 시 PostgreSQL은 계속 재시도한다.
이 동작은 실무 리스크와 직결된다.
- 실패를 무조건 무시(항상 exit 0)하면 아카이빙되지 않은 WAL 파일이 삭제될 수 있다.
- 아카이브 목적지 접근 불가 상태에서 재시도가 누적되면
pg_wal/디렉토리가 채워져 디스크 풀 장애로 이어질 수 있다.
스크립트 오류 처리를 신중하게 설계해야 한다.
4.4 아카이브 디렉토리 준비
# 아카이브 디렉토리 생성 및 권한 설정
mkdir -p /var/lib/postgresql/archive
chown postgres:postgres /var/lib/postgresql/archive
chmod 700 /var/lib/postgresql/archive
# postgresql.conf 변경 후 재시작
systemctl restart postgresql
# 아카이빙 상태 확인
psql -U postgres -c "SELECT * FROM pg_stat_archiver;"
4.5 원격 아카이빙
로컬 디스크만 사용하면 서버 전체 장애 시 아카이브도 함께 손실된다. 원격 스토리지 아카이빙이 필수다.
# rsync를 이용한 원격 서버 아카이빙
archive_command = 'rsync -a %p backup_user@archive-server:/pg_archive/%f'
# AWS S3
archive_command = 'aws s3 cp %p s3://my-pg-archive/wal/%f'
# 이중 저장 스크립트 호출
archive_command = '/usr/local/bin/archive_wal.sh %p %f'
로컬과 원격을 모두 저장하는 스크립트 예시다.
#!/bin/bash
# /usr/local/bin/archive_wal.sh
# 안전한 WAL 아카이빙: 로컬 + S3 이중 저장
WAL_PATH=$1
WAL_FILE=$2
LOCAL_ARCHIVE="/var/lib/postgresql/archive"
S3_BUCKET="s3://my-pg-archive/wal"
# 로컬 복사 실패 시 즉시 중단 (exit 1 → PostgreSQL이 재시도)
cp "${WAL_PATH}" "${LOCAL_ARCHIVE}/${WAL_FILE}" || exit 1
# S3 복사 실패는 로컬 성공 이후이므로 재시도보다 경보를 우선
# exit 0으로 아카이빙 진행은 허용하되, 알림으로 후속 조치 유도
aws s3 cp "${WAL_PATH}" "${S3_BUCKET}/${WAL_FILE}" \
--only-show-errors || \
echo "[WARN] S3 upload failed for ${WAL_FILE}" >> /var/log/pg_archive.log
exit 0
S3 복사 실패 시 exit 0을 반환하는 것은 "아카이빙 중단보다 경보 후 처리"를 선택한 설계다. 팀 운영 정책에 따라 S3 실패도 exit 1로 처리해 재시도하도록 바꿀 수 있다. 어느 쪽을 선택하든 의도를 코드 주석이나 운영 문서에 명시해야 한다.
5. pg_basebackup — 물리적 베이스 백업
5.1 사전 요건
# postgresql.conf
wal_level = replica # 필수
archive_mode = on # PITR 목적이라면 필수
max_wal_senders = 3 # pg_basebackup은 복제 프로토콜 사용
-- pg_hba.conf에 복제 접속 허용 추가
-- host replication backup_user 192.168.1.0/24 scram-sha-256
-- 백업 전용 사용자 (superuser 불필요, REPLICATION 권한만)
CREATE USER backup_user WITH REPLICATION PASSWORD 'securepass';
5.2 주요 옵션
-X (WAL 포함 방식)
| 옵션 | 설명 | 권장 |
|---|---|---|
-X none | WAL 미포함 | 비권장 (복구 불가 가능성) |
-X fetch | 백업 완료 후 WAL 수집 | 조건부 (WAL 보존 보장 안됨) |
-X stream | 백업 중 WAL 실시간 스트리밍 | 권장 |
-X stream이 권장되는 이유: 백업 진행 중 생성된 WAL이 백업 완료 전에 재활용(삭제)될 위험을 차단한다.
-F (출력 형식)
| 옵션 | 설명 |
|---|---|
-Fp (plain) | 파일 그대로 복사. 복구 시 바로 사용 가능 |
-Ft (tar) | 테이블스페이스별 tar 아카이브. 전송·저장에 유리 |
--checkpoint
# 빠른 체크포인트 강제 (백업 시작 시간 단축, I/O 부하 순간 증가)
pg_basebackup ... --checkpoint=fast
# 분산 체크포인트 (기본값, I/O 부하 분산)
pg_basebackup ... --checkpoint=spread
5.3 실전 백업 레시피
# 권장: plain 포맷 + WAL 스트리밍 + 빠른 체크포인트
pg_basebackup \
-h localhost \
-U backup_user \
-D /backups/basebackup/$(date +%Y%m%d_%H%M%S) \
-Fp \
-Xs \
-P \
--checkpoint=fast
# tar 포맷 + 압축 (전송·스토리지 비용 절감)
pg_basebackup \
-h localhost \
-U backup_user \
-D /backups/basebackup/$(date +%Y%m%d) \
-Ft \
-z \
-Xs \
-P \
--checkpoint=fast
# 백업 완료 후 무결성 검증 (PostgreSQL 13+)
pg_verifybackup /backups/basebackup/20260414
5.4 pg_verifybackup — 백업 무결성 검증
PostgreSQL 13에서 도입된 pg_verifybackup은 backup_manifest 파일을 기반으로 백업의 무결성을 검증한다.
# 기본 검증
pg_verifybackup /backups/basebackup/20260414
# WAL 검증 건너뛰기 (데이터 파일만 확인, 빠른 검증)
pg_verifybackup --no-parse-wal /backups/basebackup/20260414
backup_manifest가 없는 PostgreSQL 12 이하 버전 백업에는 사용할 수 없다.
6. 증분 백업 — PostgreSQL 17+
6.1 개요
PostgreSQL 16까지는 pg_basebackup이 전체 백업(Full Backup)만 지원했다. 증분 백업은 pgBackRest, Barman 같은 서드파티 도구에 의존해야 했다.
PostgreSQL 17부터 pg_basebackup에 네이티브 증분 백업(--incremental)이 도입됐다. 이전 백업 이후 변경된 블록만 저장해 백업 시간과 스토리지를 줄일 수 있다.
6.2 활성화 요건
증분 백업을 사용하려면 summarize_wal 파라미터를 활성화해야 한다. PostgreSQL 17에서 새로 도입된 파라미터로, WAL 요약기(WAL Summarizer) 프로세스를 가동한다.
# postgresql.conf (PostgreSQL 17+)
summarize_wal = on # 기본값: off, 서버 재시작 필요
6.3 증분 백업 워크플로
# 1단계: 전체 백업 (기준점)
pg_basebackup \
-h localhost \
-U backup_user \
-D /backups/full_20260414 \
-Ft -Xs -P --checkpoint=fast
# backup_manifest 파일이 /backups/full_20260414/backup_manifest 에 생성됨
# 2단계: 첫 번째 증분 백업 (전체 백업 기준)
pg_basebackup \
--incremental=/backups/full_20260414/backup_manifest \
-h localhost \
-U backup_user \
-D /backups/incr_20260415 \
-Ft -Xs -P --checkpoint=fast
# 3단계: 두 번째 증분 백업 (직전 증분 백업 기준)
pg_basebackup \
--incremental=/backups/incr_20260415/backup_manifest \
-h localhost \
-U backup_user \
-D /backups/incr_20260416 \
-Ft -Xs -P --checkpoint=fast
6.4 복원: pg_combinebackup
증분 백업은 단독으로 복원할 수 없다. 전체 백업과 연결된 증분 체인을 합성하는 pg_combinebackup이 필요하다.
# 전체 + 증분1 + 증분2를 합성하여 복원 가능한 디렉토리 생성
pg_combinebackup \
/backups/full_20260414 \
/backups/incr_20260415 \
/backups/incr_20260416 \
-o /restore/combined_datadir
# 합성된 디렉토리로 PostgreSQL 시작
pg_ctl -D /restore/combined_datadir start
pg_combinebackup에 백업을 나열할 때는 반드시 시간 순서대로 전달해야 한다. 순서가 틀리면 오류가 발생한다. 증분 체인의 첫 번째는 반드시 Full Backup이어야 한다.
현재 PostgreSQL 17+의 네이티브 증분 백업은 pgBackRest나 Barman에 비해 기능이 제한적일 수 있다. 기능 요건을 공식 문서와 비교한 후 도입 여부를 결정한다.
7. 관련 파라미터 튜닝 가이드
# WAL 아카이빙 & 물리 백업 관련 핵심 파라미터
# WAL 레벨 (replica 이상 필수)
wal_level = replica
# 아카이빙
archive_mode = on
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
archive_timeout = 300 # 5분마다 미완성 WAL 강제 아카이빙
# WAL 크기 제어
max_wal_size = 4GB # 체크포인트 간 최대 WAL 크기
min_wal_size = 1GB # 재활용을 위한 최소 WAL 크기 확보
wal_compression = on # WAL 압축 (CPU 오버헤드 vs 스토리지 절감)
wal_buffers = -1 # 자동 설정 (shared_buffers의 1/32, 최대 16MB)
# 체크포인트
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9
# 복제 연결 (pg_basebackup용)
max_wal_senders = 5
wal_keep_size = 1GB # 스탠바이가 WAL을 따라잡을 여유 확보
# 증분 백업 (PostgreSQL 17+)
summarize_wal = on
8. WAL 아카이빙 모니터링
WAL 아카이빙 실패는 조용히 진행된다. pg_wal/ 디렉토리가 쌓이다가 디스크 풀 장애로 이어질 수 있다. 반드시 모니터링을 설정해야 한다.
-- 아카이빙 상태 전체 확인
SELECT
archived_count,
last_archived_wal,
last_archived_time,
failed_count,
last_failed_wal,
last_failed_time,
now() - last_archived_time AS lag
FROM pg_stat_archiver;
| 컬럼 | 의미 |
|---|---|
failed_count | 누적 아카이빙 실패 수. 0이 아니면 즉시 조사 필요 |
last_failed_time | 마지막 실패 시각 |
lag | 마지막 성공 아카이빙으로부터 경과 시간 |
# pg_wal 디렉토리 크기 모니터링 (비정상적으로 커지면 경보 필요)
du -sh $(psql -U postgres -tAc "SHOW data_directory")/pg_wal
아카이빙 실패 알림 스크립트 예시다.
#!/bin/bash
# WAL 아카이빙 상태 점검 크론 (5분마다 실행 권장)
FAILED=$(psql -U postgres -tAc \
"SELECT failed_count FROM pg_stat_archiver")
if [ "$FAILED" -gt 0 ]; then
LAST_FAIL=$(psql -U postgres -tAc \
"SELECT last_failed_wal || ' at ' || last_failed_time FROM pg_stat_archiver")
echo "WAL 아카이빙 실패 감지: ${LAST_FAIL}" | \
mail -s "[ALERT] PostgreSQL WAL 아카이빙 실패" ops@example.com
fi
9. 물리적 백업 자동화 스크립트
#!/bin/bash
# /usr/local/bin/pg_physical_backup.sh
set -euo pipefail
DB_HOST="localhost"
DB_USER="backup_user"
BACKUP_ROOT="/backups/basebackup"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="${BACKUP_ROOT}/${TIMESTAMP}"
RETENTION_DAYS=7
LOG_FILE="/var/log/pg_physical_backup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
}
log "물리적 백업 시작"
mkdir -p "${BACKUP_DIR}"
pg_basebackup \
-h "${DB_HOST}" \
-U "${DB_USER}" \
-D "${BACKUP_DIR}" \
-Ft \
-z \
-Xs \
--checkpoint=fast \
-P \
2>>"${LOG_FILE}"
# 무결성 검증 (PostgreSQL 13+)
if pg_verifybackup "${BACKUP_DIR}" 2>>"${LOG_FILE}"; then
BACKUP_SIZE=$(du -sh "${BACKUP_DIR}" | cut -f1)
log "백업 완료 및 검증 성공: ${BACKUP_DIR} (${BACKUP_SIZE})"
else
log "ERROR: 백업 무결성 검증 실패! ${BACKUP_DIR}"
# 검증 실패한 백업 디렉토리 삭제 전 경로 확인
# BACKUP_DIR이 BACKUP_ROOT 하위인지 검증 후 삭제
if [[ "${BACKUP_DIR}" == "${BACKUP_ROOT}/"* ]]; then
rm -rf "${BACKUP_DIR}"
fi
exit 1
fi
# 보관 기간 초과 백업 정리
# find로 대상 목록을 확인하고 삭제 전 로그 기록
find "${BACKUP_ROOT}" -maxdepth 1 -mindepth 1 -type d \
-mtime +${RETENTION_DAYS} -print | while read -r OLD_DIR; do
log "만료 백업 삭제: ${OLD_DIR}"
rm -rf "${OLD_DIR}"
done
# crontab -e
# 매일 새벽 1시 물리적 백업 실행
0 1 * * * /usr/local/bin/pg_physical_backup.sh
10. 논리 vs 물리 — 언제 무엇을 쓸까
| 기준 | 논리적 백업 (pg_dump) | 물리적 백업 (pg_basebackup) |
|---|---|---|
| 백업 속도 | 느림 (대형 DB) | 빠름 |
| 복구 속도 | 느림 | 빠름 |
| PITR 지원 | 미지원 | 지원 (WAL 아카이빙 병행 시) |
| 선택적 복구 | 지원 (테이블·스키마 단위) | 미지원 (클러스터 전체) |
| 버전 간 이식성 | 지원 (낮은 → 높은 버전) | 미지원 (동일 메이저 버전) |
| 스탠바이 초기화 | 미지원 | 지원 |
| 증분 백업 | 미지원 | 지원 (PostgreSQL 17+) |
| 권장 활용 | 마이그레이션, 선택 복구, 개발 환경 | 재해 복구, PITR, HA 구성 |
실전에서 권장되는 조합이다.
# 매일: 물리적 백업 + 연속 WAL 아카이빙 → PITR 기반
pg_basebackup + archive_command
# 매주: 논리적 백업 → 이식성·선택 복구 기반
pg_dump
11. 마치며 — 백업 체인을 완성하는 WAL
pg_basebackup은 특정 시점의 완전한 스냅샷을 만든다. 하지만 그것만으로는 어제 찍힌 사진에 불과하다. WAL 아카이빙이 더해질 때, 그 스냅샷과 현재 사이의 모든 순간이 복구 가능해진다.
"물리 백업은 기반을 세우고, WAL 아카이빙은 그 위에 시간을 쌓는다."
PostgreSQL 17에서 도입된 네이티브 증분 백업(--incremental)은 대형 DB 환경에서 백업 비용을 낮추는 중요한 진전이다. 아직 서드파티 도구에 비해 기능이 제한적일 수 있으므로, 공식 문서와 팀 요건을 비교해 도입 여부를 결정한다.
다음 파트에서는 물리 백업과 WAL 아카이빙을 활용해 PITR을 실제로 구현하는 방법을 단계별로 다룬다.