Skip to main content
RunBook Academy

← All checklists in PostgreSQL

Monthlypg-replication-readiness

PostgreSQL Replication Readiness Review

20 items ·13 critical ·7 warn ·0 info

How to use this review

Monthly, from both ends. The primary’s view and the standby’s view answer different questions, and the primary’s view disappears at exactly the moment you most need it.

The metric that is loudest when nothing is wrong

Absence, then slowness

Test both alerts this period

-- on the standby: genuine lag, on demand, harmlessly
SELECT pg_wal_replay_pause();
-- generate writes on the primary, wait for the page, then:
SELECT pg_wal_replay_resume();

And stop a standby deliberately to test the absence alert.

Both take a minute. An alert that has never fired has never been shown to work, and every replication alert that failed in the incidents this course is built from had been configured correctly and never tested.

Slots outlive the things they were created for

Prove it end to end

-- primary
INSERT INTO repl_probe VALUES (now());

-- standby, seconds later
SELECT max(at) FROM repl_probe;

-- standby: this must FAIL
INSERT INTO repl_probe VALUES (now());
-- ERROR:  cannot execute INSERT in a read-only transaction

Two statements per standby. They exercise the whole path, which no view does on its own.

Where the numbers come from

Lag comes from pg_stat_replication on the primary in bytes, using pg_wal_lsn_diff, and from pg_stat_wal_receiver and pg_last_wal_replay_lsn() on each standby. Time-based lag is recorded but never used as the threshold, because it climbs on an idle primary while the standby is perfectly caught up.

Slot retention comes from pg_replication_slots: active, wal_status, and the bytes held from restart_lsn. max_slot_wal_keep_size comes from pg_settings with its source, because a value inherited from the -1 default is a decision nobody made.

Access this needs

A role holding pg_monitor on the primary and on every standby, since the primary-side and standby-side views answer different questions and the primary-side one is unavailable in the failure that matters most.

Read access to each standby’s configuration — primary_conninfo, recovery_min_apply_delay, cluster_name — and to the pg_wal filesystem on the primary.

The ability to write one row to a test table on the primary and read it back on each standby. That is the only item that proves data is moving; everything else proves a process is connected.

What the review produces

A dated record naming the reviewer, every node in the topology, and the disposition of every item. Attach the pg_stat_replication extract with one row per expected standby, the slot listing with active and wal_status, and the end-to-end write-and-read test with its timing.

A standby that is expected and absent is the finding that outranks every lag measurement, and it goes to the service owner the same day.

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.

Critical13 items

  1. psql -c "SELECT application_name, state, sync_state FROM pg_stat_replication ORDER BY application_name;"
  2. psql -c "SELECT application_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS bytes_behind, replay_lag FROM pg_stat_replication;"
  3. psql -c "SELECT count(*) FROM pg_stat_replication;"
  4. psql -c "SELECT slot_name, slot_type, active, wal_status, age(catalog_xmin) AS catalog_xmin_age FROM pg_replication_slots;"

Warning7 items

  1. psql -c "SELECT pg_is_in_recovery(), pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) AS unreplayed_bytes, (SELECT count(*) FROM pg_stat_wal_receiver) AS receiver;"
  2. psql -c "SHOW primary_conninfo;"
  3. psql -c "SHOW hot_standby_feedback;"
  4. psql -c "SELECT datname, confl_tablespace, confl_lock, confl_snapshot, confl_bufferpin, confl_deadlock FROM pg_stat_database_conflicts WHERE datname = current_database();"