← PostgreSQL DBA← PostgreSQL DBA
PostgreSQL DBAPostgreSQL DBA19 Th7, 2026Jul 19, 202627 phút đọc22 min read

Backup & RecoveryBackup & Recovery

Thuộc bộ kiến thức PostgreSQL DBA Roadmap.

Tổng quan

Backup và recovery có lẽ là trách nhiệm quan trọng nhất mà một DBA PostgreSQL phải gánh vác. Sai sót về indexing chỉ làm chậm query; chiến lược vacuum tồi tốn dung lượng đĩa và gây bloat; nhưng một chiến lược backup tồi sẽ khiến doanh nghiệp mất dữ liệu — vĩnh viễn, đúng vào lúc họ chịu đựng ít nhất. Mọi chủ đề khác trong bộ kiến thức này nói về việc làm cho một database khỏe mạnh chạy nhanh hơn, an toàn hơn, hoặc sẵn sàng cao hơn — chủ đề này nói về chuyện gì xảy ra khi tất cả những điều đó vẫn thất bại: một table bị drop, một câu DELETE thiếu WHERE, một storage volume hỏng, một cuộc tấn công ransomware, một lần migration bị lỗi. Nếu bạn không thể restore lại thời điểm đó, thì replication hay tuning ở mọi nơi khác cũng không còn ý nghĩa gì.

Bài học trung tâm mà các DBA kỳ cựu luôn nhắc lại là: một backup chưa từng được test không phải là backup — nó chỉ là một file chưa được xác minh, có thể restore được hoặc không. Backup thất bại một cách âm thầm nhiều hơn là thất bại ồn ào: một pg_dump hoàn tất với exit code 0 nhưng chạy trên một connection bị ngắt giữa chừng, một WAL archive có khoảng trống âm thầm, một base backup lấy từ filesystem snapshot mà thực ra không nhất quán ở mức crash-consistent. Cách duy nhất để biết một chiến lược backup có hoạt động hay không là thường xuyên, một cách máy móc, restore từ nó và xác minh kết quả — chứ không phải cho rằng dấu tick xanh trong log của cron job đồng nghĩa với khả năng khôi phục được.

Bài này đi qua hai cơ chế backup khác biệt cơ bản mà PostgreSQL cung cấp — logical (pg_dump/pg_dumpall/pg_restore) và physical (pg_basebackup) — cách physical backup kết hợp với WAL archiving liên tục để mang lại Point-in-Time Recovery (PITR), hệ sinh thái công cụ trưởng thành quản lý việc này cho bạn trong production (Barman, pgBackRest, WAL-G, pg_probackup), cách thực sự xác minh rằng backup restore được, và pg_upgrade khớp vào bức tranh này như thế nào khi nâng cấp major version. Kết thúc bằng một bảng quyết định ánh xạ loại backup với đặc tính RPO/RTO và tooling. Nạp dữ liệu hàng loạt được nói ở ./06-modifying-and-bulk-loading-data.md, lớp storage mà WAL và vacuum vận hành trên đó ở ./11-storage-internals-and-vacuum.md, và backup liên quan (và khác biệt) thế nào với replication/HA failover ở ./13-replication-and-high-availability.md. Về khía cạnh tổ chức khi recovery trở thành một sự cố đang diễn ra — giao tiếp, thẩm quyền ra quyết định, giữ cho doanh nghiệp vận hành trong lúc restore — xem ../engineering-manager/en/12-crisis-leadership-and-business-continuity.md.

Kiến thức nền tảng

Logical backup: pg_dump

pg_dump tạo ra một logical backup của một database duy nhất: một chuỗi câu lệnh SQL có thể tái tạo (hoặc một dạng mã hóa nhị phân tương đương), mà khi replay lại sẽ tạo lại schema và data. Vì nó hoạt động ở mức SQL thay vì mức disk-block, output của pg_dump có tính portable — có thể restore vào một major version PostgreSQL khác, một OS khác, thậm chí một kiến trúc CPU khác, điều mà physical backup hoàn toàn không làm được.

pg_dump hỗ trợ nhiều format output, và lựa chọn này có ý nghĩa vận hành:

# Plain SQL format — dễ đọc, restore bằng psql, dễ kiểm tra/chỉnh sửa nhất
pg_dump -h localhost -U postgres -d shop_db -F p -f shop_db.sql

# Custom format (-Fc) — nén, và là format duy nhất tương thích với các tính năng
# restore chọn lọc/parallel của pg_restore; là default được khuyến nghị cho hầu hết use case
pg_dump -h localhost -U postgres -d shop_db -F c -f shop_db.dump

# Directory format (-Fd) — mỗi table một file cộng với một TOC, cho phép DUMP
# song song qua -j ngoài việc restore song song
pg_dump -h localhost -U postgres -d shop_db -F d -j 4 -f shop_db_dir/

Plain SQL format đơn giản nhất để suy luận và diff, nhưng dump và restore đơn luồng và không cung cấp khả năng restore chọn lọc ngoài việc chỉnh sửa văn bản SQL bằng tay. Custom format (-Fc) mặc định được nén, không thực thi ngay lập tức bất cứ điều gì (nó là một archive mà pg_restore diễn giải), và là format mà hầu hết DBA chọn làm mặc định: hỗ trợ restore chọn lọc (table cụ thể, schema cụ thể), sắp xếp lại thứ tự, và restore song song. Directory format (-Fd) đi xa hơn một bước và cũng song song hóa cả việc dump (-j N), điều này quan trọng khi database đủ lớn để một lần dump đơn luồng mất hàng giờ — mỗi table được dump vào file riêng của nó cùng lúc.

Việc pg_dump một table hoặc database lớn có thể chậm, và có một điểm tinh tế đáng hiểu rõ: pg_dump lấy một snapshot nhất quán duy nhất của database ngay từ đầu lần dump (nó chạy bên trong một transaction dùng isolation REPEATABLE READ nội bộ), nên dump vẫn nhất quán về mặt transaction dù có thể mất hàng giờ để hoàn thành — các write sau đó vào database trong lúc dump đơn giản là không được bao gồm, và dump không thấy trạng thái commit dở dang nào. Điều nó không cho bạn là một điểm-trong-thời-gian có thể khôi phục cụ thể sau đó; bạn chỉ có đúng một snapshot, tại thời điểm dump bắt đầu, không hơn.

Logical backup: pg_dumpall

pg_dump chỉ hoạt động trên một database duy nhất và, một cách có chủ đích, không capture các object ở mức cluster (toàn instance) — role, tablespace, và các object khác nằm ngoài phạm vi bất kỳ database nào. pg_dumpall lấp đầy khoảng trống đó:

# Dump TẤT CẢ database trong cluster cộng với role/tablespace, ở plain SQL
pg_dumpall -h localhost -U postgres -f full_cluster.sql

# Pattern phổ biến: chỉ dump globals (roles + tablespaces), rồi pg_dump
# từng database riêng ở custom format để restore chọn lọc/song song
pg_dumpall -h localhost -U postgres --globals-only -f globals.sql
pg_dump -h localhost -U postgres -d shop_db -F c -f shop_db.dump
pg_dump -h localhost -U postgres -d analytics_db -F c -f analytics_db.dump

