본문으로 건너뛰기

"PITR" 태그로 연결된 3개 게시물개의 게시물이 있습니다.

모든 태그 보기

Barman Cloud S3 백업

· 약 14분

Barman 서버 없이 백업하기​

2~5편은 모두 Barman 서버 한 대가 PostgreSQL에서 백업을 가져오는 구조였어요. 이번 편은 그 서버를 제외하고, PostgreSQL 호스트가 S3 버킷에 직접 백업을 올리는 barman-cloud-* 명령을 다뤄요. 로컬 Docker에 MinIO를 띄워 WAL 아카이브, base backup, 보존 정책, PITR까지 한 번씩 실행해 봤어요.

Barman의 barman-cloud-* 명령은 barman-cli-cloud 패키지로 따로 배포됩니다. 공식 문서는 이 명령을 PostgreSQL 서버와 같은 머신에 설치하고, PGDATA를 읽어야 하므로 보통 postgres 사용자로 실행한다고 안내합니다. 백업 결과는 Barman 서버 카탈로그 대신 오브젝트 스토리지에 객체로 남습니다. CloudNativePG의 in-tree 구현도 복구할 때 barman-cloud-restore를 호출하므로, 7편(CNPG와 Barman Cloud Plugin)으로 넘어가기 전에 명령 단위로 동작을 확인해 두려는 목적도 있습니다.

barman-cloud 명령 9개​

Barman 3.20 문서 기준으로 명령은 아홉 개입니다.

명령역할쓰는 자리
barman-cloud-wal-archiveWAL 세그먼트 업로드archive_command
barman-cloud-wal-restoreWAL 세그먼트 다운로드restore_command
barman-cloud-backupbase backup 생성과 업로드cron, 스케줄러
barman-cloud-backup-list백업 목록수동 확인
barman-cloud-backup-show백업 상세수동 확인
barman-cloud-backup-deleteID 또는 보존 정책으로 삭제cron, 스케줄러
barman-cloud-backup-keep보존 정책 제외 표시수동
barman-cloud-check-wal-archive아카이브 위치가 비었는지 검사최초 연결, 복구 후
barman-cloud-restorebase backup 다운로드복구

모든 명령이 DESTINATION_URL SERVER_NAME 두 인자를 받습니다. s3://pgbackup/lab이 URL이고 pg18이 서버 이름이면 객체는 pgbackup 버킷의 lab/pg18/ 아래에 쌓입니다. S3 호환 스토리지의 주소는 --endpoint-url로 지정하고, 기본 --cloud-provider는 aws-s3입니다.

측정 환경​

측정은 macOS의 Docker Desktop VM(2 CPU, 약 11.7GiB, 다른 작업과 공유)에서 진행했습니다. 시간 값은 모두 한 번씩 측정한 값이라 참고용입니다.

항목값
PostgreSQLpostgres:18 이미지, 18.4 (Debian 13)
BarmanPGDG apt barman-cli-cloud 3.20.0, Python 3.13.5, boto3 1.37.9
MinIOcgr.dev/chainguard/minio, RELEASE.2026-09-22T19-25-18Z
데이터pgbench -i -s 3, DB 53MB, pg_wal 제외 PGDATA 68MB

Docker Hub의 minio/minio와 quay.io/minio/minio는 이 환경에서 pull이 되지 않아 Chainguard 이미지를 썼습니다. Barman 문서는 MinIO를 쓴다면 RELEASE.2025-02-03T21-03-04Z 이상으로 올려야 백업 삭제가 실패하지 않는다고 경고합니다. 측정 버전은 이 조건을 충족합니다. 오래된 S3 호환 스토리지를 boto3 1.36 이상과 함께 써야 한다면 문서가 안내하는 AWS_REQUEST_CHECKSUM_CALCULATION=when_required, AWS_RESPONSE_CHECKSUM_VALIDATION=when_required 환경변수를 확인해 보세요.

설치에는 PGDG apt 저장소의 barman-cli-cloud와 python3-boto3 패키지를 사용했습니다. 처음에는 venv에서 pip install "barman[cloud]==3.20.0"을 시도했지만 psycopg2 소스 빌드에서 실패했습니다. Barman 3.20부터는 Python 3.12 이상이 필요하므로(3.20.0 릴리스 노트), Python 3.11 기반 배포판에서는 패키지 버전을 먼저 확인해야 합니다.

WAL 아카이브 연결​

인증 정보는 PostgreSQL 프로세스의 환경변수(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION)로 전달했습니다. archive_command는 postmaster 환경을 물려받으므로, 운영에서는 systemd unit의 환경 파일이나 ~postgres/.aws/credentials로 같은 값을 줍니다.

연결하기 전에 아카이브 위치부터 검사합니다.

barman-cloud-check-wal-archive \
--endpoint-url http://barman-cloud-s3-backup-minio:9000 \
s3://pgbackup/lab pg18
# exit=0

