Откуда конфликт
Standby проигрывает WAL primary, в том числе записи vacuum'а: «эти мёртвые версии удалены, страница ужата». Но на standby в этот момент может идти долгий SELECT, которому те самые версии ещё видны по его снимку.
Получается тупик. Standby не может и проиграть запись очистки (она
выдернет строки из-под живого запроса), и бесконечно её откладывать
(тогда лаг растёт без предела). PostgreSQL выбирает: подождать
max_standby_streaming_delay, и если запрос не закончился - отменить
его. В логе standby это «canceling statement due to conflict with
recovery», код ошибки 40001.
Что делает feedback
hot_standby_feedback = on (на standby) разворачивает поток информации
назад. Standby сообщает primary свой самый старый нужный xmin - границу,
ниже которой удалять версии нельзя, потому что их читает запрос на
реплике.
Primary получает этот xmin и сдвигает свой горизонт очистки (см. transaction-horizon) так, чтобы не трогать ещё нужное standby. Конфликты восстановления исчезают: vacuum на primary просто не доходит до тех версий.
Чем платим
Тем же, чем за любую долгую транзакцию. Удерживая горизонт ради запроса
на standby, primary не может почистить мёртвые версии в горячих
таблицах. Растёт bloat, autovacuum ходит вхолостую, n_dead_tup
копится. Один забытый аналитический запрос на реплике может раздуть
таблицу на primary так же, как idle in transaction на самом primary.
-- на primary: чей backend_xmin держит горизонт
SELECT pid, backend_xmin, state, query
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY age(backend_xmin) DESC;
-- xmin, пришедший от standby, виден в строке walsender:
SELECT application_name, backend_xmin FROM pg_stat_replication;
Когда что включать
- Реплика для отчётов с долгими запросами - часто
feedback = on, чтобы запросы не отменялись. Следи за bloat на primary. - Реплика-горячий-резерв (failover, без чтения) -
feedback = off, primary чистит свободно, отменять нечего. - Компромисс без feedback - поднять
max_standby_streaming_delay, разрешив standby отставать на время долгого запроса, не трогая primary.
Это прямое следствие того, что vacuum (см. vacuum) привязан к горизонту транзакций. Кто держит снимок (на primary или на standby с feedback), тот держит мусор. См. streaming-replication про сам поток WAL.