← все кластеры

Vacuum, freeze, wraparound, autovacuum

Обратная сторона MVCC: мёртвые версии надо убирать, а счётчик транзакций - не давать переполниться. Vacuum, горизонт транзакции, HOT-очистка, freeze и wraparound, настройка autovacuum. Это «убийца продакшенов»: половина инцидентов с раздуванием и остановками сервера живёт именно тут.

8 вопросов · ~35 мин чтения

#why-vacuum

juniorчасто

Зачем нужен VACUUM и что именно он делает?

Что отвечать

Каждый UPDATE и DELETE оставляет мёртвую версию строки - она больше никому не видна, но занимает место в странице. VACUUM проходит по таблице, находит версии, которые не видны ни одному живому снимку, и освобождает их слоты под переиспользование; место остаётся за таблицей, но внутри страниц появляются дырки под новые строки. Заодно он обновляет карту свободного места (FSM) и карту видимости (VM), подрезает указатели строк и продвигает заморозку. Обычный VACUUM не отдаёт место файловой системе и работает без блокировки на запись.

Что хотят услышать

Senior должен: - объяснить, что vacuum переиспользует место внутри таблицы, а не возвращает его ОС (это делает только `VACUUM FULL`) - связать «что можно убрать» с горизонтом: удаляются версии старше самого старого живого снимка - назвать побочные задачи: обновление FSM/VM, заморозка, подрезка указателей - знать, что обычный vacuum не мешает SELECT/UPDATE, берёт лишь лёгкую блокировку

Подводные камни

  • Сказать «VACUUM освобождает место на диске» - обычный vacuum переиспользует место внутри таблицы, диск отдаёт только `VACUUM FULL`
  • Думать, что vacuum убирает любые мёртвые версии - только те, что старше горизонта
  • Считать, что vacuum блокирует запись - обычный режим работает онлайн

Follow-up

  • ? Чем VACUUM отличается от VACUUM FULL по эффекту на диск?
  • ? Какие версии строк vacuum не имеет права удалить?
  • ? Зачем vacuum трогает FSM и VM?

Глубина в базе знаний

tags: vacuum, maintenance, bloatbook: postgresql_internals-17.pdf:ch6 vacuum

#transaction-horizon

intermediateчасто

Что такое горизонт транзакции и почему долгая транзакция мешает очистке?

Что отвечать

Горизонт - это номер самой старой транзакции, чей снимок ещё может понадобиться. Версию строки можно убрать, только если она стала мёртвой до горизонта: иначе её ещё кто-то имеет право увидеть. Любая долгая транзакция (открытый `BEGIN`, забытый сеанс в состоянии idle in transaction, долгий аналитический запрос на Repeatable Read) держит горизонт на месте. Пока он не двигается, vacuum видит мёртвые версии, но не имеет права их удалить - они копятся, таблица раздувается, индексы пухнут. Рогов называет это горизонтом событий: за ним всё уже зафиксировано, и чистить безопасно.

Что хотят услышать

Senior должен: - определить горизонт как самый старый нужный снимок и связать его с правом vacuum удалять версии - перечислить, что держит горизонт: долгие транзакции, idle in transaction, давние снимки на реплике с обратной связью - показать диагностику: `backend_xmin` в `pg_stat_activity`, возраст самой старой транзакции, рост `n_dead_tup` - понимать, что лечится это не настройкой vacuum, а устранением долгой транзакции

Подводные камни

  • Крутить параметры autovacuum, когда настоящая причина - незакрытая долгая транзакция держит горизонт
  • Путать «мёртвых версий много» с «их нельзя убрать» - vacuum их видит, но горизонт не пускает
  • Забыть про реплики: снимок на standby с `hot_standby_feedback` тоже сдвигает горизонт на мастере

Follow-up

  • ? Как по `pg_stat_activity` найти транзакцию, которая держит горизонт?
  • ? Почему idle in transaction опаснее активного долгого запроса?
  • ? Как реплика может застопорить очистку на мастере?

Глубина в базе знаний

tags: vacuum, horizon, bloatbook: postgresql_internals-17.pdf:ch6 vacuum

#hot-updates-cleanup

intermediateчасто

Что такое HOT-обновление и как связана с ним внутристраничная очистка?

Что отвечать

HOT (heap-only tuple) - обновление, при котором новая версия строки остаётся в той же странице и на неё не заводится записей в индексах. Условие: ни одна проиндексированная колонка не изменилась и в странице хватило места. Старая версия ссылается на новую через `t_ctid`, образуя HOT-цепочку, а указатель строки превращается в redirect. Когда место в странице кончается, срабатывает внутристраничная очистка (HOT-prune): она схлопывает цепочки мёртвых версий и освобождает место, не запуская полноценный vacuum по всей таблице. Это происходит прямо во время обычных запросов.

Что хотят услышать

Senior должен: - назвать оба условия HOT: неизменность индексируемых колонок и наличие места в странице - объяснить выгоду: нет лишних записей в индексы, меньше раздувания и нагрузки на vacuum - различить HOT-prune (локальная очистка страницы по ходу запросов) и полноценный vacuum (проход по таблице с обновлением карт) - дать рычаг: `fillfactor` ниже 100 резервирует место в странице и повышает долю HOT