pg_dumpall chỉ tạo output plain SQL — nó không có custom/directory format nên không có dump song song hay restore chọn lọc. Trong thực tế, đa số hệ thống production dùng pattern kết hợp ở trên: pg_dumpall --globals-only để capture định nghĩa role và tablespace một lần, cộng với pg_dump -Fc riêng cho từng database để mỗi database có thể được restore độc lập và song song.

Restore với pg_restore

pg_restore là công cụ đối ứng của dump custom (-Fc) và directory (-Fd) format — dump plain SQL được restore bằng psql thay vào đó, vì chúng chỉ là các script SQL.

# Restore một dump custom-format vào một database mới đã tạo sẵn
createdb -h localhost -U postgres shop_db_restored
pg_restore -h localhost -U postgres -d shop_db_restored -j 4 shop_db.dump

# Restore chỉ một phần object — ví dụ một table — từ cùng một dump
pg_restore -h localhost -U postgres -d shop_db_restored \
  -t orders --data-only shop_db.dump

# Liệt kê table of contents của dump mà không restore gì
pg_restore -l shop_db.dump

Flag -j (số parallel job) là lý do chính khiến custom/directory format được ưa chuộng hơn plain SQL cho bất cứ thứ gì lớn hơn một database nhỏ: pg_restore -j 4 restore data của nhiều table và rebuild nhiều index đồng thời, có thể biến một lần restore mất hàng giờ khi đơn luồng thành chỉ còn một phần nhỏ thời gian đó. Restore chọn lọc — chỉ lấy một table, một schema, hoặc data-only/schema-only — cũng chỉ khả thi với archive custom hoặc directory format, dùng table of contents (pg_restore -l) để chọn chính xác thứ bạn cần.

Khi nào logical backup phù hợp (và khi nào không)

Logical backup là lựa chọn đúng khi: database nhỏ-đến-vừa và một chu trình dump/restore đầy đủ vừa vặn trong maintenance window của bạn; bạn đang migrate giữa các major version PostgreSQL (dump/restore logical độc lập với version, khác với copy vật lý gắn chặt với on-disk format của một major version); bạn cần restore hoặc copy chỉ một table hoặc schema thay vì toàn bộ cluster; hoặc bạn muốn một artifact portable, có thể kiểm tra hoặc diff (cho dump schema-only).

Hạn chế của chúng là tốc độ restore khi ở quy mô lớn. Restore một pg_dump nghĩa là phải thực thi lại INSERT (hoặc COPY) từng dòng một rồi rebuild lại mọi index từ đầu — với một database vài trăm gigabyte điều này có thể mất nhiều giờ dù có -j parallelism, vì việc build index và validate constraint là công việc thâm dụng CPU và I/O, mở rộng theo khối lượng dữ liệu bất kể format. Đây là lý do cốt lõi khiến các hệ thống production với database lớn dựa vào physical backup làm đường phục hồi chính, và chỉ dành logical backup cho tính portable, migration, và các use case chọn lọc/nhỏ.

Physical backup: pg_basebackup

Physical backup là một bản sao byte-cho-byte của data directory của PostgreSQL — các file thực sự mà PostgreSQL dùng để lưu heap page, index, và WAL — thay vì tái tạo logical dữ liệu qua SQL. pg_basebackup lấy bản sao như vậy qua replication protocol trong khi server vẫn đang chạy và chấp nhận write, và nó xử lý phần khó cho bạn: đảm bảo bản sao nhất quán dù các file đang được copy trong khi đồng thời bị chỉnh sửa.

# Lấy một physical base backup, ở tar format nén, kèm luồng WAL segment tương ứng
# để backup có thể dùng được ngay lập tức một mình
pg_basebackup -h localhost -U replicator \
  -D /var/backups/pgbase/$(date +%Y%m%d) \
  -Ft -z -Xstream -P

# Directory format thuần thay vì tar, hữu ích khi bạn muốn kiểm tra
# hoặc khởi động PostgreSQL trực tiếp trên các file đã copy
pg_basebackup -h localhost -U replicator \
  -D /var/backups/pgbase_plain \
  -Fp -Xstream -P

Các flag chính: -Ft tạo một archive kiểu .tar.gz cho mỗi tablespace (gọn để chuyển/lưu trữ); -Fp tạo một cây directory thuần phản chiếu một data directory thật (khởi động được ngay). -Xstream stream WAL được sinh ra trong lúc backup cùng với base backup, đây là điều làm cho backup dùng được độc lập mà không phụ thuộc vào một WAL archive được cấu hình riêng phải khả dụng ở thời điểm restore. -P hiển thị tiến trình. Role kết nối cần quyền REPLICATION (hoặc thành viên các role pg_read_all_settings/phù hợp tùy version), và pg_hba.conf phải cho phép kết nối replication từ host backup.

Bên trong, pg_basebackup hoạt động bằng cách gọi logic tương đương pg_start_backup() (qua replication protocol, không phải các hàm SQL đã deprecated) để ghi lại vị trí WAL bắt đầu, stream các file dữ liệu thật, rồi gọi logic tương đương stop-backup để ghi lại vị trí WAL kết thúc — vì các file được copy trong khi write vẫn tiếp diễn, một số page có thể bị copy ở trạng thái không nhất quán, “torn”, và chính các WAL segment sinh ra giữa lúc start và stop cho phép PostgreSQL replay các thay đổi lúc khởi động và chữa lành bất kỳ page torn nào thành trạng thái hoàn toàn nhất quán. Đây là lý do một physical backup chỉ có giá trị khi đi cùng WAL sinh ra trong lúc (và, với PITR, sau khi) backup — bạn không thể coi bản copy file thô một mình là đáng tin cậy nếu thiếu WAL đó.

Vì sao physical backup restore nhanh hơn nhiều

Restore một physical backup về cơ bản đơn giản: copy các file lên đĩa đúng vị trí, chỉnh cấu hình (postgresql.conf, recovery.signal nếu replay WAL), và khởi động PostgreSQL. Không cần chạy lại hàng triệu câu INSERT hay rebuild index từ đầu — các page B-tree, page heap trên đĩa, và mọi thứ khác đã ở dạng vật lý cuối cùng, y hệt như chúng tồn tại trên server nguồn. Với một database lớn, đây là khác biệt giữa restore đo bằng phút (bị giới hạn bởi throughput I/O copy file) so với restore đo bằng nhiều giờ (bị giới hạn bởi SQL replay thâm dụng CPU và xây dựng index). Lợi thế tốc độ này là lý do chính khiến physical backup — chứ không phải logical — là xương sống của chiến lược recovery cho bất kỳ database nào đủ lớn để thời gian restore thực sự quan trọng.

Khái niệm chính

Point-in-Time Recovery (PITR)

Một base backup duy nhất chỉ đưa bạn về đúng thời điểm nó được lấy. Point-in-Time Recovery kết hợp một base backup với một luồng WAL segment được archive liên tục, không bị gián đoạn, để cho phép bạn replay tiến từ base backup đó tới bất kỳ thời điểm nào sau đó — chính xác tới một timestamp cụ thể, một transaction ID cụ thể, hoặc một LSN (log sequence number) cụ thể. Đây là năng lực trả lời cho tin nhắn khủng khiếp nhất mà DBA nhận được: “ai đó vừa chạy DELETE FROM orders WHERE 1=1 năm phút trước — mình có thể lấy lại mọi thứ từ ngay trước lúc đó không?” Với PITR được cấu hình, câu trả lời là có: restore base backup gần nhất, replay WAL tới ngay trước (không bao gồm) transaction gây họa đó, và bạn có một database chính xác như nó tồn tại một transaction trước đó.