이 명령은 아카이브 위치가 비어 있으면 성공합니다. WAL이 하나라도 있으면 실패하고, --timeline을 지정하면 그보다 앞선 timeline의 WAL만 있을 때 통과합니다. 다른 클러스터의 WAL과 섞이는 사고를 막는 검사라서, 새 클러스터를 기존 경로에 연결하기 전에 실행해 두는 편이 좋습니다.

ALTER SYSTEM SET archive_mode = on;
ALTER SYSTEM SET archive_command =
'barman-cloud-wal-archive --endpoint-url http://barman-cloud-s3-backup-minio:9000 --zstd s3://pgbackup/lab pg18 %p';
-- archive_mode 변경은 재시작 필요

재시작한 뒤 pg_switch_wal()을 한 번 호출하자 pg_stat_archiver가 archived_count=1, failed_count=0으로 바뀌었습니다. 16MB 세그먼트는 --zstd 덕분에 버킷에 1.2MiB, 500KiB처럼 작게 올라갔고, 거의 빈 세그먼트는 700B 안팎이었습니다.

base backup 올리기​

barman-cloud-backup --endpoint-url http://barman-cloud-s3-backup-minio:9000 \
--immediate-checkpoint -n plain s3://pgbackup/lab pg18
barman-cloud-backup --endpoint-url http://barman-cloud-s3-backup-minio:9000 \
--immediate-checkpoint --zstd -n zstd s3://pgbackup/lab pg18
백업압축소요 시간 (time real)버킷 크기
plain없음2.893초data.tar 69MiB
zstd--zstd2.964초data.tar.zst 4.7MiB

압축 비율이 이렇게 큰 것은 pgbench 초기 데이터가 반복 패턴이 많은 합성 데이터이기 때문입니다. 실제 데이터에서는 이 비율이 나오지 않을 가능성이 높으니 일반화하지 않는 편이 맞습니다. 이 크기에서는 압축 여부가 소요 시간에 거의 영향을 미치지 않았습니다. 병렬 업로드 기본값은 -J 2이고, backup.info의 copy_stats에도 number_of_workers: 2로 기록됐습니다.

버킷에 남은 객체는 다음과 같습니다(mc ls -r, 일부 생략).

69MiB lab/pg18/base/20260928T070251/data.tar
1.3KiB lab/pg18/base/20260928T070251/backup.info
4.7MiB lab/pg18/base/20260928T070254/data.tar.zst
1.3KiB lab/pg18/base/20260928T070254/backup.info
1.2MiB lab/pg18/wals/0000000100000000/000000010000000000000001.zst
203B lab/pg18/wals/0000000100000000/000000010000000000000005.00000028.backup.zst
699B lab/pg18/wals/0000000100000000/000000010000000000000005.zst
...

이 lab에서는 백업마다 base/<백업ID>/ 디렉터리에 tar 한 개와 backup.info 한 개가 생겼습니다. -S/--max-archive-size는 tar 한 개의 최대 크기이며 기본값은 100GB입니다. WAL은 wals/<timeline+log 번호>/ 아래에 세그먼트 단위로 쌓입니다. backup.info는 key=value 텍스트이고 begin_wal, end_wal, timeline, systemid, backup_name 같은 값이 들어 있어서, 삭제와 복구 명령이 이 파일을 읽어 판단합니다.

Backup ID End Time Begin Wal Archival Status Name
20260928T070251 2026-09-28 07:02:52 000000010000000000000005 plain
20260928T070254 2026-09-28 07:02:54 000000010000000000000007 zstd

-n으로 지정한 이름은 barman-cloud-backup-list의 Name 열에 표시됩니다. 백업 ID 자리에는 first/oldest, last/latest, last-failed 단축어도 사용합니다. 뒤의 복구에서 사용한 latest가 그 예입니다.

보존 정책과 삭제​

Barman 서버 모드에서는 barman cron이 보존 정책을 주기적으로 적용합니다(4편). barman-cloud-*에는 그런 상주 프로세스가 없어서, barman-cloud-backup-delete -r를 백업 스케줄 옆에 직접 등록해야 합니다. 정책 문법은 서버 모드와 같은 REDUNDANCY n, RECOVERY WINDOW OF n {DAYS|WEEKS|MONTHS}이고, -m/--minimum-redundancy로 최소 개수를 지정합니다.

백업을 두 개 더 만들어 네 개로 늘린 뒤, 가장 오래된 plain에 keep --target standalone을 설정하고 REDUNDANCY 2를 --dry-run으로 먼저 실행했습니다.

