Что такое VACUUM в PostgreSQL?
Для регулярной очистки — настраивайте autovacuum, используйте pg_repack.
🟢 Junior Level
VACUUM — процесс очистки PostgreSQL от “мёртвых” строк, которые остались после UPDATE и DELETE.
Простая аналогия: Представьте доску для заметок. Когда вы стираете заметку, остаётся место. VACUUM — это когда вы убираете стёртые клочки бумаги и освобождаете место для новых заметок.
Зачем нужен:
- В PostgreSQL
UPDATEне перезаписывает строку, а создаёт новую. Старая остаётся “мёртвой”. DELETEне удаляет строку физически, а помечает как удалённую.- Без VACUUM таблица будет расти бесконечно.
Пример:
-- Автоматический VACUUM (работает сам)
-- Настраивается в postgresql.conf:
-- autovacuum = on
-- Ручной запуск
VACUUM users; -- Обычный
VACUUM FULL users; -- Полный (возвращает место ОС)
VACUUM VERBOSE users; -- С подробностями
Важно:
- Обычный VACUUM НЕ возвращает место операционной системе
- Он лишь помечает место как свободное для новых данных
VACUUM FULLвозвращает место, но блокирует таблицу
🟡 Middle Level
Как MVCC создаёт мёртвые строки
-- Таблица users: 1000 строк
UPDATE users SET status = 'ACTIVE' WHERE id = 1;
-- Старая версия строки остаётся (dead tuple)
-- Новая версия создаётся
DELETE FROM users WHERE id = 2;
-- Строка помечена как удалённая (dead tuple)
-- Физически остаётся в файле
-- После 10,000 UPDATE/DELETE:
-- Живых строк: 1000
-- Мёртвых строк: 10,000 ← занимают место!
Обычный VACUUM vs VACUUM FULL
| Параметр | VACUUM | VACUUM FULL |
|---|---|---|
| Возвращает место ОС | ❌ Нет | ✅ Да |
| Блокировка | Нет (чтение/запись) | AccessExclusiveLock |
| Скорость | Быстро | Медленно |
| Place для новых данных | ✅ Освобождает | ✅ Файл сжимается |
| Production | ✅ Безопасен | ❌ Только maintenance |
Когда допустим VACUUM FULL:
- В maintenance window, когда нет нагрузки
- На реплике, временно отключённой от балансировщика
- Для одноразовой «реанимации» после массового DELETE
Для регулярной очистки — настраивайте autovacuum, используйте pg_repack.
Фазы VACUUM
1. Scanning Heap — поиск мёртвых строк
→ Пропускает "чистые" страницы через Visibility Map
2. Vacuuming Indexes — удаление ссылок из индексов
→ Может быть параллельным (PG 13+)
3. Vacuuming Heap — очистка страниц таблицы
4. Truncating — отрезание пустых страниц в конце файла
→ Единственная фаза, возвращающая место ОС
Autovacuum
Автоматический VACUUM, который запускается сам:
-- Настройки по умолчанию
SHOW autovacuum; -- on
SHOW autovacuum_vacuum_threshold; -- 50 строк
SHOW autovacuum_vacuum_scale_factor; -- 0.2 (20%)
-- Запускается, когда:
-- dead_tuples > threshold + scale_factor * total_rows
-- Для таблицы 1000 строк:
-- 50 + 0.2 * 1000 = 250 мёртвых строк → запуск
-- Для таблицы 1 млн строк:
-- 50 + 0.2 * 1,000,000 = 200,050 мёртвых строк → запуск
-- Заметьте: для больших таблиц scale_factor 20% → autovacuum запускается
-- ОЧЕНЬ редко (нужно ждать 200K dead tuples)!
Настройка для конкретных таблиц
-- Для большой таблицы: чаще и агрессивнее
ALTER TABLE orders SET (
autovacuum_vacuum_threshold = 1000,
autovacuum_vacuum_scale_factor = 0.05, -- 5% вместо 20%
autovacuum_vacuum_cost_delay = 1 -- Меньше пауза → быстрее
);
-- Для маленькой таблицы: реже
ALTER TABLE settings SET (
autovacuum_enabled = false -- Отключить (если редко меняется)
);
Типичные проблемы
- VACUUM не успевает
-- Проверка: сколько мёртвых строк SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables WHERE n_dead_tup > 100000; -- Решение: настроить агрессивнее ALTER TABLE orders SET (autovacuum_vacuum_scale_factor = 0.01); - Длинные транзакции мешают
-- VACUUM не может удалить строки, которые видит открытая транзакция -- Найти виновника SELECT pid, now() - xact_start as duration, query FROM pg_stat_activity WHERE state = 'active' ORDER BY xact_start; - VACUUM FULL заблокировал таблицу
-- ❌ В production! VACUUM FULL orders; -- Все INSERT/UPDATE/DELETE ждут! -- ✅ Альтернатива без блокировки -- pg_repack pg_repack -t orders -d mydb
🔴 Senior Level
Visibility Map (VM) оптимизация
VM — битовая карта страниц таблицы
Каждая страница = 2 бита:
- all-visible: ВСЕ строки видимы всем транзакциям
- all-frozen: ВСЕ строки заморожены (для wraparound)
VACUUM использует VM:
- Пропускает страницы с all-visible = 1
→ В 10-100 раз быстрее для "чистых" таблиц
→ Зависит от доли «чистых» страниц: если 95% all-visible → читает 5%.
Если таблица постоянно обновляется — выигрыша почти нет.
Micro-vacuum (HOT)
Это НЕ отдельный процесс. PostgreSQL удаляет старые версии из HOT-цепочек во время обычных операций, если все версии на странице стали невидимы. «Мгновенная» очистка без autovacuum.
-- Благодаря HOT (Heap Only Tuple) обновлениям
-- PostgreSQL может делать "мини-очистку" во время SELECT/INSERT
-- Если цепочка HOT версий в одной странице:
-- - Старые версии удаляются сразу
-- - Без ожидания основного VACUUM
-- Это снижает нагрузку на autovacuum
-- Особенно для таблиц с частыми UPDATE
Cost-based VACUUM
Чтобы VACUUM не “положил” диск, он считает “стоимость” операций:
Операция Стоимость
----------------------------------
Чтение из кэша 1.0
Чтение с диска 10.0
Запись 20.0
autovacuum_vacuum_cost_limit = 200 -- Лимит перед паузой
autovacuum_vacuum_cost_delay = 2ms -- Пауза после лимита
Для больших таблиц (ТБ):
-- Увеличиваем лимит → VACUUM работает быстрее
ALTER TABLE huge_table SET (
autovacuum_vacuum_cost_limit = 1000, -- Вместо 200
autovacuum_vacuum_cost_delay = 1 -- Меньше пауза
);
Parallel VACUUM (PG 13+)
-- Параллельная очистка индексов
VACUUM (PARALLEL 4) orders;
-- autovacuum тоже может использовать параллелизм
-- Для таблиц с множеством индексов → ускорение в 3-5 раз
На PG 12 и ниже VACUUM выполняется в одном потоке. Для ускорения: настройте cost-based VACUUM (увеличьте cost_limit, уменьшите delay).
Anti-wraparound VACUUM
-- Самая важная задача VACUUM — предотвращение XID Wraparound
-- age(datfrozenxid) > autovacuum_freeze_max_age (200 млн)
-- → Агрессивный VACUUM:
-- - Невозможно отменить (CANCEL не работает)
-- - Будет работать, пока не "заморозит" всё
-- - Блокирует создание новых транзакций при > 1.5 млрд
-- Мониторинг:
SELECT
datname,
age(datfrozenxid),
ROUND(100.0 * age(datfrozenxid) / 2000000000, 2) as wraparound_pct
FROM pg_database;
-- > 1.5 млрд = 75% → КРИТИЧНО!
pg_stat_progress_vacuum (PG 12+)
-- Мониторинг в реальном времени
SELECT
pid,
relid::regclass as table_name,
phase,
heap_blks_total,
heap_blks_scanned,
heap_blks_vacuumed,
ROUND(100.0 * heap_blks_scanned / NULLIF(heap_blks_total, 0), 2) as progress_pct
FROM pg_stat_progress_vacuum;
-- Phase:
-- 'scanning heap'
-- 'vacuuming indexes'
-- 'vacuuming heap'
-- 'truncating heap'
На PG 11 и ниже pg_stat_progress_vacuum недоступен. Мониторьте через
n_dead_tupв pg_stat_user_tables.
Index-Only VACUUM (PG 14+)
-- Пропуск очистки индексов, если они не нуждаются
VACUUM (INDEX_CLEANUP false) orders;
-- Экстренная очистка только Heap
-- → Быстрее, но индексы не обновляются
-- → Использовать ТОЛЬКО если индексы в порядке
На PG 13 и ниже этот параметр недоступен — VACUUM всегда чистит и heap, и индексы.
Борьба с Bloat: VACUUM FULL vs pg_repack
VACUUM FULL:
-- ❌ Блокирует таблицу (AccessExclusiveLock)
-- ❌ Пересоздаёт таблицу и ВСЕ индексы
-- ✅ Возвращает место ОС
-- ✅ Встроенный, не требует расширений
VACUUM FULL orders;
pg_repack:
-- ✅ Не блокирует (кратковременная блокировка в конце)
-- ✅ Возвращает место ОС
-- ✅ Требует установку расширения
-- Установка:
CREATE EXTENSION pg_repack;
-- Запуск из CLI:
pg_repack -t orders -d mydb
-- Как работает:
-- 1. Создаёт временную таблицу
-- 2. Копирует данные
-- 3. Ловит изменения через триггеры
-- 4. Мгновенная подмена + DROP старой
Edge Cases
- hot_standby_feedback и Bloat
-- На реплике: hot_standby_feedback = on -- Реплика просит мастер не удалять строки -- → Мастер копит dead tuples -- → Bloat на мастере! -- Решение: -- - Мониторить Bloat на мастере -- - Регулярный VACUUM на реплике - Partitioned Tables
-- VACUUM для партицированной таблицы -- → Запускается для КАЖДОЙ партиции отдельно -- Настройка на уровне партиций: ALTER TABLE orders_y2024 SET ( autovacuum_vacuum_scale_factor = 0.01 ); - Temporary Tables
-- autovacuum НЕ видит временные таблицы! -- Нужен ручной VACUUM VACUUM ANALYZE temp_import_data;
Production Experience
Реальный сценарий #1: Autovacuum не справляется
- E-commerce: 50 млн INSERT/UPDATE в день в orders
- Autovacuum: 20% scale factor → запускается редко
- Bloat: таблица 100 ГБ (должна быть 10 ГБ)
- Решение:
ALTER TABLE orders SET ( autovacuum_vacuum_threshold = 10000, autovacuum_vacuum_scale_factor = 0.01, autovacuum_vacuum_cost_limit = 1000 ); - Результат: Bloat < 20%, производительность стабильна
Реальный сценарий #2: Wraparound emergency
- База: autovacuum отключён 2 года “для производительности”
- age(datfrozenxid) = 1.8 млрд (90%)
- Логи: “WARNING: database must be vacuumed within 200M transactions”
- Решение:
- Срочный
VACUUM FREEZEна всей БД - 18 часов работы
- Включение autovacuum обратно
- Срочный
- Урок: НЕ отключайте autovacuum ГЛОБАЛЬНО. Для отдельных таблиц при bulk load — можно временно, но сразу включите обратно.
Monitoring
-- 1. Dead tuples
SELECT
relname,
n_dead_tup,
last_autovacuum,
last_vacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
ORDER BY n_dead_tup DESC;
-- 2. XID age
SELECT datname, age(datfrozenxid)
FROM pg_database
ORDER BY age DESC;
-- 3. Progress
SELECT * FROM pg_stat_progress_vacuum;
-- 4. Autovacuum настройки таблицы
SELECT
c.relname,
c.reloptions
FROM pg_class c
WHERE c.reloptions::text LIKE '%autovacuum%';
-- 5. Bloat оценка
SELECT
schemaname,
relname,
n_live_tup,
n_dead_tup,
ROUND(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) as dead_pct
FROM pg_stat_user_tables
WHERE n_live_tup > 1000
ORDER BY dead_pct DESC;
Best Practices
- НЕ отключайте autovacuum ГЛОБАЛЬНО — настраивайте. Для отдельных редко обновляемых таблиц (справочники) можно локально отключить, но мониторьте XID age.
- Мониторьте n_dead_tup — > 10% = проблема
- age(datfrozenxid) < 1.5 млрд — предотвращайте wraparound
- pg_repack вместо VACUUM FULL в production
- Увеличивайте cost_limit для больших таблиц
- Уменьшайте scale_factor для часто обновляемых таблиц
- Parallel VACUUM (PG 13+) для таблиц с множеством индексов
- hot_standby_feedback = on → мониторьте Bloat на мастере
Резюме для Senior
- VACUUM удаляет dead tuples, НЕ возвращает место ОС
- VACUUM FULL возвращает место, но блокирует таблицу
- Visibility Map оптимизирует: пропускает “чистые” страницы
- HOT делает micro-vacuum во время обычных операций
- Wraparound — реальная угроза → мониторьте age(datfrozenxid)
- pg_repack для production без блокировок
- autovacuum настраивайте индивидуально для горячих таблиц
- Длинные транзакции — главный враг VACUUM
🎯 Шпаргалка для интервью
Обязательно знать:
- VACUUM: удаляет dead tuples (помечает место свободным), НЕ сжимает файлы
- VACUUM FULL: пересоздаёт таблицу → возвращает место ОС, но AccessExclusiveLock
- Autovacuum: запускается когда dead_tuples > threshold + scale_factor × total_rows
- Visibility Map: пропускает «чистые» страницы → VACUUM в 10-100 раз быстрее
- HOT (Heap Only Tuple): micro-vacuum — удаление старых версий в одной странице
- Cost-based VACUUM: стоимость операций → пауза чтобы не «положить» диск
- Anti-wraparound VACUUM: age > 200 млн → агрессивный, CANCEL не работает
- pg_stat_progress_vacuum (PG 12+): мониторинг в реальном времени
- pg_repack: VACUUM FULL без блокировки (триггеры + подмена)
- Parallel VACUUM (PG 13+): параллельная очистка индексов
- Index-Only VACUUM (PG 14+):
VACUUM (INDEX_CLEANUP false)— только Heap
Частые уточняющие вопросы:
- «Почему autovacuum не справляется?» → scale_factor 20% слишком много для горячих таблиц
- «Как бороться с Bloat без блокировки?» → pg_repack
- «Что будет, если age(datfrozenxid) = 1.9 млрд?» → Критично! Срочный VACUUM FREEZE
- «Почему длинные транзакции мешают VACUUM?» → VACUUM не удаляет строки, которые видит транзакция
Красные флаги (НЕ говорить):
- ❌ «VACUUM возвращает место ОС» (нет, только помечает свободным)
- ❌ «autovacuum можно отключить» (Wraparound → READ ONLY!)
- ❌ «VACUUM FULL безопасен в production» (блокирует ВСЕ операции!)
- ❌ «hot_standby_feedback без последствий» (Bloat на мастере!)
Связанные темы:
- [[Как работает MVCC в PostgreSQL]] → dead tuples, Hint Bits, XID Wraparound
- [[Зачем нужен ANALYZE]] → Autovacuum запускает ANALYZE
- [[Как оптимизировать медленные запросы]] → Bloat влияет на производительность