Skip to main content
RunBook Academy

PostgreSQLXIII · Backup, Archiving and Point-in-Time RecoveryBackup

Logical backups: pg_dump, pg_restore, scope and limits

Intermediate⏱ ~30 min🧪 Lab requiredpg_dumppg_restore

What you'll learn

  • Choose a dump format from what the restore needs to do
  • State precisely what pg_dump does and does not capture
  • Restore selectively, in parallel, and with the right ordering
  • Recognise where a logical backup is the wrong tool

Prerequisites

Practice

Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27

Not yet marked complete on this device.

A logical backup captures the contents of a database as statements that rebuild it, rather than the bytes on disk. That difference gives it capabilities a physical backup does not have, and limits a physical backup does not have either.

Formats

FormatFlagRestore withSelectiveParallelCompressedIntegrity
Plain-FppsqlNoNoExternal onlyNone
Custom-Fcpg_restoreYesYesBuilt inYes
Directory-Fdpg_restoreYesDump and restoreBuilt inYes
Tar-Ftpg_restoreYesNoExternal onlyYes
Read-only / Safethe same database in two formats
$ pg_dump -U postgres -F c -f /tmp/d.dump postgres && pg_dump -U postgres -F p -f /tmp/d.sql postgres && ls -la /tmp/d.dump /tmp/d.sql
-rw-r--r-- 1 postgres postgres  751359 /tmp/d.dump
-rw-r--r-- 1 postgres postgres 2156673 /tmp/d.sql
Read-only / Safewhat a custom-format dump knows about itself
$ pg_restore -l /tmp/d.dump
;
; Archive created at 2026-08-27 21:26:07 UTC
;     dbname: postgres
;     TOC Entries: 7
;     Compression: gzip
;     Dump Version: 1.16-0
;     Format: CUSTOM
;     Dumped from database version: 18.6 (Debian 18.6-1.pgdg13+2)
;     Dumped by pg_dump version: 18.6 (Debian 18.6-1.pgdg13+2)
;

What pg_dump does not capture

From lesson XIII-01, measured: zero role or tablespace definitions. The complete list of what a pg_dump of one database omits:

  • Roles and their passwords. pg_dumpall --globals-only.
  • Tablespace definitions. Same.
  • Other databases. One dump per database.
  • postgresql.conf and pg_hba.conf. Files, not catalogue.
  • WAL, and therefore any point in time. A dump is one instant.
  • Physical layout. Indexes are rebuilt, not copied.
  • pg_stat counters and statistics. The restored database needs ANALYZE.

That last one is easy to overlook and expensive: a freshly restored database has no planner statistics, which lesson VIII-07 measured as worth 1,003 buffers against 4 on one query. pg_restore does not run ANALYZE for you.

# after every logical restore
vacuumdb --analyze-only --jobs=8 --dbname=restored

Restoring

# whole database, parallel, into a fresh database
createdb restored
pg_restore -d restored --jobs=8 /tmp/d.dump

# schema first, inspect, then data
pg_restore -d restored --schema-only /tmp/d.dump
pg_restore -d restored --data-only  --jobs=8 /tmp/d.dump

# one table
pg_restore -d restored --table=orders /tmp/d.dump

# generate SQL without applying it, to read what would happen
pg_restore --file=would_do.sql /tmp/d.dump

Where logical backups are the right tool

Cross-version migration. A dump from 15 restores into 18. Physical backups cannot cross major versions at all.

Cross-platform migration. Different architecture, different operating system, different page layout. A dump is portable; a physical backup is not.

Selective recovery. Restoring one table from a 4 TB cluster without restoring the other 3.99 TB.

Schema extraction. --schema-only produces a readable, diffable definition.

Small databases, where a dump-and-restore is quick enough that the extra capability of PITR is not worth its complexity.

Where they are the wrong tool

Anything large. A dump reads every row through the SQL layer and a restore rebuilds every index. For a 4 TB database this is hours to days; a physical restore is a file copy.

Any RPO shorter than the dump interval. A dump is a single instant. Nightly dumps mean a 24-hour RPO, and no amount of care changes that.

A running system’s consistency across databases. pg_dumpall dumps each database in its own transaction, so two databases are not captured at the same instant.

What to take from this

  • Custom or directory format for anything you intend to restore. Plain format has no integrity check, and corruption is found mid-restore.
  • Measured: 751 kB custom against 2,157 kB plain, and a corrupted custom dump rejected before restore.
  • pg_dump omits roles, tablespaces, configuration files and statistics. Take pg_dumpall --globals-only alongside, and ANALYZE afterwards.
  • pg_restore continues past errors by default. Use --exit-on-error or check deliberately.
  • Directory format supports --jobs on the dump as well as the restore.
  • A dump holds a snapshot for its whole duration, with every consequence from lesson VII-05.

Cross-course references

  • Linux for Production Sysadmins — Part XLVIII (Backup tools) covers the pipe-and-exit-status trap that makes a logical dump appear to have succeeded, and Part XLIX (Restore) covers proving the output is usable.
  • Git, CI/CD & GitOps — Part XXXV (Secrets in Git) covers the globals dump, which contains role passwords and is the file most often committed by accident.

Quiz

Knowledge check · 6 questions

  1. Q1. A nightly pg_dump of a 500 GB database takes six hours. Tables on that cluster have been growing steadily despite healthy autovacuum. What is the connection?

  2. Q2. Why can pg_dump -Fc not be combined with --jobs, while pg_dump -Fd can?

  3. Q3. A restore log ends with 'errors ignored on restore: 412' and the database is in use the next morning. What went wrong procedurally?

  4. Q4. Which are genuine advantages of a logical backup over a physical one? Select all that apply.

  5. Q5. A freshly restored logical backup needs ANALYZE run against it before its query performance is representative.

  6. Q6. When is a logical backup the right tool, and when is it the wrong one?

Passing score: 75%. Answers are checked in this browser.