Подводные камни

  • Считать, что HOT работает при любом UPDATE - меняешь индексируемую колонку, и он отваливается
  • Путать HOT-prune с vacuum - prune локален и не обновляет карту видимости целиком
  • Держать `fillfactor=100` на таблице с тяжёлыми UPDATE и удивляться низкой доле HOT

Follow-up

  • ? Чем HOT-prune отличается от полноценного VACUUM?
  • ? Как по `pg_stat_user_tables` увидеть долю HOT-обновлений?
  • ? Почему индекс по часто меняющейся колонке снижает долю HOT?

Глубина в базе знаний

tags: vacuum, hot, pruningbook: postgresql_internals-17.pdf:ch5 hot updates

#freeze-wraparound

seniorчасто

Что такое заморозка и wraparound? Чем грозит переполнение счётчика транзакций?

Что отвечать

Номер транзакции - 32 бита, и он по кругу переполняется. Видимость считается по принципу «xmin в прошлом», а «прошлое» на кольце относительно. Чтобы старые строки не стали внезапно «из будущего» и не исчезли, vacuum их замораживает: помечает как видимые всегда и забывает их реальный xmin. Заморозка двигает `relfrozenxid` таблицы. Если заморозка отстаёт и возраст таблицы подбирается к пределу, autovacuum запускает агрессивную заморозку, а на самой грани сервер уходит в защиту: перестаёт выдавать новые xid и пускает только очистку. Это и есть авария wraparound.

Что хотят услышать

Senior должен: - объяснить причину: 32-битный счётчик и относительность «прошлого» на кольце - сказать, что заморозка убирает зависимость строки от xmin и двигает `relfrozenxid` - описать защиту на грани: остановка выдачи xid, режим «только vacuum», как из неё выходят - дать мониторинг: `age(relfrozenxid)` по таблицам, `autovacuum_freeze_max_age`, тревога заранее, а не по факту остановки

Подводные камни

  • Думать, что wraparound «давно неактуален» - на больших нагрузках с долгими транзакциями он реален и роняет запись
  • Отключать autovacuum «чтобы не мешал» - так и приходят к аварийной заморозке
  • Путать заморозку (всегда-видима) с удалением - freeze не убирает строку, а фиксирует её видимость навсегда

Follow-up

  • ? Как заранее увидеть приближение wraparound по системным представлениям?
  • ? Что делает сервер на самой грани переполнения xid?
  • ? Почему `relfrozenxid` важнее, чем суммарный возраст базы?

Глубина в базе знаний

tags: vacuum, freeze, wraparoundbook: postgresql_internals-17.pdf:ch7 freezing

#autovacuum-tuning

intermediateчасто

Когда срабатывает autovacuum и какие параметры решают его поведение?

Что отвечать

Autovacuum просыпается по таймеру (`autovacuum_naptime`) и для каждой таблицы считает порог: базовое число плюс доля от размера (`autovacuum_vacuum_threshold` плюс `autovacuum_vacuum_scale_factor` умножить на число строк). Накопилось больше мёртвых версий - запускается vacuum. Отдельно следит за возрастом ради заморозки (`autovacuum_freeze_max_age`). Интенсивность душит cost-based задержка (`autovacuum_vacuum_cost_delay`/`cost_limit`), чтобы не выедать диск. На больших таблицах дефолтный scale factor 0.2 слишком велик: vacuum приходит редко и поздно, поэтому его снижают поштучно через `ALTER TABLE ... SET`.

Что хотят услышать

Senior должен: - привести формулу порога (threshold плюс scale factor от размера) и понимать, почему дефолт плохо масштабируется на большие таблицы - различить два триггера: накопление мусора и возраст ради заморозки - объяснить cost-based throttling: как autovacuum ограничивает свою нагрузку и когда его надо ускорить - дать практику: пер-табличные настройки для горячих и больших таблиц вместо глобального выкручивания

Подводные камни

  • Оставлять `scale_factor=0.2` на таблице в сотни миллионов строк - vacuum придёт слишком поздно
  • Отключать autovacuum полностью - почти гарантированный путь к раздуванию и wraparound
  • Выкручивать частоту, забыв про `cost_delay` - vacuum упрётся в троттлинг и не догонит нагрузку

Follow-up

  • ? Почему для большой таблицы дефолтный scale factor приходится снижать?
  • ? Как autovacuum ограничивает свою нагрузку на диск?
  • ? Чем порог по мусору отличается от триггера по возрасту?

Глубина в базе знаний

tags: vacuum, autovacuum, tuningbook: postgresql_internals-17.pdf:ch6 vacuum · codelibs.ru_monitoring-postgresql.pdf:autovacuum

#vacuum-vs-vacuum-full

intermediateиногда

VACUUM против VACUUM FULL: в чём разница и когда что применять?

Что отвечать

