운영 백업은 파일을 만드는 일이 아니라 복구 시간을 보장하는 일입니다

PostgreSQL 백업을 준비할 때 가장 먼저 정해야 할 것은 어떤 명령을 쓸지가 아니라 장애 시점에 어디까지 되돌릴 수 있어야 하는지입니다. 단순히 테이블 몇 개를 옮기거나 개발 환경을 갱신하는 목적이면 pg_dump가 편합니다. 하지만 운영 데이터베이스 전체를 특정 시각으로 되돌려야 한다면 베이스 백업과 WAL 보관을 함께 설계해야 합니다. WAL은 데이터 변경 내역을 기록하는 로그이므로, 베이스 백업 이후의 WAL을 재생하면 백업 시점 이후의 상태까지 복구할 수 있습니다. 이 방식이 PostgreSQL의 Point-In-Time Recovery, 즉 PITR의 핵심입니다.

실무에서는 두 방식을 섞어 씁니다. pg_dump는 스키마 단위 이전, 일부 테이블 복구, 버전 업그레이드 사전 점검에 좋고, pg_basebackup과 WAL 아카이빙은 서버 장애, 스토리지 장애, 대량 오삭제처럼 클러스터 전체를 되살려야 하는 상황에 필요합니다. 따라서 백업 정책에는 백업 주기, WAL 보관 기간, 저장 위치, 암호화, 복구 테스트 주기, 담당자 확인 절차가 함께 들어가야 합니다.

pg_dump는 논리 백업이고 PITR의 출발점은 아닙니다

pg_dump는 SQL 또는 커스텀 포맷으로 데이터베이스 객체와 데이터를 내보내는 논리 백업입니다. 장점은 운영 서버가 아닌 다른 호스트에서도 실행할 수 있고, 특정 데이터베이스나 스키마만 선택할 수 있으며, 대개 상위 PostgreSQL 버전으로 옮기기 쉽다는 점입니다. 반대로 클러스터 전체의 파일 상태와 WAL 재생 정보를 담지 않으므로 연속 아카이빙 기반 PITR에는 사용할 수 없습니다. 즉, pg_dump 파일 하나만 가지고는 오전 10시 37분 직전 상태로 되돌리는 복구가 불가능합니다.

# 매일 새벽 실행하는 논리 백업 예시
export PGHOST=127.0.0.1
export PGPORT=5432
export PGUSER=backup_user
export PGPASSWORD='비밀번호는 실제 운영에서는 .pgpass 또는 Secret으로 관리'

pg_dump -Fc -d appdb \
  --file=/backup/logical/appdb-$(date +%F).dump

# 복구 리허설: 새 데이터베이스에 실제로 적재되는지 확인
createdb appdb_restore_test
pg_restore -d appdb_restore_test --clean --if-exists /backup/logical/appdb-2026-05-22.dump

커스텀 포맷인 -Fc를 쓰면 pg_restore로 병렬 복구와 객체별 복구를 제어하기 쉽습니다. 백업 성공 로그만 보는 것으로는 부족합니다. 최소한 주 1회는 별도 테스트 데이터베이스에 복원하고, 핵심 테이블 건수와 애플리케이션 로그인, 주요 조회 API까지 확인해야 합니다.

PITR을 원하면 베이스 백업과 WAL 아카이빙을 같이 설계합니다

운영 복구 목표가 특정 시각 복원이라면 pg_basebackup으로 클러스터 파일의 기준점을 만들고, archive_command로 WAL 파일을 안전한 저장소에 계속 보관해야 합니다. 베이스 백업만 있으면 백업이 끝난 시각 이후의 변경분을 잃습니다. WAL만 있으면 시작점이 없습니다. 둘이 같이 있어야 장애 직전이나 오삭제 직전으로 복구할 수 있습니다.

# postgresql.conf 핵심 설정 예시
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /backup/wal/%f && cp %p /backup/wal/%f'
max_wal_senders = 5

# 베이스 백업 생성 예시
pg_basebackup -h 127.0.0.1 -U repl_user \
  -D /backup/base/2026-05-22 \
  -Fp -Xs -P -R