barman-cloud-backup-keep --endpoint-url ... --target standalone \
s3://pgbackup/lab pg18 20260928T070251
barman-cloud-backup-delete --endpoint-url ... -r "REDUNDANCY 2" --dry-run \
s3://pgbackup/lab pg18
Skipping deletion of objects ['lab/pg18/base/20260928T070254/data.tar.zst',
'lab/pg18/base/20260928T070254/backup.info'] due to --dry-run option
Skipping deletion of objects ['.../000000010000000000000001.zst', ... '0002', '0003', '0004',
'0006', '0007.00000028.backup', '0007', '0008', '0009', '000A', '000B', '000C'] due to --dry-run option

삭제 대상 백업은 zstd 하나였습니다. 네 개 가운데 plain은 keep 표시 때문에 개수 계산에서 빠지고, 최신 두 개(b3, b4)가 REDUNDANCY 2를 채웠기 때문입니다. WAL 쪽 결과가 더 흥미로웠습니다. plain의 시작 세그먼트인 ...05와 ...05.00000028.backup은 목록에서 빠졌지만, plain 이후부터 b3 시작 전까지인 ...06~...0C는 삭제 대상에 들어갔습니다.

문서의 WAL 삭제 조건이 이 결과를 설명합니다. 남은 백업 중 가장 오래된 비archival 백업의 begin_wal보다 앞서고, 클라우드에 있는 어떤 archival 백업에도 필요하지 않은 WAL이 삭제 대상입니다. standalone keep은 백업 자체의 일관성에 필요한 WAL만 보존하기 때문에, plain 시점으로는 되돌려도 plain과 b3 사이의 임의 시점으로는 PITR이 되지 않습니다. 그 구간이 필요하면 --target full로 표시해야 합니다.

실제 삭제에는 0.253초가 걸렸고, 버킷은 30개 객체 97MiB에서 16개 객체 85MiB로 줄었습니다. plain이 압축하지 않은 69MiB라서 크기는 많이 줄지 않았습니다.

Backup ID End Time Begin Wal Archival Status Name
20260928T070251 2026-09-28 07:02:52 000000010000000000000005 KEEP:STANDALONE plain
20260928T070408 2026-09-28 07:04:08 00000001000000000000000D b3
20260928T070415 2026-09-28 07:04:15 000000010000000000000011 b4

keep 표시는 base/20260928T070251/annotations/keep라는 10바이트 객체로 저장됐습니다. 버킷 수명 주기 규칙(lifecycle rule)으로 오래된 객체를 삭제하는 방식과 함께 쓰면 이 관계가 깨지므로, 삭제는 barman-cloud-backup-delete에 맡기는 편이 안전합니다. AWS S3의 Object Lock을 쓰는 경우 3.17.0부터 --check-object-lock으로 잠긴 객체가 있는 백업의 삭제를 중단합니다(3.17.0 릴리스 노트).

복구와 PITR​

PITR 대상 시점을 만들기 위해 orders_marker 테이블에 1,000행을 넣었습니다. 2초 뒤 시각 2026-09-28 07:03:20+00을 기록하고, 다시 2초 뒤 테이블을 삭제했습니다.

barman-cloud-restore --endpoint-url ... \
s3://pgbackup/lab pg18 latest /var/lib/postgresql/restore
# real 0m0.330s

barman-cloud-restore는 파일만 풀어 놓습니다. 문서도 "This command does not automatically prepare Postgres for recovery"라고 적고 있습니다. restore_command, recovery target, signal 파일, 필요한 WAL도 직접 준비해야 한다고 안내합니다. 풀린 디렉터리에는 backup_label이 있었고 pg_wal은 비어 있었습니다.

# restore/postgresql.auto.conf 에 추가
port = 5433
archive_mode = off
restore_command = 'barman-cloud-wal-restore --endpoint-url http://barman-cloud-s3-backup-minio:9000 s3://pgbackup/lab pg18 %f %p'
recovery_target_time = '2026-09-28 07:03:20+00'
recovery_target_action = 'promote'
touch /var/lib/postgresql/restore/recovery.signal
chmod 700 /var/lib/postgresql/restore
pg_ctl -D /var/lib/postgresql/restore -l restore.log -w start

archive_mode = off는 복구한 인스턴스가 원본과 같은 경로에 WAL을 올리지 않게 하려고 넣었습니다. 복구 인스턴스가 promote된 뒤 timeline 2의 WAL을 같은 SERVER_NAME으로 올리면 두 클러스터의 이력이 한 경로에 섞입니다.

LOG: restored log file "000000010000000000000007" from archive
LOG: starting point-in-time recovery to 2026-09-28 07:03:20+00
LOG: restored log file "000000010000000000000008" from archive
LOG: consistent recovery state reached at 0/7000120
LOG: recovery stopping before commit of transaction 767, time 2026-09-28 07:03:22.355701+00
LOG: selected new timeline ID: 2
LOG: archive recovery complete

