46408930

By: Tom Sydney Kerckhove <syd@cs-syd.eu>

Tell the primary what the standby's readers still need

The standby was configured to serve reads and to stay deliberately
behind, and then left to postgres' defaults on what should happen when
recovery meets one of those reads.  The defaults are that the primary is
never told what a standby reader is holding, so it is free to vacuum
those row versions away, and that replaying the cleanup cancels the
reader thirty seconds after the conflicting record arrived.  So a read
here could be destroyed by a vacuum on the primary, through no fault of
the application, and come back as SQLSTATE 40001 or as a dropped
connection.

hot_standby_feedback stops that at the source: the standby reports the
oldest snapshot its readers need and the primary keeps those row
versions.  The base backup already takes a replication slot, which is
what the feedback is recorded against, so there was nowhere else it
needed to go.  wal_receiver_status_interval has to come down with it,
because the default of ten seconds is longer than the databases here
live, and feedback that arrives after the vacuum protects nothing.

Feedback closes the window rather than the door.  It cannot retract a
cleanup already in flight, and it says nothing about conflicts that are
not about row versions, such as a lock that replayed DDL wants.  The
delay is also measured from when the record arrived rather than from
when the query started, so a standby that is already behind can have
spent the whole budget before a query begins, and the query is cancelled
at once rather than waited for.  Recovery waiting is the right answer to
all of that here: a replica in a test suite is there to be behind, and a
test that wants it caught up says so with awaitReplica.

The regression test holds a REPEATABLE READ snapshot on the standby,
updates every row on the primary and vacuums away the versions that
snapshot needs, then reads again through the same transaction.  Read
committed would not do: it takes a new snapshot per statement and lets
the old one go, so nothing would be held across the cleanup and the
conflict would never arise.  It takes about ninety seconds without the
fix, and about one with it, because no conflict arises to spend the
delay budget on.

Feedback alone is enough for that test.  The two delays are for the
conflicts it does not cover.

Total

4m11s

Created Started Finished

Time to Start Worker time Duration Time to finish Idle
Config 1m18s 0s 0s 1m19s 1m18s
Eval 1m19s 13s 13s 1m32s 0s
Build 1m32s 0s 3m44s 5m16s 3m43s
Deploy 5m16s 13s 13s 5m30s 0s
Suite 1m18s 27s 4m11s 5m30s 5m02s

Timeline

Config Eval Build Deploy Compressed gap
0s1m20s1m30s5m20s5m30s