왜 정기 백업만으로는 부족한가
매일 새벽 한 번 덤프를 뜨는 백업 전략에는 근본적인 구멍이 있다. 오후 3시에 운영자가 실수로 DELETE를 잘못 날리거나 데이터가 손상되면, 복구 지점은 그날 새벽으로 되돌아간다. 즉 하루치 트랜잭션이 통째로 사라진다. 이 손실 폭을 결정하는 지표가 RPO(Recovery Point Objective)다. RPO를 초 단위로 줄이려면 특정 순간의 스냅샷이 아니라 모든 변경 이력을 붙잡아 두어야 한다.
PostgreSQL의 PITR(Point-In-Time Recovery)은 베이스 백업 하나와 그 이후 쌓인 WAL(Write-Ahead Log) 세그먼트를 결합해 "임의의 과거 시점"으로 데이터베이스를 되돌린다. 사고 직전 1초 전으로 복구하는 것이 가능해진다.
WAL 아카이빙 동작 원리
PostgreSQL은 모든 변경을 데이터 파일에 쓰기 전에 WAL에 먼저 기록한다. 기본적으로 WAL은 pg_wal 디렉터리에서 순환하며 재사용되므로 그대로 두면 과거 기록이 덮어써진다. 아카이빙은 세그먼트가 꽉 찰 때마다 별도 저장소로 복사해 보존하는 메커니즘이다. 이 세그먼트들의 연속된 사슬이 있어야 베이스 백업 이후의 모든 상태를 재생(replay)할 수 있다.
아카이빙 설정
postgresql.conf에서 아카이빙을 켠다. archive_command는 세그먼트 하나를 안전한 저장소로 복사하며, 성공 시 반드시 exit code 0을 반환해야 한다. 실패하면 PostgreSQL이 해당 세그먼트를 지우지 않고 재시도하므로, 저장소 장애가 곧 데이터 손실로 이어지지 않는다.
# postgresql.conf
wal_level = replica
archive_mode = on
# %p = 원본 경로, %f = 파일명. 덮어쓰기 방지가 핵심.
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
archive_timeout = 300 # 유휴 시에도 5분마다 강제 전환 → RPO 상한 보장
운영 환경이라면 로컬 cp 대신 오브젝트 스토리지로 올리는 것이 안전하다.
# S3로 아카이빙 (재시도/타임아웃 옵션 포함)
archive_command = 'aws s3 cp %p s3://db-wal/%f --only-show-errors'
베이스 백업 생성
WAL만으로는 복구할 수 없다. 재생의 출발점이 되는 베이스 백업이 필요하다. pg_basebackup은 서버가 켜진 상태에서 일관된 복사본을 만든다.
pg_basebackup \
-h db-primary -U replicator \
-D /backup/base_20260915 \
-Ft -z -Xstream -P
# -Ft: tar 포맷, -z: 압축, -Xstream: 백업 중 생성된 WAL도 함께 스트리밍
베이스 백업 시점과 아카이브된 WAL이 겹쳐 이어져야 한다. 오래된 베이스 백업의 시작점에 해당하는 WAL이 저장소에서 삭제되면 그 백업은 무용지물이 된다.
시점 복구 수행
복구는 베이스 백업을 풀고, 복구 목표를 지정한 뒤 recovery.signal 파일을 만들어 서버를 기동하는 순서다. PostgreSQL 12부터 복구 파라미터는 postgresql.conf에 함께 둔다.
# 1) 베이스 백업 복원
tar xzf /backup/base_20260915/base.tar.gz -C /var/lib/pgsql/data
# 2) postgresql.conf 에 복구 목표 지정
restore_command = 'aws s3 cp s3://db-wal/%f %p'
recovery_target_time = '2026-09-15 14:59:50+09'
recovery_target_action = 'pause' # 목표 도달 후 자동 승격 대신 일시정지
# 3) 복구 모드 진입 신호
touch /var/lib/pgsql/data/recovery.signal
recovery_target_action = 'pause'로 두면 목표 시점에서 멈춘다. 데이터를 확인한 뒤 문제가 없으면 pg_wal_replay_resume()로 재생을 끝내고 승격한다. 목표를 지나쳐 버리면 되돌릴 수 없으므로, 항상 여유 있게 잡고 검증 후 확정하는 편이 안전하다.
복구 목표 옵션 비교
| 파라미터 | 기준 | 주 용도 |
|---|---|---|
| recovery_target_time | 타임스탬프 | 사고 발생 시각을 아는 경우 |
| recovery_target_lsn | WAL 위치 | 정확한 트랜잭션 경계 지정 |
| recovery_target_xid | 트랜잭션 ID | 특정 트랜잭션 직전 복구 |
| recovery_target_name | 명명 지점 | 사전에 pg_create_restore_point로 표시 |
운영 시 주의점
첫째, 백업은 정기적으로 복구 리허설을 해야 한다. 복구해 본 적 없는 백업은 백업이 아니다. 둘째, archive_command 실패를 모니터링하라. 아카이빙이 막히면 pg_wal이 무한정 쌓여 디스크가 가득 차고 결국 DB가 멈춘다. pg_stat_archiver의 last_failed_time을 알람에 연결한다. 셋째, 오래된 WAL과 베이스 백업의 보존 주기를 함께 관리해야 사슬이 끊기지 않는다. 넷째, 이런 반복 작업은 직접 스크립트를 짜기보다 pgBackRest나 WAL-G 같은 도구로 병렬 업로드·검증·보존 정책을 위임하는 편이 실수를 줄인다. PITR은 결국 "지속적으로 검증된 WAL 사슬"을 유지하는 운영 규율의 문제다.