Що таке 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 |
| Швидкість | Швидко | Повільно |
| Місце для нових даних | ✅ Звільняє | ✅ Файл стискається |
| 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 впливає на продуктивність