Cơ chế này có hai phần đều phải hiện diện và không bị gián đoạn:

  1. Một base backup (từ pg_basebackup, hoặc từ một tool backup xây trên cùng cơ chế nền) lấy tại một thời điểm nào đó.
  2. WAL archiving liên tục từ thời điểm đó trở về sau — mỗi WAL segment PostgreSQL sinh ra được copy vào storage bền vững, tách biệt, ngay khi nó được sinh ra, không có khoảng trống nào.

Cấu hình archiving liên tục qua archive_modearchive_command trong postgresql.conf:

# postgresql.conf
wal_level = replica          # hoặc logical; phải tối thiểu 'replica' để archive được
archive_mode = on
archive_command = 'cp %p /var/lib/postgresql/wal_archive/%f'
# một triển khai thật sẽ dùng lệnh do tool quản lý thay vì cp trần,
# ví dụ archive_command wrapper riêng của pgBackRest hoặc Barman

Để thực hiện restore PITR: copy các file base backup vào data directory, tạo file recovery.signal (PostgreSQL 12+; version cũ hơn dùng recovery.conf), và cấu hình recovery target trong postgresql.conf:

# postgresql.conf, sau khi đã restore các file base backup
restore_command = 'cp /var/lib/postgresql/wal_archive/%f %p'
recovery_target_time = '2026-07-19 14:32:00+07'
recovery_target_action = 'promote'   # hoặc 'pause' để kiểm tra trước khi tiếp tục
touch /var/lib/postgresql/17/main/recovery.signal
systemctl start postgresql
# PostgreSQL replay WAL đã archive từ base backup tiến lên cho đến khi
# đạt recovery_target_time, rồi dừng lại và promote thành server bình thường

Bạn có thể target recovery theo timestamp (recovery_target_time), theo một transaction ID cụ thể (recovery_target_xid), theo một restore point được đặt tên tạo sẵn bằng pg_create_restore_point(), hoặc theo một LSN cụ thể (recovery_target_lsn). Sự linh hoạt này — replay tới bất kỳ điểm nào, không chỉ đúng thời điểm backup — là lý do PITR được coi là năng lực recovery chuẩn vàng, và vì sao mọi triển khai PostgreSQL production quan tâm đến dữ liệu của mình nên cấu hình nó, chứ không chỉ dựa vào backup toàn phần định kỳ. Không có WAL archiving, độ chi tiết recovery của bạn bị giới hạn ở “bất kể full backup nào đang tồn tại”, với một database backup mỗi đêm điều đó có thể nghĩa là mất tới 24 giờ giao dịch; với PITR, độ chi tiết recovery chỉ bị giới hạn bởi việc WAL archive được giữ lại xa tới đâu.

Barman

Barman (“Backup and Recovery Manager”) là một tool trưởng thành, viết bằng Python, từ 2i2c/EnterpriseDB, chạy trên một server backup riêng và quản lý toàn bộ vòng đời backup cho một hoặc nhiều server PostgreSQL: lên lịch và lấy base backup (qua streaming tương đương pg_basebackup hoặc rsync), liên tục nhận/archive WAL (qua streaming pg_receivewal hoặc push archive_command), thực thi retention policy, và điều phối restore PITR.

# barman.conf server stanza (trên host Barman)
# [pg-primary]
# conninfo = host=primary-db user=barman dbname=postgres
# streaming_conninfo = host=primary-db user=streaming_barman
# backup_method = postgres
# archiver = on
# streaming_archiver = on
# path_prefix = /usr/lib/postgresql/17/bin

# Lấy một full base backup
barman backup pg-primary

# Liệt kê các backup có sẵn cho một server
barman list-backup pg-primary

# Xác minh WAL archiving không bị gián đoạn và kiểm tra sức khỏe tổng thể của server
barman check pg-primary

# Restore một backup cụ thể (tùy chọn tới một PITR target) lên một host đích
barman recover pg-primary 20260719T060000 /var/lib/postgresql/17/main \
  --target-time "2026-07-19 14:32:00+07" --remote-ssh-command "ssh postgres@restore-host"

Barman tập trung lưu trữ backup trên một host tách biệt với server database, đây là một lợi thế resilience thật sự — backup sống sót ngay cả khi toàn bộ đĩa của primary bị mất. Nó đã được dùng trong production hơn một thập kỷ và có mô hình retention policy đơn giản, tài liệu tốt (ví dụ retention_policy = RECOVERY WINDOW OF 30 DAYS).

pgBackRest

pgBackRest là tool mà hầu hết các triển khai PostgreSQL production mới hiện nay lựa chọn, và mức độ áp dụng của nó đã dần vượt qua Barman vì vài lý do cụ thể: parallel backup và restore thực sự trên nhiều process/thread (không chỉ push WAL song song), nén tích hợp sẵn (nhiều thuật toán, bao gồm zstd) và mã hóa tích hợp sẵn khi lưu trữ, hỗ trợ native cho incremental và differential backup chỉ capture block/file đã thay đổi kể từ backup trước (giảm mạnh cả thời gian backup lẫn dung lượng lưu trữ trên database lớn), và hỗ trợ hạng nhất cho việc push backup trực tiếp lên S3, Azure Blob Storage, và GCS mà không cần bước sync riêng.

# /etc/pgbackrest/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
process-max=4
compress-type=zst

[main]
pg1-path=/var/lib/postgresql/17/main
pg1-port=5432
# Khởi tạo repository một lần
pgbackrest --stanza=main stanza-create

# Full backup
pgbackrest --stanza=main --type=full backup

# Incremental backup tiếp theo (chỉ block đã thay đổi kể từ backup trước)
pgbackrest --stanza=main --type=incr backup

# Restore, tùy chọn tới một điểm-trong-thời-gian cụ thể
pgbackrest --stanza=main --type=time \
  --target="2026-07-19 14:32:00+07" restore

process-max điều khiển số worker song song mà pgBackRest dùng cho backup/restore/archive-push, đây là nơi phần lớn lợi thế tốc độ của nó so với Barman đến từ trên các host nhiều core với storage nhanh. Incremental backup đặc biệt thay đổi bài toán vận hành cho database lớn: thay vì một full backup vài trăm gigabyte mỗi đêm, bạn có thể chạy một full backup hàng tuần và các incremental nhỏ, nhanh hàng ngày, cắt giảm đáng kể cả backup window lẫn chi phí lưu trữ.

Barman so với pgBackRest