pg_ctl start는 1.118초 만에 반환됐습니다. orders_marker는 1,000행 그대로 돌아왔고 pg_is_in_recovery()는 f였습니다. 로그의 transaction 767이 DROP TABLE이고, 기록한 시각 이후 첫 commit에서 replay가 멈췄습니다. 대량 WAL을 받아야 하는 복구라면 3.19.0에서 추가된 barman-cloud-wal-restore -p/--parallel 옵션을 검토해 볼 만합니다. 여러 세그먼트를 동시에 받아 --spool-dir에 저장하는 방식입니다.

아카이브가 실패하면​

MinIO 컨테이너를 멈춘 상태에서 WAL 세그먼트 두 개를 채웠습니다. 약 40초 뒤 상태는 다음과 같았습니다.

pg_stat_archiver: archived_count=21, failed_count=5, last_failed_wal=000000010000000000000012
archive_status: ...12.ready, ...13.ready

ERROR: Barman cloud WAL archiver exception: Could not connect to the endpoint URL: "http://.../000000010000000000000012.zst"
LOG: archive command failed with exit code 4
WARNING: archiving write-ahead log file "000000010000000000000012" failed too many times, will try again later

PostgreSQL archiver는 같은 세그먼트를 순서대로 재시도합니다. 연속 3회 실패하면 잠시 쉬었다가 다시 시도합니다. 실패한 세그먼트는 .ready 상태로 pg_wal에 남아 있습니다. MinIO를 다시 켜자 70초 안에 두 세그먼트 모두 .done으로 바뀌었습니다. 스토리지 장애가 길어지면 그동안 pg_wal이 계속 커지므로, pg_stat_archiver.failed_count와 last_failed_time을 알람 대상에 넣어 두어야 합니다. Barman 서버의 barman check처럼 이 상태를 대신 확인해 주는 프로세스가 없다는 점이 서버 모드와 가장 크게 다릅니다.

rsync, streaming 모드와 비교​

항목rsync 모드 (3편)streaming 모드 (2편)barman-cloud
백업 저장 위치Barman 서버 디스크Barman 서버 디스크오브젝트 스토리지
Barman 서버필요필요불필요
base backup 전송rsync over SSHpg_basebackupPostgreSQL 호스트에서 업로드
WAL 전송archive_command (SSH)pg_receivewalarchive_command
보존 정책 적용barman cronbarman cronbarman-cloud-backup-delete 직접 실행
파일 단위 dedupreuse_backup = link——
백업 부하 위치PostgreSQL + Barman 서버PostgreSQL + Barman 서버PostgreSQL 호스트

서버 모드에서는 Barman 서버가 여러 PostgreSQL을 한곳에서 관리하고 barman check, barman cron, 카탈로그를 제공합니다. barman-cloud는 그 관리 계층을 포기하는 대신 서버 한 대를 따로 운영하지 않아도 됩니다. 압축과 업로드에 PostgreSQL 호스트의 CPU를 쓴다는 점도 설계할 때 고려해야 합니다. 이번 측정 규모에서는 부하 차이가 드러나지 않았고, 대용량에서 비교한 수치는 없습니다.

두 방식을 함께 쓰는 구성도 있습니다. 문서는 barman-cloud-backup을 Barman 서버의 post_backup_retry_script hook으로 사용해 서버에 받은 백업을 클라우드로 복사하는 구성을 안내합니다. 로컬에서 빠르게 복구하고 원격 사본은 버킷에 두는 구조입니다.

장점​

PostgreSQL 호스트와 버킷만 있으면 백업이 성립합니다. 서버 대수를 줄이려는 환경이나 이미 오브젝트 스토리지를 표준 백업 위치로 쓰는 조직에 적합합니다. 버킷의 내구성과 수명 비용 구조를 그대로 가져오므로 원격 보관을 따로 설계하지 않아도 됩니다.

객체 배치가 단순합니다. base/<ID>/data.tar*, backup.info, wals/<prefix>/<segment> 구조라서 mc나 aws s3 ls로도 상태를 확인할 수 있고, 명령이 없는 환경에서도 tar를 직접 받아 풀 여지가 있습니다.

CloudNativePG와 쓰는 도구가 겹칩니다. in-tree 구현이 barman-cloud-restore 같은 명령을 호출하므로, 여기서 익힌 명령과 객체 배치가 Kubernetes 쪽 문제를 분석할 때도 쓰입니다. Barman Cloud Plugin 내부 구조는 이번에 확인하지 않았고 7편에서 다룹니다.

단점과 제약​

  • 상주 프로세스 없음: 보존 정책 적용, 아카이브 감시, 백업 스케줄을 전부 외부(cron, 모니터링)에 맡겨야 함
  • 복구 준비 수동: barman-cloud-restore는 파일만 복원하고 recovery 설정은 직접 작성
  • 증분 없음: 3.20 barman-cloud-backup --help에 incremental 옵션이 없고, 매번 전체 tar를 업로드
  • 부하 위치: 압축과 업로드가 PostgreSQL 호스트의 CPU와 네트워크를 사용
  • 삭제 시점: REDUNDANCY 계산에서 keep 백업이 빠지고, standalone keep은 사이 구간 WAL을 보존하지 않음

