# Горизонт транзакции _Vacuum, freeze, wraparound · PostgreSQL Knowledge Base_ **TL;DR:** Горизонт - минимум backend_xmin по всем активным транзакциям. Версию строки можно убрать, только если она стала мёртвой раньше горизонта. Одна долгая или idle-in-transaction транзакция отодвигает горизонт назад и запрещает уборку мусора во всей базе. ## Что это В любой момент в базе есть активные транзакции. У каждой - снимок (см. [snapshot](/courses/postgres/kb/snapshot.md)), который может потребовать версии строк, существовавшие на момент её старта. Поэтому VACUUM не имеет права удалять версию, если хоть одна живая транзакция могла бы её увидеть. Граница, отделяющая «точно никому не нужно» от «ещё может понадобиться», и называется горизонтом. Технически это минимум `backend_xmin` по всем активным бэкендам. Внутри VACUUM это число зовётся removable cutoff (`OldestXmin`). ## Правило удаления Версия удаляема, если её `xmax` (см. [xmin-xmax](/courses/postgres/kb/xmin-xmax.md)) закоммичен и **строго старше** горизонта. Если `xmax` новее или равен горизонту - версия остаётся, даже если фактически уже мёртвая. В отчёте `VACUUM VERBOSE` такие версии попадают в строку `N dead but not yet removable`. ## Кто держит горизонт Горизонт двигается вперёд только когда завершаются старые транзакции. Удержать его может: - долгий аналитический запрос или транзакция `REPEATABLE READ`; - забытая транзакция в состоянии `idle in transaction`; - подготовленная (`PREPARE TRANSACTION`) и не завершённая транзакция; - долгий запрос на реплике при включённом `hot_standby_feedback` (см. [hot-standby-feedback](/courses/postgres/kb/hot-standby-feedback.md)) - он отодвигает горизонт **мастера**. ## Диагностика ```sql SELECT pid, state, backend_xmin, now() - xact_start AS xact_age, query FROM pg_stat_activity WHERE state IN ('active', 'idle in transaction') ORDER BY xact_start; ``` Самая старая транзакция вверху - вероятный держатель горизонта. Защита от забытых транзакций - `idle_in_transaction_session_timeout`. ## Почему это важно Застрявший горизонт - причина половины проблем с раздуванием. VACUUM отрабатывает, но почти ничего не удаляет (см. [vacuum](/courses/postgres/kb/vacuum.md)), мусор копится, а заодно не двигается граница заморозки (см. [freeze](/courses/postgres/kb/freeze.md)). Поэтому при жалобах на bloat первым делом ищут длинную транзакцию. ## Команды ```sql SELECT pid, state, backend_xmin FROM pg_stat_activity ORDER BY backend_xmin; ``` Кто держит горизонт: смотрим самый старый backend_xmin ```sql SET idle_in_transaction_session_timeout = '5min'; ``` Автоматически прибивать транзакции, висящие без дела ## См. также - [VACUUM и removable cutoff](/courses/postgres/kb/vacuum.md) - [Заморозка и relfrozenxid](/courses/postgres/kb/freeze.md) - [Autovacuum: пороги и bloat](/courses/postgres/kb/autovacuum.md) - [Снимок данных (snapshot)](/courses/postgres/kb/snapshot.md) - [hot_standby_feedback и vacuum мастера](/courses/postgres/kb/hot-standby-feedback.md)