BarmanpgBackRest
Ngôn ngữ / xuất xứPython, được EnterpriseDB hậu thuẫnC, được Crunchy Data hậu thuẫn
Backup/restore song songHạn chế (push WAL song song qua pg_receivewal)Song song đa process thực sự cho backup, restore, và archiving
NénTiêu chuẩn (gzip)Nhiều thuật toán bao gồm zstd, nhìn chung nhanh hơn/tỷ lệ nén tốt hơn
Mã hóaKhông tích hợp sẵn (dựa vào mã hóa filesystem/transport)Mã hóa tích hợp sẵn khi lưu trữ
Incremental/differential backupHỗ trợ differential; incremental mức block hạn chế hơnNative incremental và differential mức block, mạnh trên database lớn
Cloud object storageQua plugin sync/cloud bên ngoàiHỗ trợ native S3/Azure Blob/GCS tích hợp sẵn
Độ trưởng thành / lịch sửRất trưởng thành, lịch sử production lâu dàiTrưởng thành và được áp dụng nhanh chóng; nay thường là lựa chọn mặc định
Phù hợp điển hình hiện nayCác shop đã dùng Barman, đội muốn mô hình đơn giản hơnTriển khai mới, database lớn, setup cloud-native/object-storage-first

Cả hai tool đều là lựa chọn chính đáng, sẵn sàng cho production, và cả hai đều hỗ trợ đầy đủ PITR trên cùng cơ chế PostgreSQL nền tảng đã mô tả ở trên; khác biệt chủ yếu nằm ở hiệu năng khi mở rộng quy mô, độ rộng tính năng (mã hóa, native cloud storage, incremental), và hệ sinh thái nào đội của bạn đã đầu tư vào. Với các triển khai mới, đặc biệt là bất cứ thứ gì host trên cloud hoặc đủ lớn để thời gian backup/restore là một ràng buộc thật sự, pgBackRest là khuyến nghị phổ biến hơn hiện nay.

WAL-G

WAL-G là một tool backup nhẹ hơn, viết bằng Go, được xây dựng riêng cho các triển khai cloud-native. Nó tập trung vào phạm vi hẹp hơn Barman hay pgBackRest — base backup vật lý hiệu quả và WAL archiving với tích hợp trực tiếp, hạng nhất vào S3, Google Cloud Storage, và Azure Blob Storage làm đích archive, không cần staging cục bộ trung gian.

# Cấu hình dựa trên biến môi trường (ví dụ S3)
export WALG_S3_PREFIX=s3://my-backups-bucket/pg
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...

# Push một base backup trực tiếp lên S3
wal-g backup-push /var/lib/postgresql/17/main

# archive_command trong postgresql.conf push mỗi WAL segment ngay khi được sinh ra
# archive_command = 'wal-g wal-push %p'

# Restore backup mới nhất
wal-g backup-fetch /var/lib/postgresql/17/main LATEST
wal-g wal-fetch %f %p   # dùng làm restore_command trong lúc PITR

WAL-G phổ biến trong các setup PostgreSQL dựa trên Kubernetes và cloud-native khác (nó là engine backup đứng sau nhiều PostgreSQL operator) chính vì nó coi object storage là đích chính, native thay vì một add-on, và vì phạm vi nhỏ hơn giữ overhead vận hành thấp. Nó hỗ trợ PITR qua cùng cơ chế base-backup-cộng-WAL-replay, nhưng cung cấp ít hơn về mặt quản lý retention-policy tập trung và monitoring so với Barman hoặc pgBackRest.

pg_probackup

pg_probackup là một tool quản lý backup và recovery chuyên dụng khác, ban đầu được phát triển bởi Postgres Professional, nhắm vào cùng bài toán cốt lõi như pgBackRest: backup vật lý full/incremental/differential hiệu quả với nén tích hợp sẵn, chạy song song, và hỗ trợ PITR, cộng với các lệnh validate backup và quản lý catalog. Nó có lượng người dùng đặc biệt mạnh ở các hệ sinh thái nơi PostgreSQL fork và tooling của Postgres Professional phổ biến (đáng chú ý là một số triển khai ở Nga và khu vực CIS), và là một lựa chọn hợp lý để đánh giá cùng pgBackRest, dù có cộng đồng và hệ sinh thái quốc tế nhỏ hơn pgBackRest hiện nay. Về mặt chức năng, hai tool này giải quyết những vấn đề trùng lặp; lựa chọn giữa chúng thường nằm ở mức độ phù hợp hệ sinh thái và sự quen thuộc sẵn có hơn là một khoảng cách kỹ thuật quyết định.

Best Practices

Đừng bao giờ tin một backup mà bạn chưa từng restore

Best practice quan trọng nhất trong toàn bộ lĩnh vực này là xác minh backup định kỳ, tự động: định kỳ — hàng tuần là một tần suất hợp lý cho hầu hết hệ thống production — dựng một instance thử nghiệm (một VM, container, hoặc cloud instance dùng-một-lần), restore backup mới nhất (và, lý tưởng nhất, thực hiện một lần restore PITR tới một điểm gần đây, không chỉ “restore base backup”) lên đó, chạy kiểm tra tính toàn vẹn (pgbackrest ... --stanza=main check, barman check, một SELECT count(*) đơn giản trên các table đã biết, hoặc smoke test ở mức application), và cảnh báo thật to nếu có gì thất bại. Cả Barman (barman check) và pgBackRest (xác minh WAL segment tích hợp sẵn, cộng pgbackrest verify ở các bản mới hơn) đều cung cấp tooling hỗ trợ, nhưng bài test duy nhất thực sự chứng minh khả năng khôi phục là một lần restore thật kèm theo một query thật. Một job backup “thành công” mỗi đêm suốt một năm, được phát hiện đã âm thầm hỏng từ tháng thứ hai, là một kết quả tệ hơn cả việc không có backup — nó tạo ra sự tự tin giả tạo làm trì hoãn việc xây dựng một lưới an toàn thật sự.

Giữ WAL archiving không bị gián đoạn và được giám sát

PITR chỉ tốt bằng mắt xích yếu nhất của nó, và cách phổ biến nhất khiến PITR âm thầm hỏng là một khoảng trống trong WAL archive — đĩa đầy ở đích archive, một lỗi mạng thoáng qua tới S3 không được retry, một archive_command cấu hình sai fail âm thầm. Giám sát pg_stat_archiver để phát hiện các lần archive thất bại, cảnh báo khi archiving bị trễ, và đảm bảo retention policy của bạn không bao giờ xóa một WAL segment mà một base backup vẫn còn hiệu lực phụ thuộc vào (tất cả các tool backup ở trên quản lý phụ thuộc này giúp bạn nếu được cấu hình đúng — một setup archive_command + cp tự viết tay thì không).

Lưu backup ở nơi khác primary, và lý tưởng là off-site/khác cloud account

Một backup sống trên cùng đĩa, cùng host, hoặc thậm chí cùng cloud account/region với database primary chỉ phòng vệ được một tập hợp hẹp hơn nhiều các loại sự cố so với một backup lưu trữ thực sự ở nơi khác. Mô hình host backup riêng của Barman, hoặc pgBackRest/WAL-G push lên một bucket object storage tách biệt (lý tưởng ở một account khác hoặc với credential truy cập độc lập, để sống sót qua một primary bị compromise), bảo vệ chống lại hỏng phần cứng, xóa nhầm, và cả một primary host bị compromise hoặc bị ransomware.

Khớp retention policy với yêu cầu recovery thực tế, không phải thói quen

