原本是用 pg_dump 為基礎的工具幫網站備份 PostgreSQL 資料
但是先在機器上產生檔案,再上傳到 S3 上,導致要先佔用一部分的儲存空間
加上每次都是完整備份,S3 儲存空間使用量變得非常大

所以想要使用 pgBackRest 來備份,這邊紀錄一下架設的指令與設定檔

選用 pgBackRest 有以下幾點考量:

  • 可以直接上傳到 S3
  • 支援加密
  • 支援 Full, Differential, & Incremental 備份

安裝

因為機器 OS 是用 Ubuntu,且 APT 有加上 apt.postgresql.org
所以直接:

sudo apt install pgbackrest

驗證安裝:

$ sudo -u postgres pgbackrest
pgBackRest 2.55.1 - General help

Usage:
    pgbackrest [options] [command]

Commands:
    annotate        add or modify backup annotation
    archive-get     get a WAL segment from the archive
    archive-push    push a WAL segment to the archive
    backup          backup a database cluster
    check           check the configuration
    expire          expire backups that exceed retention
    help            get help
    info            retrieve information about backups
    repo-get        get a file from a repository
    repo-ls         list files in a repository
    restore         restore a database cluster
    server          pgBackRest server
    server-ping     ping pgBackRest server
    stanza-create   create the required stanza data
    stanza-delete   delete a stanza
    stanza-upgrade  upgrade a stanza
    start           allow pgBackRest processes to run
    stop            stop pgBackRest processes from running
    verify          verify contents of a repository
    version         get version

Use 'pgbackrest help [command]' for more information.

設定

S3

建立 Bucket, Access Key 和 Secret Key

pgBackRest

設定 /etc/pgbackrest.conf,S3 的部分要換成剛剛設定的

[global]
repo1-type=s3
repo1-s3-bucket=xxxxx-bucket
repo1-s3-endpoint=xxxxx-endpoint
repo1-s3-key=xxxxx
repo1-s3-key-secret=xxxxx
repo1-s3-region=us-east-1
repo1-retention-full=28
repo1-retention-full-type=time
repo1-cipher-pass=xxxxx
repo1-cipher-type=aes-256-cbc

start-fast=y
delta=y
archive-async=y
archive-push-queue-max=100MB

[global:archive-push]
compress-level=3

[main]
pg1-path=/var/lib/postgresql/15/main

參數說明

設定完整備份至少保留 28 天 (retention-full-type=time)

repo1-retention-full=28
repo1-retention-full-type=time

如果要加密的話,可以設定這兩個參數,密碼可以使用 openssl rand -base64 48 產生

repo1-cipher-pass=xxxxx
repo1-cipher-type=aes-256-cbc

如果是使用 Minio 來替代 S3,要記得調整設定

repo1-s3-uri-style=path

初始化 stanza

這邊會在 S3 上建立必要的檔案,可以剛好驗證連線
如果有變更 stanza 的名稱,要記得替換 --stanza 參數

$ sudo -u postgres pgbackrest --stanza=main --log-level-console=info stanza-create

範例輸出:

2025-07-16 06:42:45.472 P00   INFO: stanza-create command begin 2.55.1: --exec-id=890770-b94b6b8a --log-level-console=info --pg1-path=/var/lib/postgresql/15/main --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-s3-bucket=xxxx --repo1-s3-endpoint=xxxxx --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-type=s3 --stanza=main
2025-07-16 06:42:45.481 P00   INFO: stanza-create for stanza 'main' on repo1
2025-07-16 06:42:47.016 P00   INFO: stanza-create command end: completed successfully (1548ms)

PostgreSQL

設定 /etc/postgresql/15/main/postgresql.conf,並重新啟動

archive_mode = on
archive_command = 'pgbackrest --stanza=main archive-push %p'
wal_level = replica

持續備份 WAL 是必要的,不然還原出來的資料庫會無法使用

接著執行 check 指令確認設定

$ sudo -u postgres pgbackrest --stanza=main --log-level-console=info check

範例輸出:

2025-07-16 08:16:44.161 P00   INFO: check command begin 2.55.1: --exec-id=894728-775125c7 --log-level-console=info --pg1-path=/var/lib/postgresql/15/main --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-s3-bucket=xxxx --repo1-s3-endpoint=xxxxx --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-type=s3 --stanza=main
2025-07-16 08:16:44.193 P00   INFO: check repo1 configuration (primary)
2025-07-16 08:16:44.631 P00   INFO: check repo1 archive for WAL (primary)
2025-07-16 08:16:46.977 P00   INFO: WAL segment 000000010000000D00000008 successfully archived to '/var/lib/pgbackrest/archive/main/15-1/000000010000000D/000000010000000D00000008-3c6485e7ed9dec8f0f964d55bdfba092fd815fd1.gz' on repo1
2025-07-16 08:16:46.978 P00   INFO: check command end: completed successfully (2822ms)

