Автовакуум в PostgreSQL под нагрузкой: что тормозит базу
При массовых UPDATE и DELETE автовакуум конкурирует с запросами за дисковый ввод-вывод, но отключать его опасно: растёт объём мёртвых строк и риск проблем с транзакциями. Разбираем поведение PostgreSQL 17 и что измерять перед настройкой.

В PostgreSQL 17 таблица по умолчанию становится кандидатом на автовакуум, когда число мёртвых строк превышает 50 плюс 20% от оценочного числа строк. При интенсивных UPDATE и DELETE этот порог может набираться быстрее, чем один рабочий процесс успевает убирать мусор. Итог — не только конкуренция за диск: таблица разрастается, а долгие транзакции могут мешать очистке.
Что запускает автовакуум
Автовакуум — это набор фоновых процессов: launcher проверяет, каким таблицам нужна работа, и запускает workers. Для каждой таблицы PostgreSQL отдельно решает, пора ли запускать VACUUM или ANALYZE. При стандартных настройках PostgreSQL 17 порог для VACUUM по мёртвым строкам — 50 + 20% от оценочного числа строк; для ANALYZE — 50 + 10%. Это пороги запуска, а не обещание, что очистка начнётся ровно при достижении заданного процента.
Оценка зависит от статистики, а скорость накопления строк — от нагрузки. Критерий также учитывает вставки: для VACUUM настроен отдельный порог по числу вставленных строк. Поэтому таблица с интенсивными INSERT может требовать обслуживания, даже если в ней мало UPDATE и DELETE. Значения выше — настройки по умолчанию для PostgreSQL 17; параметры можно менять глобально и для отдельных таблиц.
Запущенный VACUUM ищет версии строк, которые больше не нужны транзакциям. Он не делает таблицу компактной автоматически и обычно не возвращает занятое место операционной системе: освобождённое пространство становится доступным для повторного использования самой таблицей.
Рост числа workers не равен такому же росту скорости очистки. Если ограничение — диск или долгие транзакции, дополнительные процессы могут увеличить конкуренцию, но не устранить причину.
Сноска. MVCC — механизм PostgreSQL, который хранит версии строк, чтобы параллельные транзакции могли читать согласованные данные. После UPDATE старая версия строки не исчезает сразу: VACUUM может убрать её, когда она уже не нужна активным транзакциям.
Почему под нагрузкой очистка отстаёт
VACUUM не блокирует обычные SELECT, INSERT, UPDATE и DELETE так, как это сделал бы полный эксклюзивный захват таблицы. Но он читает и обрабатывает страницы, а значит, расходует память и дисковый ввод-вывод. PostgreSQL ограничивает эту активность механизмом стоимости: параметры vacuum cost задают, как часто процесс делает паузу. В PostgreSQL 17 стандартная задержка для автовакуума — 2 мс, а лимит стоимости по умолчанию берётся из общего vacuum_cost_limit.
Когда одновременно работают несколько autovacuum workers, общий лимит стоимости распределяется между ними. Поэтому поднять autovacuum_max_workers — не то же самое, что пропорционально увеличить пропускную способность. Можно быстрее обслуживать несколько таблиц параллельно, но одновременно сильнее нагружать дисковую подсистему. Workers тоже не обрабатывают одну таблицу параллельно: каждый занимается своей задачей.
Вторая частая причина — очистке не дают продвинуться долгие транзакции. Пока транзакция удерживает старую границу видимости данных, часть версий строк нельзя безопасно удалить. Похожий эффект могут создавать слоты логической репликации с удерживаемой границей xmin и обратная связь от standby. В такой ситуации увеличение частоты VACUUM не убирает первопричину.
Что измерять до изменения настроек
Начните с таблиц, где больше всего мёртвых строк, и сопоставьте их с частотой изменений. В PostgreSQL 17 значения в pg_stat_user_tables — оценки, а не точный подсчёт строк; проценты в запросе ниже помогают расставить приоритеты, но сами по себе не доказывают проблему.
SELECT schemaname, relname, n_live_tup, n_dead_tup,
round(100.0 * n_dead_tup /
NULLIF(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,
last_autovacuum, autovacuum_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 20;
Затем проверьте текущие работы и их фазы. Запрос показывает, обрабатывает ли VACUUM таблицу и сколько блоков он прошёл; однократный снимок не показывает скорость. Снимите несколько измерений с интервалом и сопоставьте их с нагрузкой на диск и изменением числа мёртвых строк.
SELECT pid, datname, relid::regclass AS table_name, phase,
heap_blks_scanned, heap_blks_total, heap_blks_vacuumed
FROM pg_stat_progress_vacuum;
Смотрите не на один показатель, а на картину целиком: растёт ли число мёртвых строк между циклами, как долго длится VACUUM, что происходит с задержками запросов, есть ли долгие транзакции. Счётчик autovacuum_count показывает число запусков, но не сообщает, сколько работы каждый запуск завершил.
Какие настройки менять — и какой ценой
Меняйте по одному параметру и проверяйте результат на тех же таблицах и сопоставимой нагрузке. Для крупной горячей таблицы порог запуска часто полезнее задавать отдельно: глобальное снижение порога может заставить чаще обслуживать все таблицы.
| Подход | Что может улучшить | Цена и риск |
|---|---|---|
| Снизить порог или scale factor для конкретной таблицы | VACUUM начнётся раньше, мёртвые строки будут накапливаться меньше | Больше запусков и фонового ввода-вывода; слишком частая работа тоже конкурирует с запросами |
| Увеличить число workers | Больше таблиц можно обслуживать одновременно | Растёт параллельная нагрузка; одна таблица от этого не очищается несколькими workers |
| Изменить cost delay или cost limit | Можно уменьшить паузы VACUUM или, наоборот, ограничить его активность | Меньше ограничений — выше риск конкуренции за I/O; больше пауз — дольше очистка |
У параметров есть значения по умолчанию, но рабочая настройка зависит от размера таблиц, скорости изменений и дисковой подсистемы. Перед изменением сверяйте текущие параметры именно вашего сервера PostgreSQL 17: часть настроек можно переопределить на уровне таблицы, и локальное значение изменит поведение независимо от глобального.
Где настройка не спасёт
Если число мёртвых строк остаётся высоким, а VACUUM не может удалить старые версии, ищите удерживаемую границу видимости. Проверьте долгие транзакции, слоты репликации и настройки обратной связи от standby. Увеличение числа workers в такой ситуации может добавить нагрузки, не освободив строки.
Автовакуум также не заменяет ANALYZE. Если статистика устарела, планировщик может выбирать неудачный план даже при регулярно убираемых мёртвых строках. Следите за датой последнего анализа и числом изменений с момента него; при проблемах с планами запросов отдельно проверяйте актуальность статистики.
У секционированных таблиц есть отдельная ловушка: автовакуум обслуживает секции с данными, но не запускает ANALYZE для родительской секционированной таблицы автоматически. После крупных изменений данных в секциях проверьте, не устарела ли статистика родителя, и при необходимости запланируйте анализ отдельно.
Не отключайте автовакуум как способ убрать кратковременную нагрузку. Для предотвращения переполнения счётчика транзакций PostgreSQL может запускать защитную очистку и при отключённом обычном автовакууме. Игнорировать сообщения о приближении к пределу опасно: это уже вопрос доступности базы, а не только свободного диска.
Что делать дальше
Выберите одну наиболее проблемную таблицу, снимите статистику и состояние VACUUM несколько раз во время типичной нагрузки. Если очистка не успевает, сначала проверьте, не удерживают ли старые версии транзакции или репликация; затем меняйте порог для этой таблицы либо параметры стоимости и снова измеряйте задержки запросов, темп накопления мёртвых строк и длительность обслуживания. Инвалидация кешей и перестройка индексов — отдельные задачи: их не стоит подменять настройкой автовакуума.
Антиблок-аналитик 115-ФЗ: Анализ выписок, поиск рисков блокировки счёта, инструкции по безопасным транзакциям.
Спросить за 41 ₽Источники: PostgreSQL 17: Routine Vacuuming, PostgreSQL 17: Automatic Vacuuming
Частые вопросы
Автовакуум может замедлить запросы?
Можно ли отключить автовакуум на горячей таблице?
Почему VACUUM прошёл, а размер файла почти не уменьшился?
Что делать, если мёртвые строки продолжают накапливаться?
Подойдут ли эти значения и запросы для другой версии PostgreSQL?
Почему статистика родительской секционированной таблицы устарела?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.