← Назад к вопросам

Как работает VACUUM в PostgreSQL?

2.7 Senior🔥 131 комментариев
#DevOps и инфраструктура#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

VACUUM в PostgreSQL

VACUUM — это критичный процесс очистки в PostgreSQL, который удаляет мёртвые строки (dead tuples) и восстанавливает дисковое пространство.

Почему VACUUM нужна?

PostgreSQL использует MVCC (Multi-Version Concurrency Control). Когда строка обновляется или удаляется, старая версия не стирается сразу.

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR,
    email VARCHAR
);

INSERT INTO users (name, email) VALUES ("Alice", "alice@example.com");
-- БД содержит: 1 живая строка

UPDATE users SET email = "alice.new@example.com" WHERE id = 1;
-- БД содержит:
-- 1 живая строка (новая версия)
-- 1 мёртвая строка (старая версия)

DELETE FROM users WHERE id = 1;
-- БД содержит:
-- 1 мёртвая строка (помечена как удалённая)
-- Дисковое пространство НЕ освобождено!

Как работает VACUUM

1. Маркировка мёртвых строк (Mark phase)

-- VACUUM определяет, какие версии строк больше не нужны
-- Строка мертва, если никакая активная транзакция не нуждается в старой версии

-- Пример
VACUUM users;
-- 1. Начинает сканировать таблицу
-- 2. Смотрит на xmin/xmax (transaction IDs)
-- 3. Если xmax < min_active_transaction, то строка мертва
-- 4. Помечает как removable

2. Удаление мёртвых строк (Removal phase)

-- Физически удаляет помеченные строки со скоростью блока
VACUUM (VERBOSE) users;
-- Вывод:
-- VACUUM: pages: 0 removed, 10 remain, 5 skipped due to pins (approximately 48 live rows, 2 dead rows)

3. Обновление индексов

-- VACUUM также очищает индексы
-- Если в индексе есть указатели на мёртвые строки — они удаляются

CREATE INDEX idx_users_email ON users(email);
VACUUM users;
-- Индекс автоматически очищается

4. Обновление видимости (Visibility map)

-- PostgreSQL ведёт visibility map для быстрого поиска живых строк
VACUUM users;
-- Обновляет карту видимости, помечая чистые блоки

Типы VACUUM

1. VACUUM обычный (Non-aggressive)

VACUUM users;
-- Удаляет только очень старые мёртвые строки
-- Использует xmin из committed transactions
-- Более быстрый, меньше блокирует

2. VACUUM FULL

VACUUM FULL users;
-- Полное сжатие таблицы
-- Перепаковывает блоки, перемещая живые строки
-- Освобождает место для ОС
-- ДОЛГИЙ и БЛОКИРУЕТ сильно (требует ACCESS EXCLUSIVE)

3. VACUUM ANALYZE

VACUUM ANALYZE users;
-- Вакуум + обновление статистики для плана запросов
-- Очень полезно после больших изменений данных

Автоматическое VACUUM (Autovacuum)

-- PostgreSQL автоматически запускает VACUUM
-- Параметры в postgresql.conf

autovacuum = on  -- Включено по умолчанию
autovacuum_naptime = 1min  -- Проверять каждую минуту
autovacuum_vacuum_threshold = 50  -- Если > 50 мёртвых строк
autovacuum_analyze_threshold = 50

-- Просмотр активных autovacuum процессов
SELECT * FROM pg_stat_activity WHERE query LIKE "%VACUUM%";

-- Статистика вакуума
SELECT relname, n_dead_tup, last_vacuum, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;

Пример: мониторинг и запуск VACUUM

import psycopg2
from psycopg2.extras import execute_values
import time

