Reported symptoms
A routine kernel patch reboot of db-standby-01 at 21:40 causes every
write transaction on the primary to stop returning.
The primary is up, accepting connections, and answering read queries normally throughout. The application appears frozen rather than erroring — requests hang until they hit their own timeouts.
No error appears in the primary server log at any point.
Restarting the primary was considered and rejected, correctly: the primary is not faulty.
Service returns the moment the standby finishes booting and reconnects, six minutes later.
The cluster is described in its design document as highly available.
Evidence provided
$ psql -c "SELECT pid, state, wait_event_type, wait_event, left(query,40) AS query FROM pg_stat_activity WHERE wait_event_type IS NOT NULL AND backend_type='client backend';" pid | state | wait_event_type | wait_event | query
-----+--------+-----------------+------------+-------------------------------
179 | active | IPC | SyncRep | INSERT INTO t DEFAULT VALUES;
188 | active | IPC | SyncRep | INSERT INTO t DEFAULT VALUES;
(2 rows)pg_stat_replication is empty. Reads return normally and immediately.
And this is the detail that changes how you think about the incident:
$ psql -c "SELECT count(*) FROM t;" # during, then after rows_readable
---------------
2
rows_now
----------
5Cancelling a waiting backend says it in words:
$ psql -c "SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE wait_event = 'SyncRep';"WARNING: canceling wait for synchronous replication due to user request
DETAIL: The transaction has already committed locally, but might not have been replicated to the standby.
INSERT 0 1Setting synchronous_standby_names to empty and reloading restored commit
latency to 0.021 seconds immediately.
Work the evidence before reading on
- The primary is healthy and reads work. Why do only writes hang?
- Two rows visible during the hang and five afterwards. Where were the other three?
- The cancelled
INSERTreported success. Is that safe? - What does the design document mean by “highly available” here?
Root cause
One synchronous standby is a hard dependency
The waiting transactions were already durable
Two rows visible during the hang, five afterwards. The three waiting commits had been written and flushed on the primary the whole time. They were waiting only for the standby’s acknowledgement, and PostgreSQL says so when you cancel one: already committed locally, but might not have been replicated to the standby.
The design document was wrong
Resolution
Identify the wait first. A frozen application with a healthy database has several possible causes, and this one has a signature:
SELECT pid, state, wait_event_type, wait_event, left(query, 60) AS query
FROM pg_stat_activity
WHERE backend_type = 'client backend' AND wait_event = 'SyncRep';
SELECT count(*) AS standbys FROM pg_stat_replication;
SHOW synchronous_standby_names;
IPC / SyncRep with an empty pg_stat_replication is this incident and
nothing else.
If the standby will return within an acceptable window, waiting is a legitimate choice. It preserves the guarantee, which is what the configuration was asking for.
If it will not, release the requirement:
ALTER SYSTEM SET synchronous_standby_names = '';
SELECT pg_reload_conf();
Commit latency returns at once. Understand what you have just done: the cluster is asynchronous, and a primary failure from this moment can lose recently committed transactions. Record the time and set a reminder to restore it.
The durable fix is a quorum:
ALTER SYSTEM SET synchronous_standby_names = 'ANY 1 (sync1, sync2)';
SELECT pg_reload_conf();
$ pg_ctl -D /tmp/sb -m fast stop && time psql -c 'INSERT INTO t DEFAULT VALUES;' application_name | sync_state
------------------+------------
sync2 | quorum
(1 row)
INSERT 0 1
real 0m0.022sStopping the second standby made the quorum unsatisfiable and commits hung again — as they must. A quorum tolerates the failures you provisioned for and no more, which is the honest property to design around.
Verification
pg_stat_replication shows the expected standbys with the expected
sync_state — quorum for a quorum configuration, sync and
potential for a priority one:
SELECT application_name, state, sync_state, sync_priority
FROM pg_stat_replication ORDER BY application_name;
Commit latency is normal with all standbys present.
Stop one standby deliberately and confirm commits continue. That is the entire point of the change and it takes one command.
Stop the second and confirm commits hang. Not a bug to be fixed — the guarantee being honoured. A team that has seen it will not restart the primary during the next incident.
No backend sits in SyncRep during ordinary operation:
SELECT count(*) FROM pg_stat_activity WHERE wait_event = 'SyncRep';
The standby-patching runbook has been exercised end to end, including the step that removes the standby from the synchronous set before rebooting it.
Prevention
Never configure synchronous replication to a single standby. Use
ANY 1 (a, b) across at least two, so a routine reboot is survivable.
Write down which you are buying: durability or availability. A design document that says “highly available” above a single-standby synchronous configuration has not made the trade — it has hidden it.
Alert on wait_event = 'SyncRep'. It is unambiguous, and it is the
difference between a six-minute diagnosis and six minutes of guessing.
Alert on connected synchronous standbys falling below the quorum requirement. That fires before the last one goes.
Put the removal step in the standby maintenance runbook. One reload, and this incident does not happen.
Know that cancelling a SyncRep wait returns success for a transaction
that may not be on the standby, and put the warning text in the runbook
so nobody meets it for the first time at 21:40.
Rehearse losing a standby. This cluster had never had one stopped deliberately, which is why a kernel patch became an outage.
Restore the setting afterwards. A cluster left asynchronous after an incident has quietly abandoned the guarantee it was built for, and nothing will remind you.