Quyết định retention (bao nhiêu full backup, PITR nên với xa tới đâu) dựa trên yêu cầu kinh doanh thực tế về RPO (bao nhiêu mất mát dữ liệu là chấp nhận được) và nhu cầu compliance/audit, không phải một mặc định tùy tiện “giữ mọi thứ mãi mãi” hay “giữ một tuần”. WAL retention dài hơn tốn storage; retention quá ngắn nghĩa là một vấn đề chậm được phát hiện (một corruption xuất hiện từ nhiều tuần trước, một audit compliance cần với xa hơn dự kiến) trở nên không thể khôi phục.

Lấy một full backup — và test nó — trước bất kỳ nâng cấp major version nào

pg_upgrade thực hiện nâng cấp major version tại chỗ (ví dụ, PostgreSQL 16 lên 17) bằng cách link hoặc copy các file dữ liệu bên dưới và chuyển đổi system catalog sang format của version mới, thay vì dump và reload từng dòng qua SQL. Ở chế độ --link nó dùng hard link giữa data directory cũ và mới, khiến bản thân việc nâng cấp cực nhanh — tỷ lệ với khối lượng công việc chuyển đổi catalog, không phải với khối lượng dữ liệu — đây là lý do pg_upgrade là thực hành chuẩn cho database lớn nơi một pg_dumpall/reload đầy đủ sẽ mất thời gian không chấp nhận được. Đánh đổi là chế độ --link ràng buộc các file dữ liệu của cluster cũ và mới với nhau cho đến khi bạn hoàn tất và dọn dẹp cluster cũ một cách tường minh, nên một lần nâng cấp thất bại hoặc bị gián đoạn có thể khiến cả hai cluster không dùng được nếu bạn không cẩn thận (chế độ --clone hoặc --copy tránh điều này bằng cách không chia sẻ file, với cái giá là cần gấp đôi dung lượng đĩa và nhiều thời gian hơn).

# Luôn chạy --check trước: xác thực tính tương thích mà không đụng vào dữ liệu
/usr/lib/postgresql/17/bin/pg_upgrade \
  --old-datadir=/var/lib/postgresql/16/main \
  --new-datadir=/var/lib/postgresql/17/main \
  --old-bindir=/usr/lib/postgresql/16/bin \
  --new-bindir=/usr/lib/postgresql/17/bin \
  --check

# Sau đó chạy nâng cấp thật (thêm --link cho chế độ hard-link trên database lớn)
/usr/lib/postgresql/17/bin/pg_upgrade \
  --old-datadir=/var/lib/postgresql/16/main \
  --new-datadir=/var/lib/postgresql/17/main \
  --old-bindir=/usr/lib/postgresql/16/bin \
  --new-bindir=/usr/lib/postgresql/17/bin \
  --link

pg_upgrade thao tác trực tiếp trên các file dữ liệu và catalog, một bug, một extension không tương thích bị bỏ sót, hoặc một lần chạy bị gián đoạn có thể làm hỏng hoặc khiến cluster mắc kẹt theo cách khó suy luận hơn nhiều so với một lần thất bại dump/restore logical sạch sẽ. Biện pháp phòng ngừa chuẩn, không có ngoại lệ, là: lấy một full backup đầy đủ, đã xác minh, dạng vật lý hoặc logical ngay trước khi chạy pg_upgrade, và tập dượt đúng quy trình nâng cấp trên một bản copy đã restore của production (không phải một database test đồ chơi) trước khi đụng vào cluster thật. Coi pg_upgrade --check là cần thiết nhưng chưa đủ — nó bắt được các không tương thích ở mức catalog, không phải mọi bất ngờ vận hành có thể xảy ra.

Bảng quyết định chiến lược backup

Loại backupRPO (khoảng mất dữ liệu)RTO (tốc độ restore)ToolUse case điển hình
Logical (pg_dump/pg_dumpall)Kể từ lần dump gần nhất (thường vài giờ)Chậm trên database lớn (SQL replay + rebuild index)pg_dump, pg_dumpall, pg_restoreDatabase nhỏ/vừa, migration version, restore một table/schema, archive portable
Chỉ physical base backupKể từ base backup gần nhất (thường 24h)Nhanh (copy file + khởi động)pg_basebackup, Barman, pgBackRestDatabase lớn nơi tốc độ restore full backup quan trọng, không cần recovery point dưới ngày
Physical + PITR (base backup + WAL archiving liên tục)Vài giây đến vài phút (bất kỳ điểm nào trong phạm vi WAL retention)Restore base nhanh + replay WAL tới targetBarman, pgBackRest, WAL-G, pg_probackupHệ thống production ở mọi quy mô cần khả năng “restore về ngay trước sự cố” — chuẩn vàng
Physical incremental/differentialVài phút (incremental thường xuyên khả thi)Nhanh; backup nhỏ hơn để chuyển/restorepgBackRest, pg_probackup, WAL-GDatabase rất lớn nơi full backup hàng đêm quá chậm/tốn kém

Tài liệu tham khảo

Part of the PostgreSQL DBA Roadmap knowledge base.

Overview

Backup and recovery is arguably the single most consequential responsibility a PostgreSQL DBA carries. Indexing mistakes cost query latency; a botched vacuum strategy costs disk space and bloat; a bad backup strategy costs the business its data, permanently, at the exact moment it can least afford it. Every other topic in this knowledge base is about making a healthy database faster, safer, or more available — this one is about what happens when all of that fails anyway: a dropped table, a bad DELETE without a WHERE clause, a failed storage volume, a ransomware attack, a botched migration. If you cannot restore from that moment, no amount of replication or tuning elsewhere matters.

The central lesson experienced DBAs repeat is that an untested backup is not a backup — it’s an unverified file that might restore. Backups fail silently far more often than they fail loudly: a pg_dump that completes with exit code 0 but was taken against a connection that got interrupted mid-stream, a WAL archive with a silent gap, a base backup taken from a filesystem snapshot that wasn’t actually crash-consistent. The only way to know a backup strategy works is to routinely, mechanically restore from it and verify the result — not to assume that a green checkmark in a cron job log means recoverability.

This note covers the two fundamentally different backup mechanisms PostgreSQL offers — logical (pg_dump/pg_dumpall/pg_restore) and physical (pg_basebackup) — how physical backups combine with continuous WAL archiving to deliver Point-in-Time Recovery (PITR), the mature ecosystem of tools that manage this for you in production (Barman, pgBackRest, WAL-G, pg_probackup), how to actually validate that backups restore, and how pg_upgrade fits into this picture for major version upgrades. It closes with a decision table mapping backup type to RPO/RTO characteristics and tooling. Bulk data loading is covered in ./06-modifying-and-bulk-loading-data.md, the storage layer that WAL and vacuum operate on in ./11-storage-internals-and-vacuum.md, and how backups relate to (and differ from) replication/HA failover in ./13-replication-and-high-availability.md. For the organizational side of what happens when recovery becomes a live incident — communication, decision authority, keeping the business running during the restore — see ../engineering-manager/en/12-crisis-leadership-and-business-continuity.md.

Fundamentals

Logical backups: pg_dump

