Architecture and operations
David Sterling  

PostgreSQL 19 RC 1: An Ops Plan to Rehearse pg_upgrade Now

PostgreSQL 19 RC 1 landed today, and the release team has already published the finish line: RC 2 on November 12, general availability on November 19. For anyone who runs Postgres for a living, that is a five-week window to answer one unglamorous question — what actually breaks when we upgrade? Release candidates are the right time to run that experiment: the build is close to final, churn is slowing, and you still have room to fix what you find.

Below is the rehearsal plan we use before a major-version change window: run it on real data, from a staging host, without touching production until the numbers are known.

1. Pin the timeline before you schedule anything

The RC 1 announcement sets out an unusually tidy calendar. RC 2 arrives on November 12, the same day as the scheduled update release for all supported versions — the release team deliberately folded that batch of fixes into 19 so GA ships with them. GA is planned for November 19. What matters operationally is the gap between RC 2 and GA: seven days. Any bug you only discover in that week is a bug you will live with after cutover.

Also note the upgrade constraint that never changes: 19 is a major version, so you need a major-version strategy — pg_upgrade or dump/restore. There is no in-place minor update from 18 to 19. The upgrading documentation is the reference to hand your change board.

2. Rehearse pg_upgrade on a copy of production

Take a filesystem snapshot or a base backup of the production cluster, restore it to a staging host with the same Postgres 18 binaries and the same extensions, and install the 19 RC binaries alongside. Then run the pre-flight check, which validates the catalog and reports every incompatibility without modifying data:

/usr/pgsql-19/bin/pg_upgrade --check \
  --old-datadir=/var/lib/pgsql/18/data \
  --new-datadir=/var/lib/pgsql/19/data \
  --old-bindir=/usr/pgsql-18/bin \
  --new-bindir=/usr/pgsql-19/bin

Once the check is clean, time a real run using --clone on a filesystem that supports reflinks (XFS, Btrfs, ZFS) or --link where speed matters more than a guaranteed rollback path:

time /usr/pgsql-19/bin/pg_upgrade --clone \
  --old-datadir=/var/lib/pgsql/18/data \
  --new-datadir=/var/lib/pgsql/19/data \
  --old-bindir=/usr/pgsql-18/bin \
  --new-bindir=/usr/pgsql-19/bin

The elapsed time from that run is your downtime budget, and it scales with catalog size rather than table size. Run ANALYZE afterwards, compare plan output on your ten heaviest queries, and check that every extension you depend on — PostGIS, pgvector, pg_partman, TimescaleDB — has a 19-compatible build. A missing extension is the most common reason a weekend upgrade gets rolled back at 3 a.m.

3. Re-test replication and logical decoding

RC 1 includes fixes in areas that touch replication and background maintenance: the beta-period list mentions several fixes for the automatic enablement of logical replication when wal_level is set to replica, plus a batch of parallel autovacuum fixes. Those are precisely the behaviours an HA topology depends on, so re-run your pipeline tests rather than assuming they still pass.

-- publisher side (18 today, 19 in the rehearsal)
ALTER SYSTEM SET wal_level = logical;
ALTER SYSTEM SET max_slot_wal_keep_size = '50GB';
SELECT pg_reload_conf();

CREATE PUBLICATION pg19_cutover FOR ALL TABLES;

-- subscriber side (the new 19 cluster)
CREATE SUBSCRIPTION pg19_cutover
  CONNECTION 'host=pg18-prod dbname=app user=repl'
  PUBLICATION pg19_cutover WITH (copy_data = true);

SELECT slot_name, active, restart_lsn FROM pg_replication_slots;
SELECT application_name, state, sent_lsn, replay_lsn, replay_lag
FROM pg_stat_replication;

A logical replication cutover gives you minutes of downtime instead of an hour, and it leaves the old cluster authoritative until the moment you flip writes. The tradeoff is WAL retention: a slot that stops advancing will hold WAL until it fills the disk. Set max_slot_wal_keep_size deliberately and alert on pg_replication_slots.wal_status so an abandoned rehearsal slot cannot take down the primary.

4. Prove the rollback path, not just the forward path

Two habits separate a rehearsed upgrade from a hopeful one. First, never run --link in production without a separate base backup or storage snapshot in the same change window — with hard links the old cluster is no longer independently recoverable. Second, rehearse the reverse direction once: promote the old cluster, repoint your connection pooler, and confirm that the application starts cleanly against the pre-upgrade schema. A logical replication setup makes that easy: the subscription can be reversed and the new cluster re-seeded.

Keep a compressed dump as the floor under all of it:

pg_dump -Fc -d app -f /backups/app_pre19.dump

5. Fold the PgBouncer security release into the same window

If you pool connections in front of Postgres, your next maintenance window should include PgBouncer 1.26.1, which fixes three CVEs. CVE-2026-107350 is the serious one: an authentication bypass where a client that knows the password of one SCRAM user can log in as any other SCRAM user, including backend superusers. CVE-2026-107349 and CVE-2026-107348 are unauthenticated denial-of-service issues caused by infinite loops — one from an empty option name in the startup options parameter, the other from an integer overflow in packet buffer growth that only applies when max_packet_size has been raised above its default.

[pgbouncer]
; 1.26.1 restores the 1.25 behaviour: ignore wins over tracked params
ignore_startup_parameters = extra_float_digits,search_path
max_packet_size = 2147483647   ; if you raised this, patch now
auth_type = scram-sha-256

Patch the pooler before you patch the database, not after. A pooler upgrade is restartable in seconds with no data movement, and in this case it closes an authentication hole that a database upgrade does nothing about.

6. Post-upgrade checks that catch the quiet failures

Correctness checks pass; performance regressions hide. After cutover, watch these for the first 24 hours:

  • pg_stat_activity filtered on backend_type = 'autovacuum worker' — the parallel autovacuum fixes can change how aggressively large tables are processed.
  • pg_stat_progress_vacuum and dead-tuple counts on your highest-churn tables, to confirm autovacuum keeps up.
  • pg_stat_statements ordered by total execution time, compared against a pre-upgrade capture, to catch planner regressions.
  • Client drivers. Server-side is only half of it — psqlODBC 18.00.0005 ships a security fix for CVE-2026-109448, so treat drivers as part of the same change.

7. If you run managed Postgres, your calendar is someone else’s

A release candidate only helps teams that control their own binaries. On RDS, Aurora, Cloud SQL or Azure Database, GA day is not upgrade day — the provider publishes its own supported-major-version timeline, and Aurora in particular has its own upgrade path rather than a pg_upgrade you can run yourself. If you run Postgres on your own hardware — common in Houston’s industrial, energy, logistics and healthcare shops — the five weeks between now and November 19 are quiet calendar time you will not get back later.

The five-week checklist

  • Snapshot production to staging; run pg_upgrade --check and fix every reported item.
  • Time a real --clone run; publish the measured downtime to whoever approves change windows.
  • Confirm extension availability for 19 — including PostGIS and pgvector if you use them.
  • Re-run replication and logical-decoding tests; alert on slot WAL retention.
  • Patch PgBouncer to 1.26.1 and audit max_packet_size and ignore_startup_parameters.
  • Capture a pre-upgrade pg_stat_statements baseline for post-cutover comparison.
  • Schedule the rehearsal rollback, not just the upgrade.

An RC is a free preview of the version you will be running for the next three years. Use the next five weeks to fail in staging, where failure is cheap.

1 Comment

  1. […] For the full rehearsal sequence, see PostgreSQL 19 RC 1: an ops plan to rehearse pg_upgrade now. […]

Leave A Comment