archive_command는 같은 WAL 파일을 덮어쓰지 않도록 작성해야 합니다. 위 예시는 이미 같은 파일명이 있으면 실패하도록 하여, 보관소의 WAL을 조용히 덮어쓰는 사고를 막습니다. 실제 운영에서는 로컬 디스크만 쓰기보다 NFS, 오브젝트 스토리지, 별도 백업 서버처럼 운영 DB와 장애 영역이 분리된 위치를 사용해야 합니다. 로컬 디스크에만 백업하면 서버 장애 시 원본과 백업을 함께 잃을 수 있습니다.

복구 절차는 문서가 아니라 명령으로 검증해야 합니다

PITR 복구는 장애가 난 뒤 처음 해보면 거의 반드시 지연됩니다. 디렉터리 권한, WAL 누락, 타임존 착각, 복구 대상 시각 오류, 서비스 포트 충돌 같은 문제가 뒤늦게 나옵니다. 그래서 운영 환경과 비슷한 별도 서버나 임시 볼륨에서 정기적으로 복구 리허설을 해야 합니다. 리허설의 목표는 백업 파일이 있다는 확인이 아니라, 서비스가 다시 올라오는 시간을 측정하는 것입니다.

# PITR 복구 서버에서의 예시 흐름
systemctl stop postgresql
mv /var/lib/postgresql/18/main /var/lib/postgresql/18/main.broken
cp -a /backup/base/2026-05-22 /var/lib/postgresql/18/main
chown -R postgres:postgres /var/lib/postgresql/18/main

cat > /var/lib/postgresql/18/main/postgresql.auto.conf <<'CONF'
restore_command = 'cp /backup/wal/%f %p'
recovery_target_time = '2026-05-22 13:45:00+09'
recovery_target_action = 'promote'
CONF

systemctl start postgresql
psql -d appdb -c "select now(), count(*) from orders;"

복구 대상 시각은 반드시 서비스 기준 타임존으로 기록합니다. 고객이 13시 47분에 잘못된 삭제를 실행했다면, 안전하게 13시 46분 30초처럼 사고 직전 시각을 선택하고 복원 후 데이터 상태를 확인합니다. recovery_target_action을 promote로 두면 목표 시점에 도달한 뒤 새 프라이머리로 승격됩니다. 운영 전환 전에는 기존 장애 서버와 동시에 쓰기 트래픽을 받지 않도록 라우팅과 애플리케이션 설정을 점검해야 합니다.

백업 운영에서 자주 빠지는 점검 항목

  • 백업 계정은 필요한 권한만 부여하고, 비밀번호는 스크립트 본문이 아니라 .pgpass, Secret, Vault 계열 저장소로 관리합니다.
  • 백업 파일과 WAL 저장소의 남은 용량을 모니터링합니다. archive_command가 실패하면 WAL이 쌓여 운영 디스크를 압박할 수 있습니다.
  • 베이스 백업과 WAL 보관 기간을 함께 맞춥니다. 베이스 백업은 있는데 필요한 WAL이 삭제되면 PITR은 실패합니다.
  • 논리 백업은 애플리케이션 릴리스 전후의 빠른 부분 복구 수단으로 유지하고, 전체 장애 복구는 물리 백업으로 준비합니다.
  • 복구 리허설 결과에는 시작 시각, 완료 시각, 복구 대상 시각, 누락된 WAL 여부, 검증 쿼리 결과를 남깁니다.

마무리 체크리스트

PostgreSQL 백업 정책은 pg_dump 파일 생성 여부가 아니라 실제 복구 가능성으로 평가해야 합니다. 작은 서비스라도 논리 백업만으로 충분한지, 클러스터 전체 PITR이 필요한지 먼저 구분해야 합니다. PITR이 필요하다면 pg_basebackup, archive_command, WAL 보관소, 복구 리허설이 한 세트입니다. 마지막으로 백업 성공 알림보다 복구 성공 기록을 더 중요하게 관리해야 합니다. 운영 장애 때 필요한 것은 어제 생성된 파일명이 아니라, 언제까지의 데이터를 몇 분 안에 되살릴 수 있는지에 대한 검증된 답입니다.