기준 백업 + 그 이후의 WAL 아카이브를 합쳐 원하는 과거 시점으로 되감는 복구 방식.
무엇을 구해 주나
오후 3시 05분 운영자가 WHERE 를 빠뜨린 DELETE 를 실행했다
↓
오후 3시 04분 상태로 되감는다
크래시 복구(WAL)는 크래시 직전 상태로만 돌아간다. 사람의 실수는 정상 커밋이므로 WAL로는 못 막는다. PITR만이 그 이전으로 되돌린다.
어떻게 동작하나
[기준 백업] [연속 WAL 아카이브]
2026-08-01 02:00 → ─────────────────────────→ 지금
↑
원하는 시점까지만 redo 를 적용한다
PostgreSQL 설정
# postgresql.conf — WAL 을 별도 보관소로 계속 복사해 둔다
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
# 기준 백업
pg_basebackup -D /backup/base -Fp -Xs -P
# 복원 — 목표 시점을 지정한다
# recovery.signal 파일을 만들고 postgresql.conf 에
# restore_command = 'cp /archive/%f %p'
# recovery_target_time = '2026-08-01 15:04:00'
recovery_target_time 외에 recovery_target_lsn·recovery_target_xid 로도 지정할 수 있다. 사고 트랜잭션 ID를 알면 그 직전으로 정확히 되감을 수 있다.
MySQL의 방식
# 전체 백업 + 바이너리 로그를 시점까지 적용한다
mysqlbinlog --stop-datetime="2026-08-01 15:04:00" binlog.000123 | mysql
MySQL은 redo 로그가 아니라 바이너리 로그(binlog) 를 쓴다 — redo는 InnoDB 내부용이고 binlog는 복제·PITR용이라는 역할 분리가 있다.
실무에서 반드시 챙길 것
- ① 아카이브가 실제로 쌓이는지 감시한다
- archive_command 가 실패해도 조용히 넘어가면 PITR 이 불가능해진다
- ② 복원을 정기적으로 실제로 해 본다
- 복원해 본 적 없는 백업은 백업이 아니다
- ③ 아카이브 보관 기간을 정한다
- 무한히 쌓으면 디스크가 찬다
-- 아카이브가 밀리고 있지 않은지
SELECT archived_count, failed_count, last_failed_time FROM pg_stat_archiver;
failed_count가 늘고 있으면 PITR이 이미 불가능한 상태일 수 있다.
면접 함정
- ❌ "복제본이 있으니 PITR은 필요 없다" → 실수한 DELETE는 복제본에도 즉시 전파된다.
- ❌ "백업만 있으면 PITR이 된다" → 기준 백업 + 연속 WAL 아카이브가 둘 다 있어야 한다.
되감을 시점을 어떻게 정하나
-- 사고 트랜잭션의 ID 를 알면 그 직전으로 정확히 되감을 수 있다
-- postgresql.conf
-- recovery_target_xid = '123456'
-- recovery_target_inclusive = false ← 그 트랜잭션은 제외한다
-- 시각을 모를 때는 로그에서 찾는다
-- log_statement = 'ddl' 이면 DROP/TRUNCATE 가 시각과 함께 남는다
복구 후 반드시 확인할 것
SELECT pg_is_in_recovery(); -- true 면 아직 복구 모드다
SELECT pg_wal_replay_resume(); -- 일시정지 상태면 재개
-- recovery_target_action = 'pause' 로 두면
-- 목표 시점에 멈춰서 데이터를 확인한 뒤 승격할 수 있다 (권장)
pause로 멈춰 확인한 뒤 승격하는 절차가 안전하다. 바로 승격해 버리면 시점이 잘못됐을 때 다시 처음부터 복구해야 한다.