Choose between pg_dump/pg_restore and logical replication for major/platform moves; make schema, locale/encoding, sequences, large objects, extensions and business invariants explicit, then cut over only after a write freeze and a clearly defined rollback boundary.

Logical Upgrade Paths, Cross-Platform Migration, Downtime Minimization, and Validation

Choose between pg_dump/pg_restore and logical replication for major/platform moves; make schema, locale/encoding, sequences, large objects, extensions and business invariants explicit, then cut over only after a write freeze and a clearly defined rollback boundary.

Intermediate → Advanced180–240 minutesPostgreSQL upgrade and production change engineeringCurrent patched PostgreSQL 18.x target; resolve current minor from official release/version pages at change timeMajor-upgrade labs use two locally installed supported PostgreSQL majors when availableCore PostgreSQL mandatory tooling: pg_upgrade, pg_dump/pg_restore, psql, vacuumdbServiceHub disposable objects: app.ch23_* and servicehub_ch23_* databases/clustersAdmin/OS package access required for binary/cluster upgrade labs; application DDL lab uses normal database privilegesFree local tooling only; no managed service or commercial migrator requiredLast reviewed: August 2026

Learning outcomes

ServiceHub must move from one operating-system platform to another while also changing PostgreSQL major version. pg_upgrade is designed around compatible physical storage and local cluster conversion; a logical migration reconstructs database objects and rows through SQL/protocol-level representations, making it the natural cross-platform path. The cost is more data movement and a different cutover design.

01

Compare pg_dump/pg_restore and built-in logical replication by downtime, portability and feature coverage.

02

Inventory database encoding/locale provider, extensions, large objects, sequences, schema/DDL and replication identity before migration.

03

Build and restore a custom-format logical dump into a clean disposable database using the newer client tools.

04

Validate rows, aggregates, sequence state, constraints and representative plans rather than accepting restore exit status alone.

05

Define a logical-replication cutover with a write freeze, catch-up proof, sequence/large-object handling and an explicit rollback point.

1. Inventory what must survive a logical migration

sql · ServiceHub migration inventory
SELECT current_database(),       pg_encoding_to_char(d.encoding) AS encoding,       d.datlocprovider,       d.datcollate,       d.datctype,       d.datlocale,       d.datcollversionFROM pg_database AS dWHERE d.datname = current_database();SELECT extname, extversionFROM pg_extensionORDER BY extname;SELECT count(*) AS large_object_countFROM pg_largeobject_metadata;SELECT schemaname, sequencename, last_valueFROM pg_sequencesWHERE schemaname = 'app'ORDER BY sequencename;

Encoding and database locale/provider choices are database-creation properties. ICU/libc versions can alter sort behavior. Extensions must exist on the target platform/major. Sequences and large objects need explicit attention in logical-replication designs.

2. Create the Chapter 23 source dataset

sql · source ServiceHub data
DROP TABLE IF EXISTS app.ch23_order CASCADE;SET ROLE servicehub_owner;CREATE TABLE app.ch23_order (  order_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,  tenant_id integer NOT NULL,  customer_id bigint NOT NULL,  status text NOT NULL CHECK (status IN ('queued','assigned','completed','cancelled')),  amount numeric(12,2) NOT NULL CHECK (amount >= 0),  created_at timestamptz NOT NULL,  note text NOT NULL);INSERT INTO app.ch23_order(tenant_id,customer_id,status,amount,created_at,note)SELECT (g % 32) + 1,       10000 + (g % 4000),       (ARRAY['queued','assigned','completed','cancelled'])[(g % 4)+1],       ((g % 50000) / 100.0)::numeric(12,2),       TIMESTAMPTZ '2026-01-01 00:00+00' + (g || ' seconds')::interval,       'upgrade-change-lab-' || gFROM generate_series(1,100000) AS g;CREATE INDEX ch23_order_tenant_status_idxON app.ch23_order (tenant_id, status);RESET ROLE;ANALYZE app.ch23_order;
sql · business acceptance baseline
SELECT count(*) AS orders,       sum(amount) AS amount_sum,       count(*) FILTER (WHERE status='completed') AS completed_orders,       min(created_at) AS first_created_at,       max(created_at) AS last_created_atFROM app.ch23_order;SELECT last_value, is_calledFROM app.ch23_order_order_id_seq;

Store these values with the change ticket. They are simple business invariants, not a cryptographic proof of every byte.

