PostgreSQL Mastery
PostgreSQL Mastery lessons / Module 39: Upgrades and maintenance

Upgrades and routine maintenance

Level 7: Operations and HA20 min

Minor releases (18.1 to 18.2) are bug and security fixes and should be applied promptly with a restart. Major releases change the storage format and need pg_upgrade, a dump and restore, or logical replication. Around them, routine maintenance keeps indexes, statistics and extensions healthy.

This lesson is interactive. Run every example on a real PostgreSQL 18 server in your browser, read EXPLAIN ANALYZE plans, and take the quiz.
Open the interactive lesson
You will learn to
  • Plan minor and major upgrades and their risks
  • Use pg_upgrade with --link and the post-upgrade steps
  • Run routine maintenance: REINDEX CONCURRENTLY, extension updates, collation checks

conceptThe concept

Minor upgrades: install new binaries and restart; no data conversion. Read the release notes for occasional "reindex these indexes" instructions. Each major version is supported for five years.

Major upgrades with pg_upgrade: initialise a new cluster, run pg_upgrade --check, then upgrade with --link (hard links, minutes even for terabytes, but the old cluster cannot be started afterwards) or --clone / --swap (18). Afterwards, update extensions and run vacuumdb --analyze-in-stages (statistics are carried over since 18, but extended statistics are not). Logical replication upgrades trade complexity for near-zero downtime and an easy rollback path.

Routine maintenance: REINDEX CONCURRENTLY for bloated or corrupted indexes, ALTER EXTENSION ... UPDATE after upgrades, and watching for collation changes: an OS glibc upgrade can change text sort order and silently corrupt B-tree indexes on text columns. Since 17, the builtin collation provider avoids that dependency.

whyWhy it matters

Running unsupported versions means missing security fixes, and upgrades put off for years become multi-version jumps with more surprises. A rehearsed upgrade runbook turns a scary weekend into a routine task.

Use caseReal-world scenario

The shop moves from 14 to 18: rehearsal on a restored copy with pg_upgrade --check and timing, application tests against 18, then a production run with --link during a 15-minute window, followed by analyze-in-stages and extension updates.

Try itExamples

Run and edit these in the interactive lesson.

Version and extension status
SELECT current_setting('server_version') AS version,
       current_setting('server_version_num')::int / 10000 AS major;
SELECT name, default_version, installed_version,
       default_version IS DISTINCT FROM installed_version AS needs_update
FROM pg_available_extensions
WHERE installed_version IS NOT NULL
ORDER BY name;
A pg_upgrade runbook
# 1. new binaries installed, new cluster initialised with matching options
/usr/lib/postgresql/18/bin/initdb -D /data/pg18 --data-checksums --locale-provider=builtin --builtin-locale=C.UTF-8

# 2. dry run while the old cluster is still up
/usr/lib/postgresql/18/bin/pg_upgrade -b /usr/lib/postgresql/14/bin -B /usr/lib/postgresql/18/bin \
  -d /data/pg14 -D /data/pg18 --link --check

# 3. stop the old cluster, upgrade for real, start the new one
/usr/lib/postgresql/18/bin/pg_upgrade ... --link --jobs=8

# 4. after start
psql -c "ALTER EXTENSION pg_stat_statements UPDATE"
vacuumdb --all --analyze-in-stages --missing-stats-only
# 5. rebuild standbys (rsync or new base backups), re-run backups
Rebuilding an index without blocking writes
SELECT indexrelid::regclass AS index_name,
       pg_size_pretty(pg_relation_size(indexrelid)) AS size,
       idx_scan
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 5;
REINDEX INDEX CONCURRENTLY order_items_pkey;
REINDEX CONCURRENTLY builds a new copy next to the old one and swaps them, so reads and writes continue. It cannot run inside a transaction block.
Collation versions
SELECT datname, datlocprovider, datcollate, datcollversion,
       pg_database_collation_actual_version(oid) AS actual_version
FROM pg_database
WHERE datname = current_database();
If the recorded and actual versions differ after an OS upgrade, text indexes may need REINDEX and then ALTER DATABASE ... REFRESH COLLATION VERSION.

Avoid / preferDo this, not that

Avoid
-- pg_upgrade on production first, extensions checked afterwards
Prefer
-- rehearse on a restored copy: extensions, pg_upgrade --check, application tests

Extensions without builds for the new major version, or with incompatible versions, are the most common cause of failed upgrades.

Avoid
-- OS upgrade changes glibc; indexes on text are left as they are
Prefer
SELECT datname, datcollversion, pg_database_collation_actual_version(oid) FROM pg_database;
-- REINDEX affected indexes, then ALTER DATABASE ... REFRESH COLLATION VERSION

A changed collation sorts text differently, silently corrupting B-tree indexes built with the old order.

PitfallsCommon mistakes

  • Skipping minor releases for months. They contain security and data-corruption fixes and only need a restart.
  • Running pg_upgrade --link without a tested backup. After the new cluster starts, the old one is no longer usable.
  • Upgrading the operating system under a running PostgreSQL without checking glibc collation changes.

LimitsWhen not to use it

Do not postpone minor releases: they contain only bug and security fixes and need just a restart. Major upgrades deserve planning; minor ones deserve a schedule.

SecuritySecurity notes

Minor releases fix CVEs. Track the PostgreSQL security announcements and apply minor releases within weeks, and stay on a major version that is still supported (five years from release).

ChallengeTry it yourself

List installed extensions whose installed version is behind the default version available on this server.

Show one solution
One solution
SELECT name, installed_version, default_version
FROM pg_available_extensions
WHERE installed_version IS NOT NULL AND installed_version <> default_version;

InterviewQuestions you should be able to answer

What options exist for a PostgreSQL major-version upgrade?
pg_dump/pg_restore: simple and portable, but downtime proportional to data size. pg_upgrade: rewrites only catalogs; with --link it takes minutes regardless of size, downtime is short, rollback means restoring a backup (or using --clone/--swap on suitable filesystems). Logical replication: build the new cluster, replicate, cut over in seconds to minutes, keep the old cluster for rollback; more moving parts (sequences, DDL freeze, replica identity). After any method: update extensions, analyze, rebuild standbys, and verify.
Why can an operating system upgrade corrupt PostgreSQL indexes?
Text B-tree indexes are ordered by the collation, and with the libc provider the collation comes from the OS C library. A glibc update (notably 2.28) changed the sort order of many strings, so existing indexes no longer match what comparisons return, causing missed rows and duplicate-key anomalies. Detect with collation version checks or amcheck, fix with REINDEX, and avoid it with the ICU or (17+) builtin providers and planned rebuilds.

QuizCheck your understanding

4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.

Go deeperOfficial documentation