Failover: Replica Promotion
Promote a replica to become the new primary when the current primary fails. pgcli provides a 3-step manual failover workflow — each step runs on its respective host, no auto-detection of same-host vs cross-host topology.
Overview
After failover (promote ro1 → new primary):
3-Step Failover
Each step is an independent command. Run them in order, on their respective hosts.
Step 1: pg replica promote <name>
Run on the host of the replica being promoted to primary.
What happens:
- Validates the instance is a replica (
ReplicaOfis set) and the container is running - Calls
pg_promote()(PostgreSQL 12+ native promotion — no container restart) - Waits for recovery to end (usually sub-second)
- Cleans up
primary_conninfofrompostgresql.auto.confviaALTER SYSTEM RESET - Updates config: clears
ReplicaOfandPrimaryDSN, enablesPITR - Automatically initializes PITR:
- pgBackRest stanza creation
archive_mode/archive_commandconfiguration- PostgreSQL restart to apply postmaster-level parameters
- Prints next-step instructions
Idempotent: If the replica is already promoted (e.g. from a manual pg_ctl promote), the command skips to config update.
Step 2: pg replica drop <name> -i <old-primary>
Run on the old primary host to clean up the replication slot. This step is only needed in specific scenarios.
This drops the physical replication slot pgcli_r_ro1 on the old primary. Without cleanup, the slot would hold WAL indefinitely until the primary runs out of disk space.
When to run:
| Scenario | Action | Why |
|---|---|---|
| Old primary is permanently lost | Skip | Slot is gone with the server |
| Plan to demote old primary to replica | Skip | repoint destroys the data directory (including pg_replslot/), all slots are implicitly removed |
| Old primary recovered, keep running as independent primary | Must run | Slot holds WAL indefinitely; without cleanup the disk will eventually fill up |
| Old primary recovered but will be shut down | Optional | No harm in skipping if the instance will not run again |
Keep the old primary as-is? If you want to preserve the old primary with its original data (e.g. for forensic analysis or as a read-only archive), you can simply leave it alone — do not run
droporrepointon it. The old primary keeps running as an independent instance with stale data. Just be aware that the replication slot for the promoted replica still exists and will accumulate WAL; you may want to drop just that specific slot (pg replica drop ro1 -i pg01) while leaving everything else untouched.
Step 3: pg replica repoint <name> --primary-dsn <dsn> --primary-name <name>
Run on each remaining replica host to re-point it to the new primary.
What happens:
- Queries the new primary’s extensions via DSN (
pg_extensioncatalog) - If non-builtin extensions exist (e.g. pg_cron, timescaledb), builds a local
-extimage with matching packages - Stops the old replica container and destroys its data directory
- Creates a replication slot on the new primary via DSN
- Updates config:
ReplicaOf,PrimaryDSN,ImageTag,Extensions, disablesPITR - Re-initializes via
pg_basebackup -Rfrom the new primary - Starts the replica container in standby mode
Why destroy + rebuild instead of ALTER SYSTEM SET?
After promotion, the new primary advances to a new timeline. Other replicas on the old timeline cannot simply change primary_conninfo — PostgreSQL rejects the connection with:
The only safe approach is a full pg_basebackup from the new primary.
Getting the Primary DSN
Get the new primary’s connection string from pg status on the promoted replica’s host:
Replace 127.0.0.1 with the new primary host’s IP reachable from the replica host (e.g. 10.241.21.97).
Demoting the Old Primary
When the old primary recovers, you can rejoin it as a replica of the new primary using the same repoint command:
This works even though pg01 was a primary (no ReplicaOf set). The command:
- Stops pg01 and destroys its data (including old PITR stanza)
- Creates a replication slot for pg01 on the new primary
- Sets
ReplicaOf = "ro1",PITR.Enabled = false - Re-initializes from the new primary via
pg_basebackup
After repoint, pg01 streams WAL from the new primary as a read-only replica — no WAL archiving, no backups.
Extension Sync
When a replica is repointed to a new primary, pgcli automatically synchronizes extensions:
- Query — Connects to the new primary via DSN and queries
pg_extensionfor installed extensions - Filter — Identifies non-builtin extensions (those requiring external packages, e.g. pg_cron, pgmq, timescaledb)
- Build — If non-builtin extensions exist, builds a local
-extimage:- If a local
-extimage already exists, installs missing packages on top (reuses Pigsty repo — fast) - If no
-extimage exists, builds from the base image with Pigsty repo setup apt-get installis idempotent — installing an already-present package is a no-op
- If a local
- Apply — On replica start,
ApplyExtensionswritesshared_preload_librariestopostgresql.conf - Skip CREATE EXTENSION — Replicas are read-only; extensions are replicated from the primary via
pg_basebackup+ WAL streaming
This ensures the replica container has the required shared libraries (e.g. pg_cron) that are referenced in postgresql.auto.conf.
Same-Host vs Cross-Host
pgcli does not auto-detect topology. You choose where to run each command:
| Scenario | Step 1 | Step 2 | Step 3 |
|---|---|---|---|
| All on one host | pg replica promote ro1 |
pg replica drop ro1 -i pg01 |
pg replica repoint ro2 --primary-dsn "postgres://...@127.0.0.1:..." --primary-name ro1 |
| Primary + replicas split across hosts | On replica host | On old primary host | On each replica host with the new primary’s network IP |
| Mixed | On the respective host | On old primary host | On each replica host |
The --primary-dsn must use an IP/hostname reachable from the host where repoint runs.
Complete Example
Cross-Host Example
Cascading Replication
A replica can itself serve as a primary for downstream replicas, forming a cascading chain. This reduces load on the primary and enables hierarchical topologies.
How It Works
-
Create a replica of a replica: Use the replica as the
-itarget -
WAL propagation:
- ra2 streams WAL from ra3
- ra2_ro1 streams WAL from ra2
- Data flows: ra3 → ra2 → ra2_ro1
-
Replication slots: Each link maintains its own slot
- ra3 has slot
pgcli_r_ra2 - ra2 has slot
pgcli_r_ra2_ro1
- ra3 has slot
Benefits
- Reduced primary load: Only direct replicas connect to primary
- Geographic distribution: Primary → regional replica → local replicas
- Network efficiency: Local replicas can share a regional upstream
Limitations
- Increased latency: Each hop adds replication delay
- Cascading failures: If ra2 fails, ra2_ro1 loses its upstream
- Promotion complexity: Promoting ra2_ro1 requires repointing it to a new primary
Verify Cascading
Failover with Cascading
If ra2 (middle node) fails:
If ra3 (primary) fails and ra2 is promoted:
Notes
- pg_promote() — PostgreSQL 12+ native function, no container restart required. The instance exits recovery in-place and becomes read-write immediately
- Timeline divergence — After promotion, the new primary is on a new timeline. Other replicas cannot be re-pointed with
ALTER SYSTEM SET primary_conninfo— they must be rebuilt viapg_basebackup - PITR on promoted replica — After promotion, run
pg startto create the pgBackRest stanza and enable WAL archiving. The promoted replica has no prior backup history - Replication slots — The old primary’s slot for the promoted replica becomes stale after promotion.
pg replica dropcleans it up. If the old primary is demoted to a replica,repointdestroys the old data and the stale slot is no longer referenced - Extensions — Replica containers inherit
shared_preload_librariesfrom the primary viapostgresql.auto.conf. The repoint command ensures the local image has the required extension packages before rebuilding the replica - CREATE EXTENSION skipped — Replicas are read-only;
pg_basebackupcopies the extension metadata from the primary, soCREATE EXTENSIONis not needed (and would fail with “cannot execute CREATE EXTENSION in a read-only transaction”)