3. Dump/restore: use the newer pg_dump for cross-version migration

PostgreSQL's guidance is to use the newer version's pg_dump when moving to a newer server. A newer pg_dump can read many older server versions, while an older pg_dump refuses to dump a server newer than itself.

shell · custom-format dump with PG18 client
# NEW_BIN points to the target PostgreSQL major's tools.# Cluster-global roles/tablespaces are NOT in a per-database pg_dump."$NEW_BIN/pg_dumpall" \  --dbname="service=servicehub-lab-admin" \  --globals-only \  --file=servicehub_ch23_globals.sql"$NEW_BIN/pg_dump" \  --dbname="service=servicehub-lab-admin" \  --format=custom \  --statistics \  --file=servicehub_ch23.dump"$NEW_BIN/pg_restore" --list servicehub_ch23.dump | head -n 40

pg_dump covers one database. PostgreSQL roles and tablespaces are cluster-global, so inventory/provision them separately; pg_dumpall --globals-only is one free local way to capture those definitions. Review the globals script before applying it to a target because role attributes, memberships, tablespace paths, and ownership policy may intentionally differ. Using pg_restore --no-owner does not remove the need for a deliberate target-role and ACL strategy.

A whole-database dump includes table data, sequence values and large objects by default. PostgreSQL 18 can also include optimizer statistics with --statistics; extended/custom/cumulative statistics still require appropriate post-restore analysis.

4. Restore into a clean target database—not on top of unknown state

shell · clean target restore drill
"$NEW_BIN/dropdb" --if-exists servicehub_ch23_restore"$NEW_BIN/createdb" --template=template0 servicehub_ch23_restore"$NEW_BIN/pg_restore" \  --dbname=servicehub_ch23_restore \  --no-owner \  --exit-on-error \  servicehub_ch23.dump

For a real cross-platform migration, create the target database with deliberate encoding/locale-provider settings that satisfy application requirements. Do not assume the target OS's default locale is equivalent to the source.

5. Validate restored objects and business invariants

sql · target acceptance queries
SELECT count(*) AS orders,       sum(amount) AS amount_sum,       count(*) FILTER (WHERE status='completed') AS completed_orders,       min(created_at) AS first_created_at,       max(created_at) AS last_created_atFROM app.ch23_order;SELECT last_value, is_calledFROM app.ch23_order_order_id_seq;SELECT conname, contype, convalidatedFROM pg_constraintWHERE conrelid = 'app.ch23_order'::regclassORDER BY conname;SELECT indexrelid::regclass AS index_name,       indisvalid, indisreadyFROM pg_indexWHERE indrelid='app.ch23_order'::regclassORDER BY 1;

Compare source and target acceptance values, then execute representative reads/writes. A dump that restores without error can still have different collation behavior, extension semantics, planner choices or driver/application behavior.

6. Logical replication lowers cutover downtime—but it does not replicate everything

Built-in logical replication copies table rows and ongoing INSERT/UPDATE/DELETE/TRUNCATE changes through publications/subscriptions. It can replicate between different PostgreSQL majors, making it useful for low-downtime upgrades. But current PostgreSQL does not replicate schema/DDL, sequence state or large objects.

Core logical replication replicates table changes, not schema/DDL, sequence state, or PostgreSQL large objects. Those are separate migration workstreams. Schema must be made compatible on the subscriber before replicated changes require it; sequence values need an explicit cutover synchronization; applications using large objects need a separate copy/validation strategy or a modeled-table redesign.

Object/change pg_dump/restore Built-in logical replication
Schema/DDL Included in normal dump Not replicated; apply compatible schema separately
Table rows Snapshot at dump time Initial copy + ongoing row changes
Sequences Values included by normal full dump Not replicated
Large objects Included by normal full dump Not replicated
Cross-platform Yes, logical representation Yes when both sides support logical protocol/features
Downtime Often proportional to final dump/restore/cutover unless staged Can shrink write-freeze to final catch-up/validation

7. A minimal logical-replication topology is more than CREATE SUBSCRIPTION

sql · publisher-side conceptual setup
-- Requires wal_level=logical and replication-role/HBA configuration.CREATE PUBLICATION ch23_servicehub_pubFOR TABLE app.ch23_order;SELECT pubname, puballtablesFROM pg_publicationWHERE pubname='ch23_servicehub_pub';
sql · subscriber-side conceptual setup
-- Create compatible target schema/table FIRST.-- Connection string shown without a real password.CREATE SUBSCRIPTION ch23_servicehub_subCONNECTION 'host=127.0.0.1 port=55471 dbname=servicehub_lab user=ch23_repl'PUBLICATION ch23_servicehub_pub;SELECT subname, subenabledFROM pg_subscriptionWHERE subname='ch23_servicehub_sub';

