Что такое VACUUM в PostgreSQL?
Зачем нужен VACUUM
PostgreSQL реализует изоляцию транзакций через MVCC (Multi-Version Concurrency Control): при UPDATE старая версия строки не перезаписывается, а помечается «мёртвой» (dead tuple), а рядом создаётся новая версия. При DELETE строка тоже лишь помечается мёртвой. Это позволяет параллельным транзакциям видеть согласованный снимок данных без блокировок чтения.
Со временем мёртвых версий накапливается всё больше — они занимают место в страницах таблицы и индексах, замедляют sequential scan и seq-сканы индексов. VACUUM — механизм, который убирает эти мёртвые версии.
Что делает VACUUM
- Помечает страницы с мёртвыми кортежами как пригодные для повторного использования (но не возвращает место ОС).
- Обновляет карту видимости (
visibility map) — битовый файл, сигнализирующий планировщику, что страница содержит только живые кортежи, видимые всем транзакциям. Это ускоряет Index Only Scan. - Обновляет карту свободного пространства (
free space map, FSM) — чтобы INSERT знал, куда вставлять новые строки. - Предотвращает Transaction ID Wraparound — одна из критических задач (см. ниже).
VACUUM vs VACUUM FULL
| Режим | Поведение | Блокировка | Когда использовать |
|---|---|---|---|
VACUUM | Помечает место свободным внутри файла таблицы | ShareUpdateExclusiveLock (DDL заблокирован, DML — нет) | Регулярное обслуживание |
VACUUM FULL | Перезаписывает таблицу целиком в новый файл | AccessExclusiveLock (таблица полностью заблокирована) | Разовое высвобождение места ОС после массового DELETE |
VACUUM FULL — тяжёлая операция: она эквивалентна CREATE TABLE AS SELECT + переименование. На больших таблицах — часы простоя. Альтернатива — расширение pg_repack, которое делает то же самое без полной блокировки.
VACUUM ANALYZE
Команда VACUUM ANALYZE совмещает сборку мусора с обновлением статистики для планировщика запросов. Устаревшая статистика — частая причина плохих планов (nested loop вместо hash join и т.п.).
Autovacuum
PostgreSQL запускает VACUUM автоматически через демон autovacuum. Ключевые параметры настройки:
-- Посмотреть текущие параметры autovacuum
SHOW autovacuum_vacuum_threshold; -- минимум мёртвых кортежей для запуска
SHOW autovacuum_vacuum_scale_factor; -- доля от размера таблицы
SHOW autovacuum_vacuum_cost_delay; -- пауза между порциями работы (throttle)
-- Переопределить параметры для конкретной таблицы (горячие таблицы)
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.01, -- чаще убираем мусор (1% вместо 20%)
autovacuum_vacuum_cost_delay = 2 -- меньше throttle
);
Transaction ID Wraparound — критическая задача
PostgreSQL использует 32-битный счётчик транзакций (XID). После ~2 миллиардов транзакций он обнуляется. VACUUM записывает relfrozenxid — границу, до которой все XID «заморожены» и считаются видимыми всем. Если VACUUM не успевает, база переходит в аварийный режим read-only и отказывается принимать запись:
-- Контролировать близость к wraparound
SELECT relname,
age(relfrozenxid) AS xid_age,
2000000000 - age(relfrozenxid) AS xids_left
FROM pg_class
WHERE relkind = 'r'
ORDER BY xid_age DESC
LIMIT 10;
-- Тревога: xid_age > 1.5 млрд → срочно запускать VACUUM FREEZE
Мониторинг VACUUM
-- Статистика autovacuum по таблицам
SELECT relname,
n_dead_tup,
n_live_tup,
last_autovacuum,
last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
Что хочет услышать интервьюер
Понимание связи VACUUM с MVCC: кандидат должен объяснить, почему мёртвые кортежи вообще появляются и почему PostgreSQL не удаляет их сразу
Разницу между VACUUM и VACUUM FULL, в том числе по уровню блокировок и применимости на production
Роль autovacuum и умение настраивать его параметры под нагрузку конкретной таблицы
Проблему Transaction ID Wraparound и её последствия — это маркер senior-уровня: кандидат должен знать, что нерабочий autovacuum способен остановить всю базу
Инструменты мониторинга: pg_stat_user_tables, pg_class.relfrozenxid, логи autovacuum
Пример: Ручной запуск VACUUM и проверка bloat
-- Обычный VACUUM: убирает мусор, не блокирует DML
VACUUM orders;
-- VACUUM с обновлением статистики планировщика
VACUUM ANALYZE orders;
-- VACUUM FULL: перезаписывает таблицу, возвращает место ОС (блокирует таблицу!)
VACUUM FULL orders;
-- VACUUM FREEZE: принудительно замораживает XID (для контроля wraparound)
VACUUM FREEZE orders;
-- Посмотреть количество мёртвых кортежей и когда последний раз убирал autovacuum
SELECT
relname AS таблица,
n_live_tup AS живых_строк,
n_dead_tup AS мёртвых_строк,
round(n_dead_tup::numeric
/ NULLIF(n_live_tup + n_dead_tup, 0) * 100, 1) AS bloat_pct,
last_autovacuum,
last_autoanalyze
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
Пример: Мониторинг Transaction ID Wraparound
-- Возраст XID каждой таблицы (критично, если > 1.5 млрд)
SELECT
schemaname,
relname,
age(relfrozenxid) AS xid_age,
2000000000 - age(relfrozenxid) AS xids_until_danger
FROM pg_class
JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
WHERE relkind = 'r'
AND schemaname NOT IN ('pg_catalog', 'information_schema')
ORDER BY xid_age DESC
LIMIT 20;
-- Если xid_age > 1.5 млрд — немедленно:
-- VACUUM FREEZE VERBOSE <таблица>;
Пример: Тонкая настройка autovacuum для горячей таблицы
-- Для таблицы с очень частыми UPDATE/DELETE снижаем порог запуска autovacuum
ALTER TABLE user_sessions SET (
-- Запускать VACUUM уже при 1% мёртвых кортежей (по умолчанию 20%)
autovacuum_vacuum_scale_factor = 0.01,
-- Минимум 100 мёртвых кортежей (по умолчанию 50)
autovacuum_vacuum_threshold = 100,
-- Уменьшить паузу между порциями — агрессивнее убираем мусор
autovacuum_vacuum_cost_delay = 2,
-- Аналогично для ANALYZE
autovacuum_analyze_scale_factor = 0.005
);
-- Проверить, применились ли параметры
SELECT reloptions
FROM pg_class
WHERE relname = 'user_sessions';
Типичные ошибки
Путают VACUUM и VACUUM FULL: думают, что обычный VACUUM возвращает место операционной системе — нет, он только освобождает страницы для повторного использования внутри файла
Отключают или игнорируют autovacuum на «тихих» таблицах, не зная о проблеме XID Wraparound, что приводит к аварийной остановке базы
Запускают VACUUM FULL на production в рабочее время, блокируя всю таблицу на часы вместо использования pg_repack
Не мониторят n_dead_tup и last_autovacuum — обнаруживают bloat только когда диск уже переполнен или запросы деградировали
Не знают о VACUUM FREEZE и его роли — считают, что autovacuum с дефолтными настройками всегда справляется с заморозкой XID


