VACUUM и autovacuum
VACUUM — это уборщик PostgreSQL: он возвращает в оборот место от мёртвых кортежей, а autovacuum делает это за вас в фоне — если ему не мешать и правильно настроить.
VACUUM — операция очистки, которая помечает занятое мёртвыми версиями место как свободное для повторного использования и обновляет служебные карты. Autovacuum — фоновый демон, который запускает
VACUUMиANALYZEавтоматически по порогам изменений.
В прошлом уроке мы научились видеть мёртвые кортежи и bloat. Теперь — как с ними бороться штатно. PostgreSQL не чистит мусор сам в момент UPDATE: уборка вынесена в отдельный процесс, чтобы не замедлять запись. Понимать VACUUM важно, потому что 90% проблем с раздуванием — это не «VACUUM не работает», а «autovacuum не успевает» или «ему мешают».
Зачем это на практике
Грамотно настроенный autovacuum — это разница между базой, которая годами работает ровно, и базой, которая раз в квартал «внезапно» раздувается и требует аварийного обслуживания. Кроме уборки мусора VACUUM решает ещё одну критическую задачу — заморозку версий для защиты от переполнения счётчика транзакций (wraparound), который при игнорировании способен остановить запись в базу целиком. Плюс ANALYZE кормит планировщик свежей статистикой, без которой он строит плохие планы. Эти три вещи — выживание, скорость и корректность планов — стоят на обслуживании.
Что именно делает VACUUM
Обычный VACUUM (без FULL) выполняет несколько задач, не блокируя обычную работу с таблицей:
- Освобождает мёртвые версии: помечает место, занятое мёртвыми кортежами, как свободное — оно будет переиспользовано будущими вставками в этой же таблице.
- Чистит индексы: удаляет из индексов ссылки на убранные версии.
- Обновляет карту видимости и карту свободного места — это ускоряет index-only scan и подсказывает, куда класть новые строки.
- Замораживает старые версии (freeze), сдвигая горизонт wraparound и защищая базу от остановки.
Важно: обычный VACUUM не отдаёт место операционной системе. Файл таблицы остаётся прежнего размера, просто внутри появляется переиспользуемое свободное пространство. Для приложения этого достаточно — рост прекращается.
VACUUM orders; -- обычная уборка, без блокировки таблицы
VACUUM (VERBOSE) orders; -- с отчётом: сколько версий убрано, сколько страниц
VACUUM (ANALYZE) orders; -- уборка + пересбор статистики одной командой
VACUUM FULL против обычного VACUUM
Когда таблица уже сильно раздута и место надо вернуть диску, обычного VACUUM мало. VACUUM FULL переписывает таблицу заново в компактный файл без пустот.
| VACUUM (обычный) | VACUUM FULL | |
| Блокировка | не мешает SELECT/INSERT/UPDATE | эксклюзивная: таблица недоступна целиком |
| Место на диске | переиспользуется внутри файла | возвращается ОС (файл сжимается) |
| Как работает | правит существующий файл на месте | пишет новую копию + новый файл, нужен запас места |
| Когда применять | штатно, постоянно | разово, при сильном bloat, в окно простоя |
VACUUM FULL orders; -- ЭКСКЛЮЗИВНАЯ блокировка: на проде только в окно обслуживания!
Поскольку VACUUM FULL кладёт эксклюзивную блокировку, на живом сервере его избегают. Для онлайнового устранения bloat без долгой блокировки чаще берут расширение pg_repack или перестроение индексов через REINDEX ... CONCURRENTLY.
ANALYZE: статистика для планировщика
VACUUM убирает мусор, а ANALYZE — отдельная задача: он собирает статистику о данных (сколько строк, как распределены значения, доля NULL, частые значения) в pg_statistic. Планировщик по ней оценивает, сколько строк вернёт условие, и выбирает между seq scan и index scan, порядком соединений и т.д. Устаревшая статистика — прямая причина «вдруг тормозящих» запросов после массовой загрузки данных.
ANALYZE orders; -- пересобрать статистику по всей таблице
ANALYZE orders (status, created_at); -- только по конкретным столбцам
-- посмотреть, когда последний раз собиралась статистика
SELECT relname, last_analyze, last_autoanalyze FROM pg_stat_user_tables;
После большой загрузки (COPY, bulk insert) полезно вызвать ANALYZE вручную, не дожидаясь autovacuum, иначе первые запросы пойдут по плану, рассчитанному на старый объём.
Autovacuum и его настройка
Демон autovacuum периодически проверяет таблицы и запускает уборку/анализ, когда накопилось достаточно изменений. Порог срабатывания VACUUM считается по формуле:
порог = autovacuum_vacuum_threshold
+ autovacuum_vacuum_scale_factor * число_строк_в_таблице
-- значения по умолчанию:
autovacuum_vacuum_threshold = 50
autovacuum_vacuum_scale_factor = 0.2 -- 20% строк
autovacuum_analyze_scale_factor = 0.1 -- 10% строк для ANALYZE
То есть по умолчанию таблица вакуумируется, когда изменилось ~20% строк. Для маленькой таблицы это нормально, а для таблицы в 100 млн строк 20% — это 20 млн мёртвых версий до первой уборки: слишком поздно. Поэтому для крупных горячих таблиц scale_factor уменьшают персонально:
-- агрессивнее убирать конкретную большую таблицу
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.02, -- 2% вместо 20%
autovacuum_vacuum_threshold = 1000,
autovacuum_analyze_scale_factor = 0.01
);
Другой частый тюнинг — скорость уборки. Autovacuum намеренно «спит» между порциями работы (cost-based delay), чтобы не грузить диск. На быстрых дисках это делает его слишком медленным, и он не успевает за нагрузкой; тогда поднимают лимит работы и убирают паузу:
-- в postgresql.conf — сделать autovacuum расторопнее
autovacuum_vacuum_cost_limit = 2000 -- больше 'бюджета' на один проход (по умолчанию 200)
autovacuum_vacuum_cost_delay = 2ms -- меньше пауза (в новых версиях по умолчанию уже 2ms)
autovacuum_max_workers = 5 -- больше параллельных уборщиков
Как это работает под капотом
Заморозка (freeze) — не просто «уборка про запас». Счётчик транзакций xid 32-битный и закольцован. Чтобы старые версии не «оказались из будущего» после прохождения круга, их xmin помечается как «заморожен» — то есть видим всем. VACUUM обязан успевать замораживать версии раньше, чем счётчик пройдёт критическую дистанцию (autovacuum_freeze_max_age, по умолчанию 200 млн). Если уборка хронически отстаёт, PostgreSQL сначала запускает «агрессивный» антивраппинговый autovacuum (его нельзя отключить порогами таблицы), а в крайнем случае переходит в режим, где запрещает новые транзакции, пока администратор не выполнит уборку. Поэтому отключать autovacuum «чтобы не мешал» — опасная идея: можно довести базу до полной остановки записи.
Частые ошибки
- Отключать autovacuum. «Он мешает нагрузке» — и через недели база раздута, а счётчик транзакций у опасной черты. Почти всегда правильнее настроить, а не выключить.
- Гонять
VACUUM FULLна проде по расписанию. Эксклюзивная блокировка останавливает работу с таблицей; для онлайна естьpg_repack. - Оставлять scale_factor 0.2 для огромных таблиц. Уборка приходит слишком поздно; крупным таблицам нужен индивидуальный, заметно меньший порог.
- Забывать
ANALYZEпосле массовой загрузки. Статистика устаревает, планировщик ошибается, запросы тормозят, хотя данные и индексы в порядке. - Душить autovacuum слишком сильным cost-delay. На быстрых дисках демон еле ползёт и не успевает за потоком изменений.
Итоги
- Обычный
VACUUMосвобождает место от мёртвых версий для переиспользования, чистит индексы, обновляет карты и замораживает версии — не блокируя работу и не отдавая место ОС. VACUUM FULLпереписывает таблицу, возвращая место диску, но берёт эксклюзивную блокировку — только в окно простоя.ANALYZEсобирает статистику для планировщика; после bulk-загрузки его полезно вызвать вручную.- Autovacuum запускается по порогу
threshold + scale_factor * строки; для больших горячих таблиц scale_factor снижают индивидуально черезALTER TABLE. - Заморозка защищает от wraparound; отключать autovacuum опасно — база может остановить запись.