This topology is optional/administrative because it requires two servers plus logical-replication authentication. The free mandatory lab is the dump/restore drill above. For production logical migration, apply additive target schema changes before publisher changes that would otherwise make incoming rows incompatible.

8. Sequence state creates a cutover hazard

Suppose the subscriber has caught up with every table row but its identity sequence still has an old value. After cutover, the first insert can collide with an existing primary key. During the final write freeze, capture source sequence state and apply it to the target.

sql · source sequence state at write freeze
SELECT last_value, is_calledFROM app.ch23_order_order_id_seq;
sql · target sequence repair using captured source values
-- Substitute the captured values from the source:SELECT setval(  'app.ch23_order_order_id_seq',  100000,  true);SELECT last_value, is_calledFROM app.ch23_order_order_id_seq;

Do not hard-code 100000 in a real runbook; transfer the observed source state. Large objects require a separate migration mechanism because built-in logical replication does not carry them.

9. Write-freeze defines the clean rollback boundary

  1. Prove target schema/extensions/locale/clients are ready while source remains primary.
  2. Let initial copy and replication catch up.
  3. Enter an explicit application write freeze.
  4. Wait until replication shows no unapplied source changes.
  5. Transfer/verify sequence state and any non-replicated objects.
  6. Run business invariants and smoke writes against the target in a controlled gate.
  7. Route production writes to the target only after the acceptance decision.

Before target writes begin, rollback can often mean “keep source primary and abandon/rebuild target.” After target accepts unique production writes, switching back to the old source without reverse replication/reconciliation loses or forks those writes. The rollback design changes at that moment.

Wrong approach

“Logical replication is caught up, so flip DNS and we can always flip back.” Once the new primary accepts writes that are not replicated back, the old source is no longer a current rollback copy. Define the irreversible/write-divergence point in the runbook.

10. Cross-platform validation includes collation semantics and query performance

sql · target collation/version mismatch audit
SELECT c.oid::regcollation,       c.collprovider,       c.collversion,       pg_collation_actual_version(c.oid) AS actual_versionFROM pg_collation AS cWHERE c.collversion IS DISTINCT FROM pg_collation_actual_version(c.oid)ORDER BY 1;EXPLAIN (ANALYZE, BUFFERS, SETTINGS, TIMING OFF)SELECT tenant_id, status, count(*), sum(amount)FROM app.ch23_orderGROUP BY tenant_id, statusORDER BY tenant_id, status;

Repeat the same business queries on source and target. Identical rows do not guarantee identical sort order under a different collation library or identical performance under a different planner/OS/storage stack.

11. Cleanup

shell · remove logical dump artifact after secure retention policy
"$NEW_BIN/dropdb" --if-exists servicehub_ch23_restorerm -f servicehub_ch23.dump servicehub_ch23_globals.sql

Dump files contain data and executable database definitions; protect them as backups, not disposable build logs. Only delete after the lab or according to the real backup retention policy.

Production judgment

Choose dump/restore when simplicity/portability matters and the data-movement window is acceptable. Choose logical replication when low write downtime justifies topology complexity. In both cases, validate encoding/locale/extensions/sequences/large objects/application invariants and define the last safe rollback point before target-only writes.

Check your understanding

  1. Why is a logical dump naturally suitable for cross-platform migration?
  2. What three important things does built-in logical replication not replicate?
  3. Why should the newer pg_dump be used for an upgrade dump?
  4. What is the clean rollback point in a one-way logical-replication cutover?
  5. Why can row-count equality still be insufficient validation?
Review the answers

Logical dumps reconstruct SQL/data representations instead of reusing platform-specific physical cluster files. Logical replication does not replicate DDL/schema, sequences, or large objects. Newer pg_dump understands the target-era output and can dump supported older servers. The clean rollback point ends when target-only writes begin unless reverse synchronization exists. Collation, constraints, extension behavior, business invariants and plans can differ even with equal row counts.

Authoritative references

Upgrade, compatibility, locking, and migration behavior is version-sensitive. These PostgreSQL primary sources define the mechanisms used here; always read the exact source/target release notes during a real change.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.