Почему VACUUM зависает на фазе индексов в PostgreSQL 15 и как это исправить

    Ситуация, когда VACUUM в PostgreSQL 15 на большой таблице (2.4 ТБ) зависает в фазе vacuuming indexes на несколько суток, знакома многим администраторам баз данных. Это типичная проблема для сильно фрагментированных таблиц с крупными индексами. В статье разберём причины и дадим конкретные шаги для решения.

    Почему VACUUM застревает на индексах

    Фаза vacuuming indexes - самая ресурсоёмкая часть процесса вакуума. Она требует последовательного обхода каждого индекса и удаления ссылок на мёртвые строки. Если индекс большой (например, первичный ключ 690 ГБ или составной индекс 800 ГБ), а количество мёртвых кортежей достигает лимита max_dead_tuples (в вашем случае 178 956 969), вакуум может зависнуть, так как он вынужден многократно перечитывать одни и те же страницы.

    Почему стандартные настройки не помогают

    Увеличение maintenance_work_mem до 50 ГБ не дало эффекта, потому что память используется в основном для хранения битовой карты мёртвых кортежей, а не для ускорения обхода индекса. vacuum_cost_delay = 0 убирает задержки, но не решает проблему фрагментации. Даже перестроение индексов не помогло - это указывает на то, что проблема в механизме очистки, а не в самих индексах.

    Как корректно очистить таблицу вместе с индексами

    Шаг 1. Принудительная очистка без индексов

    Запустите VACUUM (INDEX_CLEANUP OFF, TRUNCATE OFF) - это позволит быстро обработать основную таблицу, не тратя время на индексы. После этого все мёртвые строки будут помечены как свободные, но индексы останутся с «мусором».

    Шаг 2. Параллельная очистка индексов

    Используйте REINDEX INDEX CONCURRENTLY для каждого индекса по очереди. Это создаст новую версию индекса без блокировки записи в таблицу. После завершения старый индекс удалится автоматически.

    REINDEX INDEX CONCURRENTLY table_pkey;
    REINDEX INDEX CONCURRENTLY idx_composite;

    Шаг 3. Финальный VACUUM FULL

    После перестроения индексов выполните VACUUM FULL - он компактизирует таблицу и вернёт место на диске. Однако учтите, что это заблокирует таблицу на время выполнения.

    Какие ещё настройки стоит проверить

    • work_mem - увеличьте до 256-512 МБ для ускорения сортировок при REINDEX.
    • max_parallel_workers_maintenance - установите 2-4 для параллельного построения индексов.
    • autovacuum_vacuum_scale_factor - уменьшите до 0.01, чтобы вакуум запускался чаще, но порциями.
    • vacuum_freeze_min_age - уменьшите до 10000, чтобы избежать накопления мёртвых строк.

    Почему затык именно на этой таблице

    Скорее всего, таблица содержит большое количество обновлений (UPDATE) или удалений (DELETE), что приводит к накоплению мёртвых кортежей. Также возможно, что индексы имеют низкую селективность (например, составной индекс на int, timestamp), из-за чего вакуум вынужден обрабатывать много страниц. Рекомендуется настроить autovacuum на эту таблицу отдельно, задав более агрессивные параметры.

    Профилактика для будущего

    Чтобы VACUMM больше не зависал, внедрите следующие практики: регулярно запускайте REINDEX по расписанию (например, раз в неделю), настройте autovacuum с учётом размера таблицы, и рассмотрите секционирование - разбейте таблицу по дате или другому ключу, чтобы каждый сегмент был меньше 500 ГБ.

    Часто задаваемые вопросы