Backup

預設是 Full 備份

$ sudo -u postgres pgbackrest --stanza=main --log-level-console=info backup

範例輸出:

2025-07-16 08:22:05.655 P00   INFO: backup command begin 2.55.1: --checksum-page --delta --exec-id=894932-75be644c --log-level-console=info --pg1-path=/var/lib/postgresql/15/main --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-retention-full=28 --repo1-retention-full-type=time --repo1-s3-bucket=xxxx --repo1-s3-endpoint=xxxxx --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-type=s3 --stanza=main --start-fast
2025-07-16 08:22:06.004 P00   WARN: checksum-page option set to true but checksums are not enabled on the cluster, resetting to false
2025-07-16 08:22:06.081 P00   WARN: no prior backup exists, incr backup has been changed to full
2025-07-16 08:22:06.081 P00   INFO: execute non-exclusive backup start: backup begins after the requested immediate checkpoint completes
2025-07-16 08:22:06.121 P00   INFO: backup start archive = 000000010000000D0000000A, lsn = D/A000028
2025-07-16 08:22:06.121 P00   INFO: check archive for prior segment 000000010000000D00000009
2025-07-16 08:40:44.330 P00   INFO: execute non-exclusive backup stop and wait for all WAL segments to archive
2025-07-16 08:40:44.466 P00   INFO: backup stop archive = 000000010000000D0000000A, lsn = D/A043E68
2025-07-16 08:40:44.895 P00   INFO: check archive for segment(s) 000000010000000D0000000A:000000010000000D0000000A
2025-07-16 08:40:45.985 P00   INFO: new backup label = 20250716-082206F
2025-07-16 08:40:49.470 P00   INFO: full backup size = 3.8GB, file total = 2801
2025-07-16 08:40:49.470 P00   INFO: backup command end: completed successfully (1123824ms)
2025-07-16 08:40:49.471 P00   INFO: expire command begin 2.55.1: --exec-id=894932-75be644c --log-level-console=info --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-retention-full=28 --repo1-retention-full-type=time --repo1-s3-bucket=xxxx --repo1-s3-endpoint=xxxxx --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-type=s3 --stanza=main
2025-07-16 08:40:49.733 P00   INFO: repo1: time-based archive retention not met - archive logs will not be expired
2025-07-16 08:40:49.733 P00   INFO: expire command end: completed successfully (263ms)

Differential 備份

$ sudo -u postgres pgbackrest --stanza=main --log-level-console=info --type=diff backup

Incremental 備份

$ sudo -u postgres pgbackrest --stanza=main --log-level-console=info --type=incr backup

Restore

沒有經過 Restore 測試的備份不是備份

首先要先停止 postgresql

$ sudo systemctl stop postgresql@15-main.service

整個資料庫會被覆蓋,Restore 前要確認清楚

$ sudo -u postgres pgbackrest --stanza=main --log-level-console=info restore

範例輸出:

2025-07-18 10:55:37.334 P00   INFO: restore command begin 2.55.1: --delta --exec-id=7903-3d1b5292 --log-level-console=info --pg1-path=/var/lib/postgresql/15/main --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-s3-bucket=xxxx --repo1-s3-endpoint=xxxxx --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --repo1-type=s3 --stanza=main
2025-07-18 10:55:37.414 P00   INFO: repo1: restore backup set 20250716-082206F_20250716-183022I, recovery will start at 2025-07-16 18:30:22
2025-07-18 10:55:37.415 P00   INFO: remove invalid files/links/paths from '/var/lib/postgresql/15/main'
2025-07-18 10:56:29.513 P00   INFO: write updated /var/lib/postgresql/15/main/postgresql.auto.conf
2025-07-18 10:56:29.515 P00   INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started)
2025-07-18 10:56:29.516 P00   INFO: restore size = 3.8GB, file total = 2801
2025-07-18 10:56:29.517 P00   INFO: restore command end: completed successfully (52184ms)

完成後啟動資料庫驗證資料

$ sudo systemctl start postgresql@15-main.service

Cron

$ crontab -e -u postgres
# m h  dom mon dow   command
30 19  *   *   0     pgbackrest --type=full --stanza=main backup > /dev/null 2>&1
30 19  *   *   1-6   pgbackrest --type=diff --stanza=main backup > /dev/null 2>&1

監控備份與 WAL 狀況

// TODO: 待補充

參考資料