백업이 아니라 복구가 시험이다
2편 마지막에 이렇게 적었다 — “백업의 가치는 백업이 아니라 복구에서 결정된다.” 이 글은 그 한 줄을 직접 돌려보는 워크북이다. Barman의 PITR target 옵션 4가지를 한 lab에서 한 번씩 시도해 본다.
처음 보는 독자도 따라올 수 있게 환경을 짧게 정리한다.
| 호스트 | 역할 | Barman 라벨 |
|---|---|---|
demo-pg01 | PostgreSQL 17 primary | — |
demo-barman01 | Barman 3.18 서버 | pg01 |
pg01은 Barman 서버 라벨, demo-pg01은 실제 호스트네임 — 두 이름은 별개다. 환경 셋업이 처음이라면 2편을 먼저 보고 오는 게 빠르다.
참고로
barman recover와barman restore는 같은 명령이다. Barman 3.x에서restore가 새 권장 이름이지만recover도 그대로 동작한다. 이 글은 2편과 톤을 맞춰recover로 통일했다.
시나리오 준비 — 지웠다 살리는 데이터
PITR을 그럴듯하게 돌려 보려면 의도적으로 손상된 시점이 필요하다. 다음 SQL을 demo-pg01에서 미리 실행해 둔다.
-- T0: 기준 데이터
sudo -u postgres psql <<'SQL'
CREATE TABLE notes (
id int PRIMARY KEY,
body text,
ts timestamptz default now()
);
INSERT INTO notes(id, body)
SELECT g, 'note-' || g FROM generate_series(1, 1000) g;
SQL
-- T1 (예: 14:30:00): 안전 시점 — 이후로 되감을 라벨 박기 + XID 기록
sudo -u postgres psql <<'SQL'
BEGIN;
SELECT pg_create_restore_point('safe-state'); -- 시나리오 C 용
SELECT pg_current_xact_id(); -- 시나리오 B 용 — 예: 12345
COMMIT;
SQL
-- T2 (예: 14:32:00 이후): 사고 — 테이블 삭제
sudo -u postgres psql -c "DROP TABLE notes;"
이제 notes 테이블이 사라졌다. base backup이 T0 이전에 떠 있고 WAL이 계속 수집되고 있다고 가정한다 (2편의 lab 그대로). 이 base backup + WAL을 가지고 T1 직후 / XID 12345 직후 / 명시 라벨 / base backup 직후 4가지 시점으로 되감아 본다.
복원 대상은 빈 디렉토리 — 매 시나리오 사이에 비워서 시작한다.
sudo -u barman rm -rf /var/lib/barman/restore && \
sudo -u barman mkdir -p /var/lib/barman/restore
시나리오 A — --target-time: 시계로 되감기
가장 직관적이다. 사고 직전 시각으로 돌린다.
sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-time "2026-05-06 14:32:00"
기대 동작 — 14:32:00 시점까지 WAL을 replay한 뒤 멈춘다. notes 테이블은 살아 있고, DROP TABLE은 reach되지 않는다.
타임존은 Barman 서버의 시스템 시간대가 기본이다. 다른 TZ로 명시하려면 2026-05-06 14:32:00+09 형태로 붙인다 — 운영에서는 항상 명시하는 편이 안전하다.
“Recovery targets must be a value after the end of the backup.” — base backup 시작 시점 이전은 reach 불가능. 시점이 base backup 시작보다 이르면 즉시 실패한다.
시나리오 B — --target-xid: 트랜잭션 ID로 되감기
시계는 누적된 운영 환경에서 의외로 부정확하다. Barman 호스트와 PostgreSQL 호스트의 시계가 살짝 어긋나 있거나, 동시에 여러 트랜잭션이 들어왔거나. 트랜잭션 ID(XID)가 가장 정확한 좌표다.
sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-xid 12345
기대 동작 — XID 12345가 commit된 직후까지 replay하고 멈춘다. T1 시점에 기록해 둔 XID가 사고 직전 마지막 안전 트랜잭션이라 이걸로 멈추면 notes는 살아 있다.
| 항목 | 내용 |
|---|---|
| 강점 | 시점이 논리적으로 정확 — 시계 오차·동시 트랜잭션 영향 없음 |
| 약점 | 사고 직후가 되어서야 그 XID를 안다 → 사고 직전에 미리 기록해 두는 게 이상적 |
| 보조 | pg_waldump으로 WAL을 훑어 commit 레코드의 XID 시퀀스 추적 가능 |
--exclusive 플래그를 같이 주면 그 XID 직전까지만 replay (그 트랜잭션 자체는 제외). 기본값은 그 XID 포함.
시나리오 C — --target-name: 명시 라벨로 되감기
운영 중 위험한 작업 직전에 라벨을 박아 두는 패턴.
-- 사고 직전(T1)에 미리 박아 둔 라벨
SELECT pg_create_restore_point('safe-state');
sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-name 'safe-state'
기대 동작 — safe-state restore point까지 replay하고 멈춘다.
이 옵션의 가치는 언어가 자연스럽다는 점이다. 시각·XID는 사고 후 재구성해야 하지만 라벨은 팀이 기억할 수 있는 이름이다 — before-migration-2026q2, pre-DROP-INDEX-experiment 같은. 운영 가이드를 “위험한 ALTER 직전엔 restore point부터 박는다“로 정착시키면 PITR이 한층 평이해진다.
시나리오 D — --target-immediate: base backup 직후
가장 단순한 옵션 — 마지막 base backup이 일관성을 확보하는 그 시점까지만 replay하고 멈춘다. 즉 base backup 종료 직후의 클러스터 상태로 일어선다.
sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-immediate
언제 쓰는가 — “가장 최근 base backup의 그 순간으로 일단 돌려놓고, 이후 사고 영향을 분리해 분석하고 싶다” 같은 forensic 시나리오. WAL replay를 최소화해 빠르게 일관성 상태에 도달한다.
그 외 옵션 빠른 참조
| 옵션 | 의미 | 비고 |
|---|---|---|
--target-lsn | LSN(3/64000000 형식)으로 되감기 | XID보다 더 세밀, 특정 WAL 위치를 정확히 알 때 |
--target-tli | 특정 timeline으로 복구 | latest / current / 숫자 ID |
--target-action | reach 후 동작 — shutdown / pause / promote | 미지정 시 PostgreSQL이 paused 후 운영자 결정 대기 |
--exclusive | target 직전까지(target 자체 제외) replay | 기본은 target 포함 |
--standby-mode | replica로 일어나도록 standby.signal 생성 | target과 무관한 옵션 |
--target-action이 특히 중요하다. 지정하지 않으면 paused — 운영자가 SELECT pg_wal_replay_resume()을 직접 호출할 때까지 새 트랜잭션이 돌지 않는다. 의도하지 않으면 “왜 안 살아나지” 하고 한참 헤맨다.
backup_id=auto(가장 최근 backup 자동 선택)일 때는 제약이 있다 — --target-time, --target-lsn, --target-tli만 허용된다. --target-xid나 --target-name을 쓰려면 명시적인 backup-id를 지정해야 한다 (barman list-backup pg01로 확인).
시점을 정하는 법 — barman show-backup + pg_waldump
PITR의 절반은 어디로 되감을지 정하는 일이다. 두 명령어가 그 좌표를 준다.
sudo -u barman barman show-backup pg01 latest
출력에서 다음 항목을 본다.
| 항목 | 의미 |
|---|---|
Begin time / End time | base backup 경계 — 그 이전 시점은 reach 불가 |
Begin LSN / End LSN | base backup의 LSN 경계 — --target-lsn 사용 시 기준점 |
Begin Offset / End Offset | WAL 파일 내 위치 |
WAL을 더 세밀하게 보려면 pg_waldump로 commit 레코드를 훑는다.
sudo -u postgres /usr/pgsql-17/bin/pg_waldump \
/var/lib/barman/pg01/streaming/000000010000000000000004 \
| grep COMMIT | head
각 줄에 LSN·XID·timestamp가 함께 나오므로, 사고 직전의 어떤 좌표를 사용할지 골라잡기 쉬워진다.
자주 만나는 함정 5가지
| 증상 | 원인 | 해결 |
|---|---|---|
Recovery targets must be a value after the end of the backup | target이 base backup 시작 이전 | barman show-backup의 Begin time 이후로 잡거나 더 오래된 backup 사용 |
| 복구 후 PostgreSQL이 안 살아남 | --target-action 미지정 → paused 상태 | SELECT pg_wal_replay_resume(); 또는 처음부터 --target-action promote |
| timeline mismatch | 이전 복구 후 새 timeline으로 진입 | --target-tli latest 또는 명시적 timeline ID |
target-name 못 찾음 | restore point가 현재 timeline의 WAL에 없음 | pg_create_restore_point가 해당 timeline에 박혔는지 확인 |
barman recover가 아무 진행 안 함 | --remote-ssh-command로 cross-host 복원인데 SSH 키 미설치 | barman 사용자에서 postgres@<host>로 키 기반 접속 미리 잡기 |
정리
| 항목 | 내용 |
|---|---|
| 4가지 target | --target-time (시계) / --target-xid (XID) / --target-name (라벨) / --target-immediate (base 직후) |
| 가장 정확 | XID — 시계 오차·동시 트랜잭션 무관 |
| 가장 운영 친화 | --target-name — 위험 작업 직전 pg_create_restore_point 박는 패턴 |
| 시점 결정 | barman show-backup + pg_waldump 조합 |
| 필수 동반 | --target-action(기본 paused), --exclusive(stop-before vs include) |
backup_id=auto 제약 | --target-time, --target-lsn, --target-tli만 허용 — 그 외는 명시 backup-id 필요 |
“PITR은 두 단계다 — 시점을 정하는 일이 반이고, 복구하는 일이 반이다. 보통 시점을 정하는 일이 더 어렵다.”