pg_dump produces a logical backup of a single database: a reconstructable sequence of SQL statements (or an equivalent binary encoding of them) that, when replayed, recreates the schema and data. Because it works at the SQL level rather than the disk-block level, a pg_dump output is portable — it can be restored into a different PostgreSQL major version, a different OS, even a different CPU architecture, which physical backups categorically cannot do.

pg_dump supports several output formats, and the choice matters operationally:

# Plain SQL format — human-readable, restorable with psql, easiest to inspect/edit
pg_dump -h localhost -U postgres -d shop_db -F p -f shop_db.sql

# Custom format (-Fc) — compressed, and the only format compatible with pg_restore's
# selective/parallel restore features; the recommended default for most use cases
pg_dump -h localhost -U postgres -d shop_db -F c -f shop_db.dump

# Directory format (-Fd) — one file per table plus a TOC, enables parallel DUMP
# via -j in addition to parallel restore
pg_dump -h localhost -U postgres -d shop_db -F d -j 4 -f shop_db_dir/

Plain SQL format is the simplest to reason about and diff, but it dumps and restores single-threaded and offers no selective-restore capability beyond editing the SQL text by hand. Custom format (-Fc) is compressed by default, does not immediately execute anything (it’s an archive pg_restore interprets), and is the format most DBAs reach for as the default: it supports selective restore (specific tables, specific schemas), reordering, and parallel restore. Directory format (-Fd) goes one step further and also parallelizes the dump itself (-j N), which matters when the database is large enough that a single-threaded dump takes hours — each table is dumped to its own file concurrently.

A pg_dump of a single large table or database can be slow, and there’s a subtlety worth understanding: pg_dump takes a single consistent snapshot of the database at the start of the dump (it runs inside one transaction using REPEATABLE READ isolation internally), so the dump is transactionally consistent even though it may take hours to complete — later writes to the database during the dump simply aren’t included, and the dump doesn’t see a half-committed intermediate state. What it does not give you is a specific recoverable point in time after the fact; you get exactly one snapshot, the one taken when the dump began, and nothing more granular.

Logical backups: pg_dumpall

pg_dump operates on a single database and, deliberately, does not capture cluster-wide (instance-wide) objects — roles, tablespaces, and other objects that live outside any one database. pg_dumpall fills that gap:

# Dump ALL databases in the cluster plus roles/tablespaces, in plain SQL
pg_dumpall -h localhost -U postgres -f full_cluster.sql

# Common pattern: dump only the globals (roles + tablespaces), then pg_dump
# each database separately in custom format for selective/parallel restore
pg_dumpall -h localhost -U postgres --globals-only -f globals.sql
pg_dump -h localhost -U postgres -d shop_db -F c -f shop_db.dump
pg_dump -h localhost -U postgres -d analytics_db -F c -f analytics_db.dump

pg_dumpall only produces plain SQL output — it has no custom/directory format and therefore no parallel dump or selective restore. In practice, most production setups use the hybrid pattern above: pg_dumpall --globals-only to capture roles and tablespace definitions once, plus a separate pg_dump -Fc per database so each database can be restored independently and in parallel.

Restoring with pg_restore

pg_restore is the counterpart to custom (-Fc) and directory (-Fd) format dumps — plain SQL dumps are restored with psql instead, since they’re just SQL scripts.

# Restore a custom-format dump into a fresh, already-created database
createdb -h localhost -U postgres shop_db_restored
pg_restore -h localhost -U postgres -d shop_db_restored -j 4 shop_db.dump

# Restore only a subset of objects — e.g. one table — from the same dump
pg_restore -h localhost -U postgres -d shop_db_restored \
  -t orders --data-only shop_db.dump

# List the table of contents of a dump without restoring anything
pg_restore -l shop_db.dump

The -j (parallel jobs) flag is the main reason custom/directory format is preferred over plain SQL for anything beyond a small database: pg_restore -j 4 restores multiple tables’ data and rebuilds multiple indexes concurrently, which can turn a restore that would take hours single-threaded into one that takes a fraction of that. Selective restore — pulling one table, one schema, or data-only/schema-only — is also only possible against custom or directory format archives, using the table of contents (pg_restore -l) to pick exactly what you need.

When logical backups are (and aren’t) the right tool

Logical backups are the right choice when: the database is small-to-medium sized and a full dump/restore cycle fits comfortably in your maintenance window; you’re migrating between PostgreSQL major versions (a logical dump/restore is version-independent, unlike a physical copy which is tied to the on-disk format of one major version); you need to restore or copy just one table or schema rather than the whole cluster; or you want a portable, inspectable artifact you can diff or version-control (for schema-only dumps).

Their limitation is restore speed at scale. Restoring a pg_dump means re-executing INSERTs (or COPYs) row by row and then rebuilding every index from scratch — for a multi-hundred-gigabyte database this can take many hours even with -j parallelism, because index builds and constraint validation are CPU- and I/O-bound work that scales with data volume regardless of format. This is the core reason production systems with large databases lean on physical backups for their primary recovery path, and reserve logical backups for portability, migrations, and small/selective use cases.

Physical backups: pg_basebackup

A physical backup is a byte-for-byte copy of the PostgreSQL data directory — the actual files PostgreSQL uses to store heap pages, indexes, and WAL — rather than a logical reconstruction of the data through SQL. pg_basebackup takes such a copy over the replication protocol while the server keeps running and accepting writes, and it handles the tricky part for you: ensuring the copy is consistent even though files are being copied while concurrently being modified.

# Take a physical base backup, in compressed tar format, with a matching
# WAL segment stream so the backup is immediately usable on its own
pg_basebackup -h localhost -U replicator \
  -D /var/backups/pgbase/$(date +%Y%m%d) \
  -Ft -z -Xstream -P

# Plain directory format instead of tar, useful when you want to inspect
# or directly start PostgreSQL against the copied files
pg_basebackup -h localhost -U replicator \
  -D /var/backups/pgbase_plain \
  -Fp -Xstream -P

Key flags: -Ft produces a .tar.gz-style archive per tablespace (compact for transfer/storage); -Fp produces a plain directory tree that mirrors a real data directory (immediately startable). -Xstream streams the WAL generated during the backup alongside the base backup itself, which is what makes the backup usable in isolation without depending on a separately configured WAL archive being reachable at restore time. -P shows progress. The connecting role needs the REPLICATION privilege (or membership in pg_read_all_settings/appropriate roles depending on version), and pg_hba.conf must permit a replication connection from the backup host.

Internally, pg_basebackup works by calling pg_start_backup()-equivalent logic (via the replication protocol, not the deprecated SQL functions) to record a starting WAL location, streaming the actual data files, and then calling the stop-backup equivalent to record an ending WAL location — because files are copied while writes continue, some pages may be copied in an inconsistent, torn state, and it’s the WAL segments generated between start and stop that let PostgreSQL replay changes at startup and heal any such torn pages into a fully consistent state. This is why a physical backup is only valid together with the WAL generated during (and, for PITR, after) the backup — you cannot treat the raw file copy alone as trustworthy without that WAL.

Why physical backups restore so much faster

