kb/vacuum
Очистка мёртвых версий: внутристраничный prune, HOT-обновления, горизонт транзакции, removable cutoff, заморозка relfrozenxid и колесо XID. Почему долгая транзакция держит мусор и как autovacuum решает, что пора.
Autovacuum чистит таблицу, когда мёртвых версий накопится больше порога threshold + scale_factor × reltuples (50 и 0.2 по умолчанию). scale_factor 0.2 плохо масштабируется на большие таблицы, поэтому горячим задают индивидуальные пороги. autovacuum=off ведёт к bloat и wraparound.
HOT (Heap-Only Tuple) - обновление, которое не пишет в индексы: оно возможно, если ни одна индексируемая колонка не изменилась и новая версия помещается в ту же страницу. fillfactor оставляет в странице резерв, чтобы обновления оставались HOT.
Когда одну строку блокируют несколько транзакций сразу (FOR SHARE, внешние ключи), вместо одного xid в xmax пишется multixact - ссылка на список участников. Это второй 32-битный счётчик со своим кругом, переполнением и границей relminmxid, за которым следят отдельно.
VACUUM удаляет мёртвые версии из таблицы и индексов, обновляет Free Space Map и Visibility Map, продвигает горизонт заморозки. Удаляет только версии старше removable cutoff. Обычный VACUUM не уменьшает файл - это делает VACUUM FULL под эксклюзивной блокировкой.
xid - 32-битное число, счётчик переполняется примерно через 4 млрд транзакций. Сравнения видимости идут по кругу: версия старше текущего на ~2 млрд «переворачивается» в будущее и молча исчезает. Защита - заморозка и лестница из autovacuum, failsafe, WARNING и single-user.
Prune - лёгкая уборка одной страницы: убирает мёртвые версии внутри неё, не трогая индексы. Запускается оппортунистически при чтении или записи страницы. Корень HOT-цепочки не удаляет, а превращает в redirect-указатель.
Горизонт - минимум backend_xmin по всем активным транзакциям. Версию строки можно убрать, только если она стала мёртвой раньше горизонта. Одна долгая или idle-in-transaction транзакция отодвигает горизонт назад и запрещает уборку мусора во всей базе.
Заморозка помечает старую версию как видимую всем безусловно (бит HEAP_XMIN_FROZEN), выводя её из круговых сравнений и защищая от wraparound. relfrozenxid - граница заморозки таблицы; age(relfrozenxid) показывает её возраст в транзакциях.