Backup, PITR and replication
pg_dump and pg_basebackup, WAL archiving for point-in-time recovery, physical streaming replicas, logical replication and verification.
Two kinds of backup
# logical: portable, per database, restores into any version
pg_dump -Fc -f appdb.dump appdb
pg_restore -d appdb_restore --jobs=4 --clean --if-exists appdb.dump
# only the schema, or only the data
pg_dump --schema-only -f schema.sql appdb
pg_dump --data-only --table=invoice -f invoice.sql appdb
# globals: roles and grants live outside any database
pg_dumpall --globals-only -f globals.sql
# physical: a byte copy of the cluster, needed for PITR
pg_basebackup -D /var/lib/postgresql/replica -h primary -U replicator -X stream -P -R| Method | Restores to | Cost |
|---|---|---|
pg_dump | A point in time, one database | Slow for large databases, blocks nothing |
pg_basebackup | The whole cluster | Fast, requires WAL for consistency |
| Continuous archiving | Any moment after the base backup | Storage and restore complexity |
| Managed snapshots | Usually a point in time | Provider-specific, verify the restore yourself |
⚠️
An untested backup is not a backup. Restore into a scratch instance on a schedule, run
pg_dump over the restored copy, compare row counts on a few critical tables, and record how long the restore took - that number is your real recovery time objective.Point-in-time recovery
# postgresql.conf on the primary
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /archive/%f && cp %p /archive/%f'
archive_timeout = 60s
max_wal_senders = 10
wal_keep_size = 1GB# recovery settings for a restore
restore_command = 'cp /archive/%f %p'
recovery_target_time = '2026-09-18 09:30:00+00'
recovery_target_action = 'promote'
recovery_target_inclusive = onarchive_commandreturning non-zero makes PostgreSQL retry the same segment forever, and WAL accumulates on disk until the volume fills. Do not usecpto a local disk in production - usepgbackrestorwal-g.- A PITR restore replays the base backup then all WAL up to the target. Test the whole path, not just the base restore.
- A dropped table is the classic PITR case: restore to just before the drop, then export the table and re-import it into the live database rather than promoting the old copy.
- Archiving does not back up the base. You need both, and the retention policy for each is different.
Replication
-- on the primary: a role for replication
create role replicator with replication login password '...';
-- on the replica: point it at the primary
-- primary_conninfo = 'host=primary user=replicator password=... application_name=replica1'
-- monitor lag
select client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) as replay_lag_bytes
from pg_stat_replication;
-- logical replication: publish selected tables
create publication app_pub for table invoice, customer;
-- on the subscriber
create subscription app_sub connection 'host=primary dbname=appdb'
publication app_pub;| Mode | Granularity | Use |
|---|---|---|
| Physical streaming | Whole cluster, identical bytes | Read replicas, failover |
| Synchronous commit | Zero data loss window | When losing a commit is unacceptable, at latency cost |
| Logical replication | Per table, per operation | Upgrades, selective replication, different versions |
| Cascading replicas | Replica of a replica | Offloading the primary |
A physical replica is read-only and byte-identical, so it is the right target for a failover promotion. Logical replication is more flexible but does not carry DDL, sequences or large objects - it replicates the rows you publish and nothing else.
FAQ
Should I use a hot standby for reporting?
Only for moderate read load. Long analytical queries on a replica can conflict with WAL replay, and PostgreSQL will either delay the query or cancel it depending on
hot_standby_feedback and max_standby_*_delay. Heavy reporting deserves its own logical replica or a warehouse.How do I know if replication is healthy?
Alert on
replay_lag_bytes and on the age of the last replayed transaction, not just on whether the connection is up. A replica that is connected but hours behind is not a failover candidate.Related
Query planning and performance tuning Next steps: the PostgreSQL ecosystem and migrations
Last refreshed 2026-09-18.