PostgreSQLXVII · Capacity, Maintenance and UpgradesMaintenance
Major version upgrade options compared
What you'll learn
- Compare dump/restore, pg_upgrade and logical replication honestly
- Choose a route from downtime tolerance and cluster size
- Identify what each route does not carry across
- Plan the rollback for each
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
Major versions change the on-disk format, so the data must be moved. There are four ways, and the right one is decided almost entirely by downtime tolerance and size.
The four routes
| Route | Downtime | Cluster size | Rollback |
|---|---|---|---|
pg_dump / pg_restore | Hours to days | Small only | Old cluster untouched |
pg_upgrade --copy (default) | Minutes to hours | Medium | Old cluster untouched |
pg_upgrade --link / --swap | Minutes | Any | None. See below |
| Logical replication | Seconds | Any | Switch back |
Dump and restore
Simple, thorough, and slow. Everything is read through the SQL layer and every index rebuilt, which lesson XIII-02 covered.
It has one property nothing else offers: it produces a cluster with no inherited physical baggage — no bloat, no fragmented indexes. That is occasionally worth the downtime on a small database and never worth it on a large one.
pg_upgrade in copy mode
The default. Copies the data files, converts the catalogue.
$ pg_upgrade --old-datadir=... --new-datadir=... --old-bindir=... --new-bindir=...Upgrade Complete
----------------
ELAPSED: 2 secondspg_upgrade --link and --swap
--link hard-links the data files into the new cluster instead of
copying them, so the time is independent of cluster size — minutes on
any size.
$ /usr/lib/postgresql/17/bin/pg_upgrade --help | grep -E 'link|clone|copy|swap'
/usr/lib/postgresql/18/bin/pg_upgrade --help | grep -E 'link|clone|copy|swap'--- 17 ---
-k, --link link instead of copying files to new cluster
--clone clone instead of copying files to new cluster
--copy copy files to new cluster (default)
--copy-file-range copy files to new cluster with copy_file_range
--- 18 ---
(the same four, plus)
--swap move data directories to new clusterLogical replication
Replicate from the old major version to a new one, let it catch up, then switch the application over. Downtime is the switchover — seconds.
It is the only route that offers near-zero downtime on a large cluster, and it costs the most in preparation:
- Every table needs a replica identity — a primary key, or an explicitly configured one. Tables without one cannot replicate updates or deletes.
- Sequences are not replicated. They must be advanced on the target before the switchover, and forgetting produces duplicate key errors immediately afterwards.
- DDL is not replicated. The schema must be kept in step manually.
- Large objects are not replicated.
- The initial sync of a large table is itself a long operation.
It is a project rather than a procedure, and on a large cluster with a strict downtime budget it is the right one.
Choosing
| Situation | Route |
|---|---|
| Small database, downtime available | Dump and restore |
| Medium, hours available | pg_upgrade --copy |
| Large, minutes available, backup verified | pg_upgrade --link / --swap |
| Large, seconds available | Logical replication |
| Reflink-capable filesystem | pg_upgrade --clone |
What none of them carry across for free
Configuration. postgresql.conf is not migrated. Settings are
removed and added between major versions, and a copied file can contain
settings the new version rejects at startup.
Extensions. They must be installed for the new major version
before the upgrade. pg_upgrade --check reports missing ones, which
is a large part of why the check is worth running days ahead.
Cumulative statistics. Lesson XVII-07 measured last_analyze NULL
and n_live_tup zero after an upgrade, even in 18 where planner
statistics survive.
Anything outside the cluster. Cron jobs, backup scripts, monitoring
queries referencing views that changed — lesson XVI-03’s
pg_stat_bgwriter split is exactly this.
What to take from this
- Four routes, chosen by downtime tolerance and size, not by elegance.
- Measured: copy mode on 78 MB took 2 seconds. That establishes the mechanism and nothing about a terabyte.
--linkand--swaphave no rollback. The fallback is a restore.--swapis new in 18, verified by comparing the 17 and 18 binaries.- Logical replication gives seconds of downtime and needs replica identities, manual sequence advancement, and schema discipline.
- None of them carry configuration, extensions, cumulative statistics or anything outside the cluster.
Cross-course references
- Linux for Production Sysadmins — Part LXXIII (Change management) covers choosing an approach by its rollback cost rather than by its runtime, which is the decision this lesson turns on.
- Git, CI/CD & GitOps — Part LX (Forward fix versus rollback) covers the same choice in delivery terms, including the case where rollback stops being available partway through.
Quiz
Knowledge check · 6 questions
Q1. A 4 TB cluster must be upgraded with under an hour of downtime, and a verified backup exists. Which route fits?
Q2. After a pg_upgrade --link, the new cluster is running and the application reports a problem. Why can the old cluster not simply be started?
Q3. A logical-replication upgrade completes and the application immediately hits duplicate key errors on insert. What was missed?
Q4. Which are NOT carried across by pg_upgrade? Select all that apply.
Q5. A copy-mode pg_upgrade measured at 2 seconds on a 78 MB cluster supports an estimate for a multi-terabyte upgrade.
Q6. Why can pg_upgrade move terabytes of user data in minutes, and what does that imply about when it cannot?
Passing score: 75%. Answers are checked in this browser.