증분 백업은 서버 쪽 기능으로 추가되고 있습니다. 3.18.0은 backup_method = postgres에 basebackups_directory, wals_directory를 클라우드 URL로 지정하는 block-level 증분 백업을 실험 기능으로 추가했습니다. 이번 글에서는 측정하지 않았습니다.

버전별 변화와 CloudNativePG​

최근 Barman 릴리스는 클라우드 기능을 barman 본체로 옮기는 방향입니다.

버전날짜클라우드 관련 변경
3.17.02026-01-07barman-cloud-backup-delete --check-object-lock (AWS S3)
3.18.02026-03-12backup_method = local-to-cloud 추가, 클라우드 증분 백업(실험)
3.19.02026-05-20클라우드 백업을 barman restore로 복원, barman cloud-wal-restore, barman-cloud-wal-restore 병렬
3.20.02026-08-27Python 3.12 이상 필요, AWS S3 SSE-C (--sse-customer-key)

3.18.0 릴리스 노트는 local-to-cloud를 설명하면서 이것이 "the first step toward deprecating the standalone barman-cloud-* scripts"라고 적었습니다. 3.20.0 기준으로 스크립트 제거 일정은 찾지 못했습니다. 지금 스크립트로 구성하더라도 이 방향은 알고 있어야 합니다.

CloudNativePG 쪽은 공식 릴리스 노트에서 다음 범위까지 확인했습니다.

CNPG 릴리스날짜in-tree Barman Cloud 제거 예정 버전
1.26.02025-05-231.28.0 (deprecated 발표)
1.27.42026-03-311.29.0에서 1.30.0으로 변경
1.30.02026-06-291.30.0에서 1.31.0으로 변경

1.28.0이 1.29.0으로 바뀐 시점은 이번에 확인하지 못했습니다. 1.30.0 릴리스 노트는 "Users are still encouraged to migrate to the Barman Cloud Plugin"이라고 적고 있습니다. 2026-09-23에 나온 1.30.1에도 in-tree Barman Cloud 복구 관련 수정이 포함되어 있습니다. 제거 일정이 여러 번 미뤄졌지만 방향은 바뀌지 않았으므로, 1.31 전에 플러그인 전환을 계획해 두는 편이 안전합니다. 전환 절차는 7편에서 다룹니다.

정리​

항목내용
구성PostgreSQL 호스트에 barman-cli-cloud, archive_command에 barman-cloud-wal-archive
객체 배치<prefix>/<server>/base/<ID>/, <prefix>/<server>/wals/<prefix>/
보존 정책barman-cloud-backup-delete -r를 직접 스케줄, --dry-run으로 먼저 확인
keepstandalone은 백업 시점만, full은 이후 WAL까지 보존
복구barman-cloud-restore 후 restore_command, recovery target, recovery.signal 직접 작성
감시pg_stat_archiver.failed_count, pg_wal 크기
CNPGin-tree Barman Cloud는 1.31.0 제거 예정 (1.30.0 릴리스 노트)

서버 없이 버킷 하나로 백업이 돌아가는 건 편하지만, Barman 서버가 담당하던 감시와 정리를 이제 직접 챙겨야 해요. 저는 --dry-run 출력으로 WAL 삭제 범위를 한 번 확인해 보고 나서야 standalone keep의 의미가 분명해졌어요.

참고 자료​

Barman PITR 워크북

· 약 7분

백업이 아니라 복구가 시험이다​

2편 마지막에 이렇게 적었어요. "백업의 가치는 백업이 아니라 복구에서 결정됩니다." 이 글은 그 한 줄을 직접 돌려보는 워크북이에요. Barman의 PITR target 옵션 4가지를 한 lab에서 한 번씩 시도해 봐요.

처음 보는 독자도 따라올 수 있게 환경을 짧게 정리합니다.

호스트역할Barman 라벨
demo-pg01PostgreSQL 17 primary해당 없음
demo-barman01Barman 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가지 시점으로 되감아 봅니다.

base backup부터 테이블 삭제까지의 PITR 복구 좌표

복원 대상은 빈 디렉토리입니다. 매 시나리오 사이에 비우고 시작합니다.

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에는 도달하지 않습니다.

타임존은 Barman 서버의 시스템 시간대가 기본입니다. 다른 TZ로 명시하려면 2026-05-06 14:32:00+09 형태로 붙입니다. 운영에서는 항상 명시하는 편이 안전합니다.

