Skip to main content
RunBook Academy

← All checklists in PostgreSQL

As neededpg-post-upgrade

PostgreSQL Post-Upgrade Verification

19 items ·15 critical ·4 warn ·0 info

How to use this checklist

Immediately after the upgrade, before the window closes and before anybody deletes anything.

Half of it compares against measurements taken beforehand. If those were not captured, most of this checklist cannot be completed — which is itself the finding.

The backup that does not exist yet

What to compare, and against what

CheckCompared against
Row counts on significant tablesValues captured before the window
relkind object countsValues captured before the window
Extensions and versions, every databaseThe pre-upgrade enumeration
EXPLAIN output for significant queriesPlans captured before the window
Non-default settingspg_settings from the old cluster

Check what statistics arrived before rebuilding them

Things that live outside the data directory

An upgrade moves the data. It does not necessarily move:

  • pg_hba.conf and pg_ident.conf
  • TLS certificate material
  • postgresql.conf, if it is managed separately
  • Anything a configuration-management tool owns

So the authentication test matters, and it must be run from the application’s network path — a test over loopback will pass against a trust rule regardless of whether the real path works.

Attempt a wrong credential too. A path that has only ever succeeded has not been shown capable of refusing.

Do not run delete_old_cluster.sh yet

pg_upgrade generates it, and in copy mode it is the only thing standing between you and a rollback that costs minutes rather than a full restore.

Keep the old data directory until the new cluster has served for an agreed period, and make its removal a separate, authorised step with a note.

Record the timings

Check, upgrade, statistics, verification, total. The next upgrade is planned from these numbers.

Measured in one estate: pg_upgrade itself took 1587 ms on a small database in copy mode. The window was sized at two hours. Knowing which phase actually consumes the time is what makes the next window honest.

Where the numbers come from

Every figure is a comparison against the pre-upgrade capture: row counts, object counts by relkind, extension versions in every database, non-default settings diffed from the old cluster, and plans for the significant queries.

What the statistics actually carried across is measured before any ANALYZEpg_stats and pg_class.reltuples — and recorded, because that measurement is what tells you whether the next window needs the statistics phase at all.

The phase timings come from the window that just ran, recorded while they are still known.

Access this needs

A role holding pg_monitor and pg_read_all_settings on every node of the upgraded estate, plus the captured pre-upgrade state.

A non-privileged connection from the application’s own network path with the real credential and driver, and a deliberately wrong one — pg_hba.conf and TLS material live outside the data directory and do not always follow an upgrade.

Write access to wherever the new physical backup will be taken, because that item is the most urgent one here and cannot be deferred to a later review.

What the review produces

A dated record naming the reviewer, the versions before and after, and the disposition of every item, with the full before-and-after comparison attached and the phase timings recorded for the next upgrade.

Two items are reported regardless of everything else: whether a new physical backup of the upgraded cluster exists, and whether the old cluster is still retained under an agreed date and owner. Until the first is true the estate’s recovery path is a restore of the old version followed by another upgrade, which is a recovery time objective nobody agreed to.

Sign-off

  • Reviewer: ________________ Date: ___________
  • Database owner: ___________ Date: ___________
  • Service owner: ____________ Date: ___________

Every critical item must pass. A failing critical item is a blocker, not a note for the next sprint: record the date, the reviewer, the disposition of every item that did not pass, and the name of whoever accepted the residual risk.

Critical15 items

  1. psql -c "SELECT version();"
  2. psql -c "SELECT relkind, count(*) FROM pg_class WHERE relkind IN ('r','i','S','v','m') GROUP BY 1 ORDER BY 1;"
  3. psql -c "SELECT extname, extversion FROM pg_extension ORDER BY extname;"
  4. psql -c "SELECT application_name, state, sync_state, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS bytes_behind FROM pg_stat_replication ORDER BY application_name;"
  5. psql -c "SELECT pg_switch_wal();"

Warning4 items

  1. psql -c "SELECT count(*) AS stats_rows FROM pg_stats WHERE tablename = 'orders';"