Restoring a physical backup is mechanically simple: copy the files onto disk in the right location, adjust configuration (postgresql.conf, recovery.signal if replaying WAL), and start PostgreSQL. There is no need to re-run millions of INSERT statements or rebuild indexes from scratch — the on-disk B-tree pages, heap pages, and everything else are already in their final physical form, exactly as they existed on the source server. For a large database, this is the difference between a restore measured in minutes (dominated by file copy I/O throughput) versus one measured in many hours (dominated by CPU-bound SQL replay and index construction). This speed advantage is the primary reason physical backups — not logical ones — are the backbone of recovery strategy for any database large enough that restore time matters.

Key Concepts

Point-in-Time Recovery (PITR)

A single base backup only gets you back to the moment it was taken. Point-in-Time Recovery combines a base backup with a continuous, unbroken stream of archived WAL segments to let you replay forward from that base backup to any point in time after it — down to a specific timestamp, a specific transaction ID, or a specific LSN (log sequence number). This is the capability that answers the DBA’s most dreaded page: “someone ran DELETE FROM orders WHERE 1=1 five minutes ago — can we get everything back from right before that?” With PITR configured, the answer is yes: restore the most recent base backup, replay WAL up to (but not including) the offending transaction, and you have a database exactly as it stood one transaction earlier.

The mechanism has two moving parts that must both be present and unbroken:

  1. A base backup (from pg_basebackup, or from a backup tool built on the same underlying mechanism) taken at some point in time.
  2. Continuous WAL archiving from that point forward — every WAL segment PostgreSQL generates is copied to durable, separate storage as it’s produced, with no gaps.

Configure continuous archiving via archive_mode and archive_command in postgresql.conf:

# postgresql.conf
wal_level = replica          # or logical; must be at least 'replica' to archive
archive_mode = on
archive_command = 'cp %p /var/lib/postgresql/wal_archive/%f'
# a real deployment would use a tool-managed command instead of a bare cp,
# e.g. pgBackRest's or Barman's own archive_command wrapper

To perform a PITR restore: copy the base backup into the data directory, create a recovery.signal file (PostgreSQL 12+; older versions used recovery.conf), and configure the recovery target in postgresql.conf:

# postgresql.conf, after restoring the base backup files
restore_command = 'cp /var/lib/postgresql/wal_archive/%f %p'
recovery_target_time = '2026-07-19 14:32:00+07'
recovery_target_action = 'promote'   # or 'pause' to inspect before continuing
touch /var/lib/postgresql/17/main/recovery.signal
systemctl start postgresql
# PostgreSQL replays archived WAL from the base backup forward until it
# reaches recovery_target_time, then stops and promotes to a normal server

You can target recovery by timestamp (recovery_target_time), by a specific transaction ID (recovery_target_xid), by a named restore point created in advance with pg_create_restore_point(), or by a specific LSN (recovery_target_lsn). This flexibility — replay to any point, not just the moment of a backup — is why PITR is considered the gold-standard recovery capability, and why every production PostgreSQL deployment that cares about its data should have it configured, not merely periodic full backups. Without WAL archiving, your recovery granularity is limited to “whichever full backup happens to exist,” which for a database backed up nightly could mean losing up to 24 hours of transactions; with PITR, your recovery granularity is limited only by how far back your retained WAL archive reaches.

Barman

Barman (“Backup and Recovery Manager”) is a mature, Python-based tool from 2i2c/EnterpriseDB that runs on a dedicated backup server and manages the entire backup lifecycle for one or more PostgreSQL servers: scheduling and taking base backups (via pg_basebackup-equivalent streaming or rsync), continuously receiving/archiving WAL (via pg_receivewal streaming or an archive_command push), enforcing retention policies, and orchestrating PITR restores.

# barman.conf server stanza (on the Barman host)
# [pg-primary]
# conninfo = host=primary-db user=barman dbname=postgres
# streaming_conninfo = host=primary-db user=streaming_barman
# backup_method = postgres
# archiver = on
# streaming_archiver = on
# path_prefix = /usr/lib/postgresql/17/bin

# Take a full base backup
barman backup pg-primary

# List available backups for a server
barman list-backup pg-primary

# Verify WAL archiving is uninterrupted and check overall server health
barman check pg-primary

# Restore a specific backup (optionally to a PITR target) onto a target host
barman recover pg-primary 20260719T060000 /var/lib/postgresql/17/main \
  --target-time "2026-07-19 14:32:00+07" --remote-ssh-command "ssh postgres@restore-host"

Barman centralizes backup storage on a separate host from the database server itself, which is a real resilience advantage — the backup survives even if the primary’s own disks are lost entirely. It’s been in production use for well over a decade and has a straightforward, well-documented retention policy model (retention_policy = RECOVERY WINDOW OF 30 DAYS, for example).

pgBackRest

pgBackRest is the tool most new production PostgreSQL deployments reach for today, and its adoption has been steadily overtaking Barman’s for a few concrete reasons: true parallel backup and restore across multiple processes/threads (not just parallel WAL push), built-in compression (multiple algorithms, including zstd) and built-in encryption at rest, native incremental and differential backups that only capture changed blocks/files since a prior backup (dramatically shrinking both backup time and storage footprint on large databases), and first-class support for pushing backups directly to S3, Azure Blob Storage, and GCS without a separate sync step.

# /etc/pgbackrest/pgbackrest.conf
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=2
process-max=4
compress-type=zst

[main]
pg1-path=/var/lib/postgresql/17/main
pg1-port=5432
# One-time repository initialization
pgbackrest --stanza=main stanza-create

# Full backup
pgbackrest --stanza=main --type=full backup

# Subsequent incremental backup (only changed blocks since the last backup)
pgbackrest --stanza=main --type=incr backup

# Restore, optionally to a specific point in time
pgbackrest --stanza=main --type=time \
  --target="2026-07-19 14:32:00+07" restore

process-max controls how many parallel workers pgBackRest uses for backup/restore/archive-push, which is where most of its speed advantage over Barman comes from on multi-core hosts with fast storage. Incremental backups in particular change the operational math for large databases: instead of a full multi-hundred-gigabyte backup every night, you can run one full backup weekly and small, fast incrementals daily, cutting both backup window and storage cost substantially.

Barman vs. pgBackRest

BarmanpgBackRest
Language / originPython, EnterpriseDB-backedC, Crunchy Data-backed
Parallel backup/restoreLimited (parallel WAL push via pg_receivewal)True multi-process parallelism for backup, restore, and archiving
CompressionStandard (gzip)Multiple algorithms including zstd, generally faster/better ratios
EncryptionNot built-in (rely on filesystem/transport encryption)Built-in at-rest encryption
Incremental/differential backupsDifferential supported; block-level incremental more limitedNative block-level incremental and differential, strong on large DBs
Cloud object storageVia external sync/cloud pluginsNative S3/Azure Blob/GCS support built in
Maturity / track recordVery mature, long production historyMature and rapidly gained adoption; now a common default choice
Typical fit todayExisting Barman shops, teams wanting a simpler mental modelNew deployments, large databases, cloud-native/object-storage-first setups

Both tools are legitimate, production-grade choices and both fully support PITR against the same underlying PostgreSQL mechanism described above; the difference is mostly about performance at scale, feature breadth (encryption, native cloud storage, incrementals), and which ecosystem your team is already invested in. For new deployments, especially anything cloud-hosted or large enough that backup/restore time is a real constraint, pgBackRest is the more common recommendation today.

WAL-G

