PITR과 standby 운영: base backup, timeline, failover/rejoin
WAL 아카이브가 PITR의 기반이다
PostgreSQL의 PITR(Point-In-Time Recovery)은 두 요소로 성립한다: 베이스 백업과 WAL 아카이브의 연속성. 베이스 백업은 특정 시점의 물리적 스냅샷이고, WAL 아카이브는 그 이후 모든 변경 이력을 담는다. 두 요소 중 하나라도 끊기면 원하는 시점으로 되돌아갈 수 없다.
WAL 아카이브를 활성화하려면 두 가지 설정이 필요하다.
wal_level = replica # PITR + 스트리밍 복제를 모두 지원하는 최솟값
archive_mode = on
archive_command = 'cp %p /archive/%f'%p는 아카이브할 WAL 세그먼트의 소스 경로, %f는 대상 파일명이다. archive_command는 성공 시에만 종료 코드 0을 반환해야 한다. 실패했는데 0을 반환하면 PostgreSQL은 해당 세그먼트를 완료로 처리하고 pg_wal/에서 제거한다. 아카이브에는 파일이 없는데 내부 카탈로그에는 완료로 기록된다. PITR 시도 시 복구 체인이 조용히 끊긴다.
archive_timeout을 설정하면 WAL 쓰기가 없어도 일정 주기마다 세그먼트를 강제 전환한다.
archive_timeout = 300 # 5분마다 세그먼트 전환 강제아카이브 상태는 pg_stat_archiver로 실시간 확인한다.
SELECT
archived_count,
last_archived_wal,
last_archived_time,
failed_count,
last_failed_wal,
last_failed_time
FROM pg_stat_archiver;failed_count가 증가하고 있다면 archive_command를 즉시 점검해야 한다.
pg_basebackup 실전
베이스 백업은 pg_basebackup으로 수행한다. 운영 중인 PostgreSQL에서 파일 시스템 수준의 물리적 복사본을 가져온다.
pg_basebackup \
-h localhost \
-U replicator \
-D /backup/base-$(date +%Y%m%d) \
-Fp \
-Xs \
-P \
-R-Xs(WAL 스트리밍)가 핵심이다. 백업 실행 중에 발생하는 WAL 세그먼트를 스트리밍으로 가져와 백업 디렉터리의 pg_wal/에 포함한다. 덕분에 restore_command 설정 없이도 백업 직후 스탠바이를 바로 기동할 수 있다.
-R을 쓰면 스탠바이 구성 파일을 자동으로 만든다: standby.signal 파일과 postgresql.auto.conf의 primary_conninfo 항목.
백업 실행 시 PostgreSQL은 내부적으로 체크포인트를 수행한다. 체크포인트 완료 LSN이 베이스 백업의 시작 기준점이 된다. 이 시점 이후 WAL이 연속적으로 아카이브되어 있어야 복구가 가능하다.
| 옵션 | 용도 |
|---|---|
-Fp | plain 포맷: 디렉터리 그대로 복사 |
-Ft | tar 포맷: 단일 파일로 묶음 |
-Xs | WAL 스트리밍: 백업 중 발생 WAL 포함 |
-Xp | WAL 위치 지정: 별도 아카이브에서 나중에 가져옴 |
-R | standby.signal + primary_conninfo 자동 생성 |
-j N | 병렬 복사로 백업 속도 향상 |
-z | gzip 압축 (tar 포맷 전용) |
복구 파라미터: PostgreSQL 12+
PostgreSQL 12부터 recovery.conf가 폐지됐다. 복구 관련 설정은 postgresql.conf(또는 postgresql.auto.conf)에 통합됐고, 복구 모드 진입은 신호 파일로 제어한다.
| 신호 파일 | 역할 |
|---|---|
recovery.signal | 지정 시점까지 복구 후 승격 |
standby.signal | 스탠바이 루프(연속 복제 대기) |
두 파일이 모두 있으면 standby.signal이 우선한다. 복구가 완료되면 recovery.signal은 자동으로 삭제된다. 파일이 남아 있으면 다음 재시작 시 다시 복구 모드로 진입한다.
핵심 복구 파라미터:
| 파라미터 | 설명 |
|---|---|
restore_command | 아카이브에서 WAL 세그먼트를 가져오는 셸 명령 |
recovery_target_time | 복구 목표 타임스탬프 |
recovery_target_lsn | 복구 목표 LSN |
recovery_target_xid | 복구 목표 트랜잭션 ID |
recovery_target_name | pg_create_restore_point()로 지정한 이름 |
recovery_target | 'immediate': 일관성 도달 즉시 중단 |
recovery_target_inclusive | 목표 트랜잭션 포함 여부 (기본값 true) |
recovery_target_action | 복구 완료 후 동작: 'promote' / 'pause' / 'shutdown' |
타임존 명시 필수:
recovery_target_time에 타임존 없이 쓰면 서버 타임존이 적용된다. 항상'2026-06-29 14:00:00+09'형식으로 지정한다.
PITR 전체 흐름
아래 다이어그램은 베이스 백업과 WAL 아카이브에서 복구 인스턴스를 만들고 승격하는 흐름을 보여준다.
운영 중
주기적
물리 스냅샷
연속 로그
복원
restore_command
recovery_target_*
pg_promote()
1단계: 베이스 백업 복원
tar -xzf /backup/base-20260629.tar.gz -C /var/lib/postgresql/17/restored2단계: 복구 파라미터 설정
# postgresql.conf에 추가
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2026-06-29 14:00:00+09'
recovery_target_action = 'promote'3단계: recovery.signal 생성 후 기동
touch /var/lib/postgresql/17/restored/recovery.signal
pg_ctl start -D /var/lib/postgresql/17/restored4단계: 복구 완료 확인
SELECT pg_is_in_recovery(); -- false = 승격 완료PostgreSQL 로그에서 다음 메시지를 확인한다.
LOG: recovery stopping before commit of transaction ..., time ...
LOG: database system is ready to accept connectionsrecovery_target_action = 'pause'로 설정하면 복구가 완료 직전에 멈추고 데이터를 검증한 다음 SELECT pg_wal_replay_resume()으로 최종 승격할 수 있다.
Timeline ID와 분기
복구 또는 승격이 일어날 때마다 PostgreSQL은 타임라인 ID(TLI)를 하나 증가시킨다. WAL 세그먼트 파일명에 타임라인이 포함되어 있어, 동일 LSN에서 분기된 두 타임라인의 WAL이 충돌하지 않는다.
승격 직후 PostgreSQL은 <TLI>.history 파일을 아카이브에 기록한다.
# 타임라인 2의 히스토리 파일 (00000002.history)
1 0/5000000 no recovery target specified이 파일이 있어야 새 스탠바이가 구 타임라인의 WAL을 재생하다가 분기 시점에서 자동으로 새 타임라인으로 전환할 수 있다.
PITR로 시간을 되돌리면 새 타임라인이 생긴다. 이 타임라인 이후의 원래 WAL은 사용하지 않는다. 둘은 같은 LSN에서 갈라진 독립적인 이력이다. "잘못 복구했다"는 것을 알았을 때 아카이브에 원래 WAL이 남아 있으면 다시 그 시점으로 돌아갈 수 있다. 이것이 타임라인이 PITR에서 중요한 이유다.
Hot Standby 운영
스탠바이가 WAL을 재생하는 동안에도 읽기 쿼리를 허용하는 모드가 Hot Standby다. hot_standby = on(기본값)으로 활성화된다.
# 스탠바이 postgresql.conf
hot_standby = on
hot_standby_feedback = on # 프라이머리에게 스탠바이 실행 쿼리 정보 전달
max_standby_streaming_delay = 30s # 충돌 시 WAL 재생 최대 지연 허용 시간쿼리-복제 충돌 메커니즘
WAL 재생이 읽기 쿼리와 충돌하면(예: 쿼리가 참조 중인 행의 dead tuple을 재생이 정리하려 할 때) PostgreSQL은 max_standby_streaming_delay 동안 재생을 잠시 멈춘다. 그래도 충돌이 해소되지 않으면 쿼리를 강제 취소한다.
ERROR: canceling statement due to conflict with recoveryhot_standby_feedback = on이면 스탠바이는 현재 실행 중인 가장 오래된 트랜잭션 ID를 프라이머리에 전달한다. 프라이머리는 그 ID가 필요로 하는 dead tuple을 VACUUM이 제거하지 않도록 보류한다. 충돌이 줄지만 프라이머리에 bloat이 약간 증가할 수 있다.
hot_standby = off이면 스탠바이는 복구만 수행하고 쿼리를 받지 않는다(Warm Standby). 읽기 분산이 불필요하다면 충돌 자체가 없어 운영이 단순해진다.
Failover와 pg_rewind로 재합류
Failover: 스탠바이 승격
프라이머리 장애가 확인되면 스탠바이를 승격한다.
# pg_ctl 방식
pg_ctl promote -D /var/lib/postgresql/17/main
# SQL 함수 방식 (PostgreSQL 12+)
SELECT pg_promote();승격 후 다른 스탠바이가 있다면 primary_conninfo를 새 프라이머리로 갱신한다.
# 기존 스탠바이의 postgresql.auto.conf 수정 후 리로드
primary_conninfo = 'host=new-primary port=5432 user=replicator'
pg_ctl reload -D /var/lib/postgresql/17/replica2pg_rewind: 구 프라이머리 재합류
구 프라이머리는 새 프라이머리와 WAL이 분기된 상태다. primary_conninfo만 바꿔도 타임라인이 다르므로 복제에 참가할 수 없다. 이때 pg_rewind를 쓴다.
pg_rewind는 두 인스턴스의 분기 LSN을 찾아 분기 이후 변경된 데이터 블록만 새 프라이머리에서 가져온다. 전체 베이스 백업을 다시 받는 것보다 훨씬 빠르다.
사전 요건:
# postgresql.conf — 미리 설정해 두어야 한다
wal_log_hints = on # data_checksums가 없을 때 필요
# 또는
data_checksums = on # initdb 시에만 활성화 가능두 옵션 중 하나가 있어야 pg_rewind가 변경 블록을 식별할 수 있다. data_checksums는 클러스터 초기화 시에만 설정 가능하다.
pg_rewind 실행:
# 1. 구 프라이머리 클린 셧다운 확인
pg_ctl stop -D /var/lib/postgresql/17/old-primary
# 2. pg_rewind 실행
pg_rewind \
--target-pgdata=/var/lib/postgresql/17/old-primary \
--source-server="host=new-primary port=5432 user=replicator" \
--progress
# 3. 스탠바이 신호 파일 + primary_conninfo 설정
touch /var/lib/postgresql/17/old-primary/standby.signal
cat >> /var/lib/postgresql/17/old-primary/postgresql.auto.conf <<EOF
primary_conninfo = 'host=new-primary port=5432 user=replicator'
EOF
# 4. 기동
pg_ctl start -D /var/lib/postgresql/17/old-primary클린 셧다운 필수:
pg_rewind는 구 프라이머리가 정상 종료된 상태여야 한다. 크래시 상태라면 먼저 PostgreSQL을 기동해 크래시 복구를 완료한 뒤 즉시 정상 종료한다. 그 후 pg_rewind를 실행한다.
운영 체크리스트
archive_command가 실패 시 반드시 비-0 종료 코드를 반환하는가?pg_stat_archiver.failed_count증가를 감지하는 알림이 있는가?- 베이스 백업과 WAL 아카이브 사이의 LSN 연속성을 주기적으로 검증하는가?
recovery_target_time에 타임존을 명시적으로 지정했는가?wal_log_hints = on또는data_checksums = on이 설정되어 pg_rewind 사용이 가능한가?max_slot_wal_keep_size를 설정해 스탠바이 단절 시 디스크 고갈을 방지하는가?- PITR 복구 리허설을 마지막으로 실행한 날짜와 성공 여부를 기록하고 있는가?
- failover → pg_rewind 재합류 절차가 문서화되어 팀 전체가 실행할 수 있는가?
References
- PostgreSQL Documentation, "Continuous Archiving and Point-in-Time Recovery" — https://www.postgresql.org/docs/current/continuous-archiving.html
- PostgreSQL Documentation, "pg_basebackup" — https://www.postgresql.org/docs/current/app-pgbasebackup.html
- PostgreSQL Documentation, "pg_rewind" — https://www.postgresql.org/docs/current/app-pgrewind.html
- PostgreSQL Documentation, "Hot Standby" — https://www.postgresql.org/docs/current/hot-standby.html
- PostgreSQL Documentation, "Recovery Target Settings" — https://www.postgresql.org/docs/current/recovery-target-settings.html
- PostgreSQL Documentation, "pg_stat_archiver" — https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ARCHIVER-VIEW
- Crunchy Data, "Point-in-Time Recovery with PostgreSQL" — https://www.crunchydata.com/blog/point-in-time-recovery-with-postgresql
- Cybertec, "PITR and pg_rewind in PostgreSQL" — https://www.cybertec-postgresql.com/en/pitr-point-in-time-recovery-for-postgresql/
- pgBackRest Documentation, "User Guide" — https://pgbackrest.org/user-guide.html
- PostgreSQL Wiki, "Timelines" — https://wiki.postgresql.org/wiki/Timelines