"Recovery targets must be a value after the end of the backup." — base backup 종료(End time) 이전 시점으로는 복구할 수 없습니다. 시점이 End time보다 이르면 즉시 실패합니다.

시나리오 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-lsnLSN(3/64000000 형식)으로 되감기XID보다 더 세밀, 특정 WAL 위치를 정확히 알 때
--target-tli특정 timeline으로 복구latest / current / 숫자 ID
--target-action도달 후 동작: shutdown / pause / promote미지정 시 PostgreSQL이 paused 후 운영자 결정 대기
--exclusivetarget 직전까지(target 자체 제외) replay기본은 target 포함
--standby-modereplica로 일어나도록 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 timebase backup 경계이며 그 이전 시점에는 도달 불가
Begin LSN / End LSNbase backup의 LSN 경계이며 --target-lsn 사용 시 기준점
Begin Offset / End OffsetWAL 파일 내 위치

WAL을 더 세밀하게 보려면 pg_waldump로 commit 레코드를 확인합니다. streaming/에는 수신 중인 .partial 파일만 머물고, 완성된 WAL은 wals/<앞 16자리>/ 아래로 아카이브됩니다. 아래 명령은 Barman 호스트에 PostgreSQL 17 클라이언트 패키지(pg_waldump 포함)가 설치돼 있다는 전제입니다. Barman에 WAL 압축을 설정했다면 파일을 먼저 풀어야 합니다.

sudo -u barman /usr/pgsql-17/bin/pg_waldump \
/var/lib/barman/pg01/wals/0000000100000000/000000010000000000000004 \
| grep COMMIT | head

각 줄에 LSN, XID, timestamp가 함께 나오므로, 사고 직전의 어떤 좌표를 사용할지 골라잡기 쉬워집니다.

자주 만나는 함정 5가지​

증상원인해결
Recovery targets must be a value after the end of the backuptarget이 base backup 종료 이전barman show-backup의 End 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은 두 단계로 나뉘어요. 시점을 정하는 일이 반이고 복구하는 일이 반인데, 보통은 시점을 정하는 쪽이 더 어려워요.

참고 자료​

Barman 빠른 시작

· 약 8분

어떻게 Barman인가​

1편이 "왜 Barman인가"였다면, 이 글은 "어떻게 Barman인가"예요. 1편에서 Barman의 14년 궤적과 아키텍처를 정리했어요. 이번에는 같은 자리에서 한 발짝 더 들어가 두 대의 Rocky Linux 머신을 준비하고, streaming-only 모드로 PostgreSQL을 설정한 뒤, 5개 명령어로 첫 백업과 PITR 복구까지 끝내 봅니다.

이번에 다룰 시나리오를 한 화면에 펼치면 다음과 같습니다.

단계명령어의미
1barman check pg01PG 연결, streaming, 권한 확인
2barman backup pg01첫 base backup
3barman list-backup pg01카탈로그 조회
4barman recover pg01 latest <dir>최신 백업 복원
5barman recover ... --target-time "..."임의 시점 (PITR) 복원

읽으면서 따라 할 수 있도록 모든 명령어와 설정 파일 내용을 그대로 옮겨 둡니다.

사전 준비​

두 호스트로 lab 환경을 구성합니다.

호스트역할OS
demo-pg01PostgreSQL 17 primaryRocky Linux 9
demo-barman01Barman 3.18 서버Rocky Linux 9

두 호스트가 hostname으로 서로 통신한다고 가정합니다(DNS 또는 /etc/hosts). 단일 머신에서 시험하려면 demo-pg01과 demo-barman01을 모두 localhost로 두고 진행해도 됩니다.

PostgreSQL에서 Barman으로 base backup과 WAL이 전달되는 흐름

설치​

PGDG 저장소에서 설치합니다.

demo-pg01과 demo-barman01 양쪽에 PGDG 저장소를 등록합니다.

sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql

demo-pg01에서 PostgreSQL 17 설치/초기화:

sudo dnf install -y postgresql17-server postgresql17-contrib
sudo /usr/pgsql-17/bin/postgresql-17-setup initdb
sudo systemctl enable --now postgresql-17

demo-barman01에 Barman 3.18 설치:

sudo dnf install -y barman barman-cli

설치 시 barman 시스템 사용자가 자동 생성되며, 백업 카탈로그 기본 경로는 /var/lib/barman입니다. /etc/cron.d/barman도 함께 깔리므로 별도 systemd timer 설정 없이 cron이 매 분 한 번 barman cron을 돌립니다.

설정 1: PostgreSQL 측 (demo-pg01)​

streaming 백업은 PostgreSQL의 replication 프로토콜 위에서 동작하므로, replication user와 replication slot이 필요합니다.

/var/lib/pgsql/17/data/postgresql.conf 핵심 항목:

listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10

/var/lib/pgsql/17/data/pg_hba.conf에 Barman 측 접속을 열어 둡니다.