WAL-G is a lighter-weight, Go-based backup tool built specifically with cloud-native deployments in mind. It focuses on a narrower scope than Barman or pgBackRest — efficient physical base backups and WAL archiving with direct, first-class integration into S3, Google Cloud Storage, and Azure Blob Storage as the archive target, with no intermediate local staging required.

# Environment-based configuration (S3 example)
export WALG_S3_PREFIX=s3://my-backups-bucket/pg
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...

# Push a base backup directly to S3
wal-g backup-push /var/lib/postgresql/17/main

# archive_command in postgresql.conf pushes each WAL segment as it's produced
# archive_command = 'wal-g wal-push %p'

# Restore the latest backup
wal-g backup-fetch /var/lib/postgresql/17/main LATEST
wal-g wal-fetch %f %p   # used as restore_command during PITR

WAL-G is popular in Kubernetes-based and other cloud-native PostgreSQL setups (it’s the backup engine behind several PostgreSQL operators) precisely because it treats object storage as the primary, native destination rather than an add-on, and because its smaller scope keeps operational overhead low. It supports PITR through the same base-backup-plus-WAL-replay mechanism, but offers less in the way of centralized retention-policy management and monitoring than Barman or pgBackRest.

pg_probackup

pg_probackup is another dedicated backup and recovery manager, originally developed by Postgres Professional, aimed at the same core problem as pgBackRest: efficient full/incremental/differential physical backups with built-in compression, parallel operation, and PITR support, plus backup validation and catalog management commands. It has a particularly strong following in ecosystems where Postgres Professional’s PostgreSQL fork and tooling are common (notably some Russian and CIS-region deployments), and it’s a reasonable alternative to evaluate alongside pgBackRest, though it has a smaller international community and ecosystem than pgBackRest today. Functionally the two tools solve overlapping problems; the choice between them is often more about ecosystem fit and existing familiarity than a decisive technical gap.

Best Practices

Never trust a backup you haven’t restored

The single most important best practice in this entire domain is routine, automated backup validation: periodically — weekly is a reasonable cadence for most production systems — spin up a scratch instance (a disposable VM, container, or ephemeral cloud instance), restore the latest backup (and, ideally, exercise a PITR restore to a recent point, not just “restore the base backup”) onto it, run integrity checks (pgbackrest ... --stanza=main check, barman check, a simple SELECT count(*) against known tables, or application-level smoke tests), and alert loudly if anything fails. Both Barman (barman check) and pgBackRest (built-in WAL segment verification, plus pgbackrest verify in newer versions) provide tooling to help, but the only test that actually proves recoverability is a real restore followed by a real query. A backup job that “succeeds” every night for a year, discovered to have been silently broken since month two, is a worse outcome than no backup at all — it created false confidence that delayed building a real safety net.

Keep WAL archiving unbroken and monitored

PITR is only as good as its weakest link, and the most common way PITR silently breaks is a gap in the WAL archive — a full disk on the archive destination, a transient network failure to S3 that isn’t retried, a misconfigured archive_command that fails quietly. Monitor pg_stat_archiver for failed archive attempts, alert on archiving lag, and make sure your retention policy never deletes a WAL segment that a still-valid base backup depends on (all the backup tools above manage this dependency for you if configured correctly — a bare hand-rolled archive_command + cp setup does not).

Store backups off the primary, and ideally off-site/off-cloud-account

A backup that lives on the same disk, same host, or even same cloud account/region as the primary database defends against a much narrower set of failures than one stored genuinely elsewhere. Barman’s model of a separate backup host, or pgBackRest/WAL-G pushing to a separate object storage bucket (ideally in a different account or with independent access credentials, to survive a compromised primary), protects against hardware failure, accidental deletion, and a compromised or ransomed primary host alike.

Match retention policy to actual recovery requirements, not habit

Decide retention (how many full backups, how far back PITR should reach) based on real business requirements for RPO (how much data loss is acceptable) and compliance/audit needs, not an arbitrary “keep everything forever” or “keep one week” default. Longer WAL retention costs storage; too-short retention means a slow-to-discover issue (a corruption introduced weeks ago, a compliance audit reaching back further than expected) becomes unrecoverable.

Take a full backup — and test it — before any major version upgrade

pg_upgrade performs an in-place major version upgrade (e.g., PostgreSQL 16 to 17) by linking or copying the underlying data files and converting the system catalogs to the new version’s format, rather than dumping and reloading every row through SQL. In --link mode it uses hard links between the old and new data directories, making the upgrade itself extremely fast — proportional to catalog conversion work, not to data volume — which is why pg_upgrade is standard practice for large databases where a full pg_dumpall/reload would take unacceptably long. The trade-off is that --link mode ties the old and new clusters’ data files together until you explicitly complete and clean up the old cluster, so a failed or interrupted upgrade can leave both clusters unusable if you aren’t careful (--clone or --copy modes avoid this by not sharing files, at the cost of needing double the disk space and more time).

# Always run --check first: validates compatibility without touching data
/usr/lib/postgresql/17/bin/pg_upgrade \
  --old-datadir=/var/lib/postgresql/16/main \
  --new-datadir=/var/lib/postgresql/17/main \
  --old-bindir=/usr/lib/postgresql/16/bin \
  --new-bindir=/usr/lib/postgresql/17/bin \
  --check

# Then run the real upgrade (add --link for hard-link mode on large databases)
/usr/lib/postgresql/17/bin/pg_upgrade \
  --old-datadir=/var/lib/postgresql/16/main \
  --new-datadir=/var/lib/postgresql/17/main \
  --old-bindir=/usr/lib/postgresql/16/bin \
  --new-bindir=/usr/lib/postgresql/17/bin \
  --link

Because pg_upgrade operates directly on data files and catalogs, a bug, a missed extension incompatibility, or an interrupted run can corrupt or strand a cluster in a way that’s much harder to reason about than a clean logical dump/restore failure. The standard precaution, without exception, is: take a full, verified physical or logical backup immediately before running pg_upgrade, and rehearse the exact upgrade procedure against a restored copy of production (not a toy test database) before touching the real cluster. Treat pg_upgrade --check as necessary but not sufficient — it catches catalog-level incompatibilities, not every possible operational surprise.

Backup strategy decision table

Backup typeRPO (data loss window)RTO (restore speed)ToolTypical use case
Logical (pg_dump/pg_dumpall)Since last dump (often hours)Slow on large DBs (SQL replay + index rebuild)pg_dump, pg_dumpall, pg_restoreSmall/medium DBs, version migration, single-table/schema restore, portable archives
Physical base backup onlySince last base backup (often 24h)Fast (file copy + startup)pg_basebackup, Barman, pgBackRestLarge DBs where full-backup restore speed matters, no need for sub-day recovery points
Physical + PITR (base backup + continuous WAL archiving)Seconds to minutes (any point covered by WAL retention)Fast base restore + WAL replay to targetBarman, pgBackRest, WAL-G, pg_probackupProduction systems of any size that need “restore to just before the incident” capability — the gold standard
Incremental/differential physicalMinutes (frequent incrementals feasible)Fast; smaller backups to transfer/restorepgBackRest, pg_probackup, WAL-GVery large DBs where nightly full backups are too slow/costly

References