def monitor_vacuum_status(conn):
    """Мониторит статус VACUUM"""
    with conn.cursor() as cur:
        cur.execute("""
            SELECT
                relname,
                n_dead_tup,
                n_live_tup,
                round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) as dead_ratio,
                last_vacuum,
                last_autovacuum
            FROM pg_stat_user_tables
            WHERE n_dead_tup > 1000  -- Много мёртвых строк
            ORDER BY n_dead_tup DESC;
        """)
        
        results = cur.fetchall()
        for table, dead, live, ratio, last_manual, last_auto in results:
            print(f"{table}:")
            print(f"  Dead tuples: {dead}")
            print(f"  Live tuples: {live}")
            print(f"  Dead ratio: {ratio}%")
            print(f"  Last VACUUM: {last_manual}")
            print(f"  Last AUTOVACUUM: {last_auto}")
            
            if ratio > 30:  # Если более 30% мертво
                print(f"  -> Требуется явный VACUUM FULL")

def manual_vacuum(conn, table_name, full=False):
    """Запускает ручной VACUUM"""
    with conn.cursor() as cur:
        vacuum_cmd = "VACUUM FULL ANALYZE" if full else "VACUUM ANALYZE"
        print(f"Запускаю: {vacuum_cmd} {table_name}")
        
        start = time.time()
        cur.execute(f"{vacuum_cmd} {table_name};")
        conn.commit()
        
        elapsed = time.time() - start
        print(f"Завершено за {elapsed:.2f} сек")
        
        # Проверяем результат
        cur.execute("""
            SELECT n_dead_tup, n_live_tup
            FROM pg_stat_user_tables
            WHERE relname = %s;
        """, (table_name,))
        
        dead, live = cur.fetchone()
        print(f"После VACUUM: {dead} мёртвых, {live} живых строк")

# Использование
conn = psycopg2.connect("dbname=mydb user=postgres")
monitor_vacuum_status(conn)
manual_vacuum(conn, "users", full=False)
manual_vacuum(conn, "large_table", full=True)

Проблемы с VACUUM

1. VACUUM дольше обычно

-- Может быть, таблица очень большая или очень "грязная"
-- Решение: попытайтесь разделить на части

VACUUM (ANALYZE, VERBOSE) large_table;  -- Медленно

-- Лучше:
-- 1. Увеличить работники: max_parallel_workers_per_gather
-- 2. Запустить в off-peak время
-- 3. Использовать VACUUM FULL только если критично

2. Autovacuum блокирует запросы

-- Autovacuum может блокировать SELECT/UPDATE
-- Решение:

-- Увеличить задержку autovacuum
autovacuum_vacuum_cost_delay = 50ms
autovacuum_vacuum_cost_limit = 1000  -- мс

-- Отключить autovacuum для таблицы во время peak
ALTER TABLE users SET (
    autovacuum_enabled = false
);

-- Позже включить обратно
ALTER TABLE users SET (
    autovacuum_enabled = true
);

3. Transaction ID wraparound

-- PostgreSQL использует 32-bit transaction IDs
-- После 2 млрд транзакций может наступить wraparound
-- VACUUM помогает избежать этого

-- Просмотр текущего txid
SELECT txid_current();

-- Если близко к wraparound — VACUUM FREEZE срочно
VACUUM FREEZE users;  -- Помечает все строки как очень старые

Best Practices

-- 1. Оставить autovacuum включенным (default)
-- 2. Мониторить n_dead_tup
SELECT * FROM pg_stat_user_tables WHERE n_dead_tup > 10000;

-- 3. VACUUM FULL только когда критично
-- 4. Запускать во время низкой активности
-- 5. Для высоконагруженных таблиц настроить параметры

ALTER TABLE users SET (
    autovacuum_vacuum_scale_factor = 0.01,  -- 1% вместо 10%
    autovacuum_analyze_scale_factor = 0.005  -- 0.5%
);

-- 6. Мониторить pg_stat_progress_vacuum (PG 13+)
SELECT * FROM pg_stat_progress_vacuum;

Сравнение операций

ОперацияСкоростьБлокировкаИспользованиеКогда
VACUUMБыстроСлабаяЧастоеАвтоматическое, регулярное
VACUUM FULLМедленноСильнаяРедкоеПосле больших удалений
VACUUM ANALYZEБыстроСлабаяЧастоеПосле изменений данных
VACUUM FREEZEСреднееСредняяРедкоеБлизко к wraparound

VACUUM — это жизненно важная часть администрирования PostgreSQL. Без неё таблицы разбухают и производительность падает.