Обычный VACUUM работает онлайн: помечает мёртвые версии переиспользуемыми внутри таблицы, не блокирует чтение и запись, но не уменьшает файл на диске. VACUUM FULL переписывает таблицу в новый файл без мёртвых версий, физически уменьшает её и отдаёт место ОС, но берёт ACCESS EXCLUSIVE - таблица недоступна на всё время и нужно место под копию. Поэтому повседневная гигиена - обычный vacuum и autovacuum; VACUUM FULL это разовая операция, когда таблица уже сильно раздулась и есть окно. Альтернатива без полной блокировки - `pg_repack`.

Что хотят услышать

Senior должен: - противопоставить онлайн-vacuum (место внутри таблицы) и VACUUM FULL (новый файл, диск отдаётся ОС) - назвать цену VACUUM FULL: ACCESS EXCLUSIVE и место под копию таблицы - объяснить, что регулярный vacuum нужен именно чтобы не доводить до VACUUM FULL - знать `pg_repack`/`CLUSTER` как способы убрать раздувание с меньшей блокировкой или с переупорядочением

Подводные камни

  • Гонять VACUUM FULL по расписанию - это полная блокировка таблицы, не рутинная операция
  • Ждать от обычного vacuum уменьшения файла на диске - он этого не делает
  • Запускать VACUUM FULL без свободного места под копию - операция упадёт на середине

Follow-up

  • ? Почему VACUUM FULL требует свободного места размером с таблицу?
  • ? Когда оправдан `pg_repack` вместо VACUUM FULL?
  • ? Что меняет `CLUSTER` помимо устранения раздувания?

Глубина в базе знаний

tags: vacuum, vacuum-full, bloatbook: postgresql_internals-17.pdf:ch6 vacuum

#multixact-wraparound

seniorредко

Что такое MultiXact и почему у него свой wraparound?

Что отвечать

Когда одну строку одновременно блокируют несколько транзакций (например несколько `SELECT FOR SHARE`), в `xmax` нельзя записать один номер. Тогда заводится MultiXact - идентификатор группы транзакций, а сам список участников лежит в SLRU-каталогах `pg_multixact`. У MultiXact свой 32-битный счётчик и, значит, свой wraparound и своя заморозка (`autovacuum_multixact_freeze_max_age`). На нагрузке с активной блокировкой строк (очереди задач, FOR SHARE) MultiXact растёт быстро и иногда становится узким местом раньше обычного xid.

Что хотят услышать

Senior должен: - объяснить, зачем нужен MultiXact: несколько блокировщиков одной строки не помещаются в один `xmax` - знать, что у него отдельный счётчик, отдельный wraparound и отдельная заморозка - связать с нагрузкой: паттерны вроде FOR SHARE и очередей разгоняют рост MultiXact - уметь смотреть возраст MultiXact, а не только обычного xid, при разборе проблем с заморозкой

Подводные камни

  • Следить только за возрастом xid и пропустить переполнение MultiXact
  • Не знать, что `SELECT FOR SHARE` нескольких сессий порождает MultiXact
  • Считать, что заморозка xid заодно решает MultiXact - у него свои пороги

Follow-up

  • ? Какой паттерн нагрузки быстрее всего разгоняет MultiXact?
  • ? Где хранится список участников MultiXact?
  • ? Почему возраст MultiXact надо мониторить отдельно от возраста xid?

Глубина в базе знаний

tags: vacuum, multixact, freezebook: postgresql_internals-17.pdf:ch7 freezing

#heap-pruning

seniorиногда

Чем внутристраничная очистка отличается от полноценного vacuum?

Что отвечать

Внутристраничная очистка (page pruning) случается прямо во время обычного запроса, когда он трогает страницу: PostgreSQL схлопывает HOT- цепочки и помечает мёртвые версии переиспользуемыми в пределах одной этой страницы. Это дёшево и не требует прохода по таблице, но не трогает индексы и не обновляет карту видимости целиком. Полноценный vacuum идёт по всей таблице, чистит ссылки в индексах, обновляет FSM и VM, двигает заморозку. Prune снимает давление между запусками vacuum, но не заменяет его.

Что хотят услышать

Senior должен: - локализовать prune: одна страница, по ходу запроса, без обхода таблицы - перечислить, что prune не делает: не чистит индексы, не обновляет карты целиком, не замораживает - объяснить, что prune и HOT работают в паре и сглаживают раздувание между запусками vacuum - понимать, что полноценный vacuum всё равно нужен для индексов и заморозки

Подводные камни

  • Считать, что page pruning заменяет vacuum - он не трогает индексы и заморозку
  • Думать, что prune запускается отдельным процессом - он встроен в обычное чтение страницы
  • Ожидать от prune обновления карты видимости по всей таблице - это работа vacuum

Follow-up

  • ? Что page pruning принципиально не может сделать?
  • ? Когда именно срабатывает внутристраничная очистка?
  • ? Почему даже при активном prune таблице всё равно нужен vacuum?

Глубина в базе знаний

tags: vacuum, pruning, hotbook: postgresql_internals-17.pdf:ch5 hot updates