Skip to main content
RunBook Academy

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

Physical backups and the low-level API

Advanced⏱ ~30 minpsql

What you'll learn

  • Explain why a running cluster cannot be copied naively
  • Drive the low-level backup API correctly, including backup_label
  • Recognise the session-scoped failure mode of a non-exclusive backup
  • Decide when the low-level API is warranted at all

Prerequisites

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 physical backup is a copy of PGDATA. That sounds trivial and it is not, because the copy takes time and the cluster does not stop changing while it runs.

Why a naive copy is not a backup

Copying a 500 GB data directory takes minutes to hours. During that time PostgreSQL is writing. A file copied at the start reflects one instant; a file copied at the end reflects another. The result is torn: an index pointing at a heap page that did not exist when the index was copied, a page half-written by the copy itself.

There are exactly two ways to get a usable physical backup:

  1. Stop the cluster and copy the directory. Correct, and unavailable on anything you cannot take down.
  2. Tell PostgreSQL you are copying, copy while it runs, and replay WAL over the result until it is consistent again.

The second is what “online backup” means, and every physical backup tool implements it.

What the low-level API actually does

SELECT pg_backup_start('label', fast => true);
-- copy PGDATA by whatever means
SELECT pg_backup_stop();

pg_backup_start() does three things that make the copy legal:

  • Forces a checkpoint, so there is a known redo point from which replay can begin. fast => true makes it an immediate checkpoint; false spreads it over checkpoint_completion_target, which is gentler and slower.
  • Guarantees full page writes are on for the duration, so every page touched during the copy has a complete image in WAL. This is why the torn pages a naive copy produces do not matter: replay overwrites them wholesale. Lesson XII-01 measured that effect — 473 full page images in the first UPDATE round after a checkpoint against 2 in the rounds that followed.
  • Records the start LSN, which becomes the point replay begins from.

pg_backup_stop() records the end LSN and returns the label.

Read-only / Safea complete low-level backup, in one session
$ psql -U postgres -X -f llapi.sql
# the file contains:
#   SELECT pg_backup_start('after-disconnect-test', true);
#   SELECT pg_backup_stop();
 pg_backup_start
-----------------
0/F000028

(0/F000120,"START WAL LOCATION: 0/F000028 (file 00000001000000000000000F)+
CHECKPOINT LOCATION: 0/F000080                                           +
BACKUP METHOD: streamed                                                  +
BACKUP FROM: primary                                                     +
START TIME: 2026-08-27 21:32:58 UTC                                      +
LABEL: after-disconnect-test                                             +
START TIMELINE: 1                                                        +
","")
NOTICE:  all required WAL segments have been archived

The failure mode that catches people

A non-exclusive backup belongs to the session that started it.

Read-only / Safewhat the server log says when the session ends first
$ docker logs rbpg-pitr 2>&1 | grep -i abort
2026-08-27 21:32:50.904 UTC [576] WARNING:  aborting backup due to backend exiting before pg_backup_stop was called

A WARNING. Not an error, not a FATAL. If nothing is watching the server log, the backup script’s own output shows a start, a successful file copy, and an exit status of zero.

The practical consequence is that the low-level API cannot be driven from separate commands:

# BROKEN. Three sessions. The backup is aborted the moment the first
# psql exits, and the copy is of a cluster that is not in backup mode.
psql -c "SELECT pg_backup_start('nightly', true)"
tar czf /backup/base.tar.gz -C /var/lib/postgresql/18/main .
psql -c "SELECT pg_backup_stop()"

The copy still happens, the tarball still exists, and it is not a backup. The whole sequence must run inside one connection, which is why correct implementations use a client library holding the connection open across the copy rather than a shell script calling psql three times.

Read-only / Safea second backup in the same session
$ SELECT pg_backup_start('second', true);
ERROR:  a backup is already in progress in this session
Read-only / Safebackup_label is absent from PGDATA during a non-exclusive backup
$ SELECT count(*) AS backup_label_in_pgdata FROM pg_ls_dir('.') f WHERE f = 'backup_label';
 backup_label_in_pgdata
------------------------
                    0

What a correct low-level backup requires

  1. One session, held open for the whole backup.
  2. pg_backup_start().
  3. Copy PGDATA, excluding postmaster.pid, pg_wal/’s contents, and the standard exclusions the documentation lists.
  4. pg_backup_stop(); write the returned label to backup_label in the copy.
  5. Ensure every WAL segment between start and end LSN is archived — pg_backup_stop()’s NOTICE: all required WAL segments have been archived is the confirmation.
  6. Verify. A backup you have not restored is a hypothesis.

What to take from this

  • A running cluster cannot be copied naively. The API plus WAL replay is what makes an online physical backup consistent.
  • pg_backup_start() forces a checkpoint, guarantees full page writes, and records the start LSN.
  • pg_backup_stop() returns backup_label. You must write it. Omitting it produces a cluster that starts and is silently wrong.
  • The backup is session-scoped. Losing the connection aborts it with a WARNING and nothing else.
  • Prefer pg_basebackup or an established tool. Use the low-level API only when the environment requires it.

Cross-course references

  • Linux for Production Sysadmins — Part XLVIII (Backup tools) covers snapshot-based copies and whether a snapshot is atomic across the volumes a cluster spans.
  • Ceph & Distributed Storage — Part XXXVII (RBD snapshots) and Part CVI (RBD backup) cover the same question on distributed storage, where crash consistency across images is not free.
  • Proxmox — Part XIII (Proxmox Backup Server) covers hypervisor snapshots of a running database and what they do and do not guarantee.

Quiz

Knowledge check · 6 questions

  1. Q1. A backup script runs pg_backup_start in one psql invocation, tars PGDATA, then runs pg_backup_stop in another psql invocation. It exits zero every night. What is the state of those backups?

  2. Q2. A restored physical backup starts cleanly, reports no errors, and later returns rows that reference index entries pointing at nothing. backup_label was never written into the copy. Why does this happen?

  3. Q3. PGDATA is on one LVM volume and pg_wal on another. A backup procedure snapshots each volume in turn without using the backup API. What is wrong with it?

  4. Q4. What does pg_backup_start do that makes an online file copy usable? Select all that apply.

  5. Q5. The exclusive backup API was removed because a crash during a backup left backup_label in PGDATA, which could prevent the production primary from restarting.

  6. Q6. Why must the low-level backup API be driven from a single held-open connection, and what goes wrong if it is not?

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