# TYPE DATABASE USER ADDRESS METHOD
host replication barman demo-barman01 scram-sha-256
host postgres barman demo-barman01 scram-sha-256

PostgreSQL을 재시작한 후 사용자와 슬롯을 생성합니다.

sudo systemctl restart postgresql-17
sudo -u postgres psql <<'SQL'
CREATE USER barman WITH REPLICATION ENCRYPTED PASSWORD 'changeme';
GRANT EXECUTE ON FUNCTION pg_backup_start(text, boolean) TO barman;
GRANT EXECUTE ON FUNCTION pg_backup_stop(boolean) TO barman;
GRANT EXECUTE ON FUNCTION pg_switch_wal() TO barman;
GRANT EXECUTE ON FUNCTION pg_create_restore_point(text) TO barman;
GRANT pg_read_all_settings, pg_read_all_stats TO barman;
SELECT pg_create_physical_replication_slot('barman');
SQL

실제 운영에서는 changeme을 비밀 관리자(Vault, AWS Secrets Manager 등)로 옮기고 .pgpass 또는 환경변수로 분리해야 합니다.

설정 2: Barman 측 (demo-barman01)​

/etc/barman.conf는 default 값 그대로 두는 편이 무난합니다. 서버별 설정만 추가합니다(default 항목 자체는 별도 글에서 다룹니다).

/etc/barman.d/pg01.conf:

[pg01]
description = "Production PG primary"
conninfo = host=demo-pg01 user=barman dbname=postgres
streaming_conninfo = host=demo-pg01 user=barman
backup_method = postgres
streaming_archiver = on
slot_name = barman
create_slot = manual
retention_policy = RECOVERY WINDOW OF 4 WEEKS

[pg01]은 Barman 서버 라벨이자 서버 식별자입니다. CLI(barman backup pg01), 카탈로그 디렉토리(/var/lib/barman/pg01/...), conf 파일명(pg01.conf)에 같은 값을 씁니다. 실제 호스트네임 demo-pg01과는 별개이며, Barman이 부르는 이름과 네트워크가 부르는 이름을 분리한 것입니다.

conninfo = host=demo-pg01 ...에는 Barman이 PostgreSQL에 접속할 때 사용하는 실제 호스트네임을 지정하므로 라벨과 달라도 자연스럽습니다. backup_method = postgres는 pg_basebackup을 통한 streaming 백업을 뜻하고, streaming_archiver = on은 WAL을 pg_receivewal 방식으로 받도록 설정합니다. slot_name = barman은 앞에서 만든 replication slot을 사용합니다. create_slot = manual로 지정한 이유는 슬롯을 SQL로 이미 만들었으므로 Barman이 자동 생성하지 않게 하기 위해서입니다.

비밀번호는 barman 사용자의 ~/.pgpass로 분리합니다.

sudo -u barman tee ~barman/.pgpass > /dev/null <<'EOF'
demo-pg01:5432:*:barman:changeme
EOF
sudo chmod 600 ~barman/.pgpass
sudo chown barman:barman ~barman/.pgpass

5개 명령어 시나리오​

이제부터는 모든 명령어를 barman 사용자로 실행합니다. sudo -i -u barman으로 barman 셸에 들어가거나, 명령어마다 sudo -u barman을 앞에 붙입니다.

1. barman check pg01​

셋업을 검증합니다.

sudo -u barman barman check pg01

기대 출력 (요약):

Server pg01:
PostgreSQL: OK
wal_level: OK
replication slot: OK
directories: OK
retention policy settings: OK
pg_basebackup: OK
pg_basebackup compatible: OK
systemid coherence: OK
pg_receivexlog: OK
receive-wal running: OK
archiver errors: OK

한 줄이라도 FAILED가 나오면 그 항목에 셋업 단계의 문제가 있습니다. 가장 자주 보이는 receive-wal running: FAILED는 cron이 아직 한 번도 돌지 않았거나 slot 이름이 어긋났다는 뜻입니다. 한 번 강제로 돌려 두는 편이 빠릅니다.

sudo -u barman barman cron

2. barman backup pg01​

첫 base backup을 실행합니다.

sudo -u barman barman backup pg01

기대 출력 (요약):

Starting backup using postgres method for server pg01 in /var/lib/barman/pg01/base/20260504T143012
Backup start at LSN: 0/3000028
Starting backup copy via pg_basebackup for 20260504T143012
Copy done (time: 12 seconds)
Backup size: 41.5 MiB
Backup end at LSN: 0/4000060
Marking backup as DONE
Backup completed (start time: 2026-05-04 14:30:12, elapsed time: 14 seconds)

/var/lib/barman/pg01/base/<timestamp>/ 아래에 base backup이 쌓입니다. WAL은 /var/lib/barman/pg01/streaming/으로 수신된 뒤 /var/lib/barman/pg01/wals/ 아래에 아카이브됩니다.

