Hi Troels, You are hitting something called a recovery conflict. This means a read query on the replica conflicts with incoming WAL changes from the primary on certain objects.
There are 3 main types of conflicts: Snapshot conflict: The primary's VACUUM removes tuple versions that a transaction on the replica still needs to see. If the replica replays that VACUUM-related WAL record, the read transaction would no longer see the correct data. Postgres must either pause WAL replay or cancel the read query. Enabling hot_standby_feedback mitigates most of these cases because the primary delays vacuuming tuple versions visible to the standby. Lock conflict: The primary requires an AccessExclusiveLock on an object (usually a table), typically during DDL operations or the table-truncation phase of a VACUUM. If a read query is actively accessing that table, replay cannot proceed. Buffer pin conflict: An operation on the primary needs an exclusive cleanup lock (sometimes called a super-exclusive lock) on a specific buffer pageāmost often during b-tree or heap index vacuuming. while a query on the replica holds a pin on that buffer (common during heavy nested loop joins). In all of these cases, Postgres tries to balance replication lag against read query execution. This is where max_standby_streaming_delay comes into play. If set too low, your queries will frequently be canceled. If set too high, replication lag will increase. If set to -1 (wait indefinitely), you run the risk of the primary accumulating excess WAL until it exceeds wal_keep_size or invalidates the replication slot via max_slot_wal_keep_size, which would break replication entirely. This is a trade off to consider. Thanks, Shihao
