# hot_standby_feedback и vacuum мастера _Репликация · PostgreSQL Knowledge Base_ **TL;DR:** Длинный запрос на standby читает старые версии строк. Если primary их уже почистил vacuum'ом, standby обязан либо отменить запрос (конфликт восстановления), либо отстать. hot_standby_feedback = on говорит primary не удалять то, что ещё нужно standby - ценой роста мусора на primary. ## Откуда конфликт 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](/courses/postgres/kb/transaction-horizon.md)) так, чтобы не трогать ещё нужное standby. Конфликты восстановления исчезают: vacuum на primary просто не доходит до тех версий. ## Чем платим Тем же, чем за любую долгую транзакцию. Удерживая горизонт ради запроса на standby, primary не может почистить мёртвые версии в горячих таблицах. Растёт bloat, autovacuum ходит вхолостую, `n_dead_tup` копится. Один забытый аналитический запрос на реплике может раздуть таблицу на primary так же, как `idle in transaction` на самом primary. ```sql -- на 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](/courses/postgres/kb/vacuum.md)) привязан к горизонту транзакций. Кто держит снимок (на primary или на standby с feedback), тот держит мусор. См. [streaming-replication](/courses/postgres/kb/streaming-replication.md) про сам поток WAL. ## Команды ```sql SHOW hot_standby_feedback; ``` Проверить, шлёт ли standby свой xmin обратно на primary ```sql SELECT application_name, backend_xmin FROM pg_stat_replication; ``` На primary: какой xmin удерживает каждый standby с feedback ```sql SHOW max_standby_streaming_delay; ``` Сколько standby ждёт перед отменой запроса при конфликте восстановления ## См. также - [Физическая (потоковая) репликация](/courses/postgres/kb/streaming-replication.md) - [Логическая репликация (publication/subscription)](/courses/postgres/kb/logical-replication.md) - [Горизонт транзакции](/courses/postgres/kb/transaction-horizon.md) - [VACUUM и removable cutoff](/courses/postgres/kb/vacuum.md)