3. barman list-backup pg01​

카탈로그를 조회합니다.

sudo -u barman barman list-backup pg01

기대 출력:

pg01 20260504T143012 - F - 2026-05-04 14:30:26 - Size: 41.5 MiB - WAL Size: 16 MiB

F는 full backup입니다. backup-id는 timestamp 기반(20260504T143012)이며, latest 키워드를 별칭으로 쓸 수 있습니다.

상세 보기:

sudo -u barman barman show-backup pg01 latest

Begin time / End time / Begin LSN / End LSN 등 PITR 타깃을 결정할 때 필요한 값이 모두 여기에 있습니다.

4. barman recover​

최신 백업으로 복원합니다.

복원 대상은 빈 디렉토리여야 합니다. PostgreSQL이 새로 기동할 자리를 미리 비워 둡니다.

sudo mkdir -p /var/lib/barman/restore
sudo chown barman:barman /var/lib/barman/restore

sudo -u barman barman recover pg01 latest /var/lib/barman/restore

복원이 끝나면 그 디렉토리 안에 base backup이 풀리고, recovery.signal과 postgresql.auto.conf의 restore_command 항목이 자동 생성됩니다. PostgreSQL을 그 데이터 디렉토리로 띄우면 곧장 기동합니다.

다른 호스트로 직접 복원하려면 --remote-ssh-command "ssh postgres@<host>"를 추가합니다. 이때 barman 사용자에서 그 호스트의 postgres로 SSH 키 기반 접속이 미리 설정되어 있어야 합니다.

5. barman recover --target-time​

특정 시점으로 되감는 PITR을 실행합니다.

sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-time "2026-05-04 15:00:00"

복원 시점은 base backup 종료(End time) 이후, 가장 마지막에 받은 WAL 이전 사이여야 합니다. 정확한 경계가 헷갈리면 barman show-backup pg01 latest의 Begin time / End time을 기준으로 잡습니다.

다른 PITR 타깃 옵션도 같은 자리에 지정합니다.

옵션의미
--target-time시점 기준
--target-xid트랜잭션 ID 기준
--target-namepg_create_restore_point()로 만든 명시적 라벨
--target-immediatebase backup 직후 일관성 시점

일상 운영​

cron 한 줄과 보존 정책을 설정합니다.

설치 시 동봉된 /etc/cron.d/barman이 매 분 barman cron을 실행합니다(WAL 수신과 아카이브 정리 담당). 정기 백업은 별도 cron 한 줄로 잡는 방식이 표준입니다.

# /etc/cron.d/barman-backup — 매일 02:00에 모든 등록 서버 백업
0 2 * * * barman /usr/bin/barman backup all

보존 정책은 서버별 conf 한 줄로 끝납니다.

retention_policy = RECOVERY WINDOW OF 4 WEEKS
# 또는 개수 기반:
# retention_policy = REDUNDANCY 5

RECOVERY WINDOW는 "이 시점부터 N 단위(WEEKS/DAYS) 전까지 PITR이 가능하도록 보장"한다는 의미입니다. 지난 4주 동안 임의 시점으로 되감을 수 있도록 base backup과 WAL을 함께 보존합니다. 정책에서 벗어난 backup은 barman cron이 자동으로 정리합니다.

자주 만나는 에러 빠른 가이드​

증상원인한 줄 해결
receive-wal running: FAILEDstreaming WAL receiver 미실행barman cron 수동 실행, slot 이름/권한 재점검
replication slot: FAILEDPG 측 슬롯이 없음 / 이름 불일치pg_create_physical_replication_slot('barman') 다시 실행
pg_basebackup: FAILEDreplication 권한/pg_hba 미흡barman 사용자의 REPLICATION 속성 확인, pg_hba에 host replication 행
Connection refusedlisten_addresses, 방화벽postgresql.conf의 listen_addresses = '*', firewalld에서 5432 허용

barman check는 한 번에 끝내려 들기보다 "FAIL 한 줄씩 잡아 나가는 도구"로 보면 마음이 편합니다. 위 4개를 해결하면 첫 셋업의 90%가 끝납니다.

정리​

항목내용
백업 모델streaming-only (SSH 없이 pg_basebackup + replication slot)
카탈로그 위치/var/lib/barman/pg01/{base,streaming,wals}
일상 명령어barman cron (자동) + barman backup all (cron 1회/일)
검증 명령어barman check pg01 — 셋업 직후/이상 발생 시 첫 진단
PITRbarman recover ... --target-time "..." 한 줄
보존 정책RECOVERY WINDOW OF 4 WEEKS 권장

백업의 가치는 백업이 아니라 복구에서 결정돼요. 셋업 직후 PITR을 한 번은 반드시 돌려봐요.

참고 자료​