PostgreSQL, одна из самых популярных реляционных баз данных с открытым исходным кодом, использует сложный механизм управления транзакциями на основе многоверсионности (MVCC). Этот подход позволяет обеспечить высокую производительность и согласованность данных, но порождает необходимость в регулярной «уборке» – процессе, известном как VACUUM. Хотя VACUUM критически важен для здоровья базы данных, но мониторинг и понимание его поведения традиционно были непростой задачей.

Зачем нужен VACUUM в PostgreSQL?
В основе работы PostgreSQL лежит механизм многоверсионности (MVCC), который при обновлении или удалении данных не стирает старые версии строк сразу, а помечает их как «мертвые». Главная задача процесса VACUUM — находить и удалять эти устаревшие строки, освобождая место на диске для повторного использования.
Не менее важна и очистка индексов. VACUUM удаляет из них ссылки на уже ненужные версии строк. Это предотвращает разрастание индексов (bloat) и помогает сохранить высокую скорость поиска данных.
VACUUM также обслуживает служебные структуры. Он обновляет карту видимости (Visibility Map), которая отмечает страницы, содержащие только актуальные данные. Это необходимо для оптимизаций, таких как Index-Only Scans, и ускоряет последующие запуски самого VACUUM. Обновляется и карта свободного пространства (Free Space Map), помогающая базе данных быстро находить место для вставки новых строк.
Критически важная функция VACUUM — это «заморозка» (freezing) старых идентификаторов транзакций. Она предотвращает потенциально катастрофическую проблему их зацикливания (Transaction ID Wraparound), помечая очень старые строки как видимые для всех. Это также позволяет безопасно очищать журнал завершённых транзакций (pg_xact).
Наконец, VACUUM собирает свежую статистику о физическом хранении данных в таблицах. Эта информация жизненно необходима оптимизатору запросов PostgreSQL для построения эффективных планов выполнения.
Что произойдёт, если VACUUM не будет работать?
Без регулярной очистки устаревшие версии строк будут накапливаться. Это приведет к разрастанию как самих таблиц, так и, что еще хуже, их индексов — явлению, известному как bloat.
Раздутые таблицы и индексы не только занимают лишнее место на диске, но и серьезно замедляют выполнение запросов. Базе данных приходится сканировать гораздо больше страниц, содержащих «мусор», чтобы найти нужные данные.
Неактуальные карты видимости и свободного пространства также снизят эффективность работы, например, делая невозможными оптимизации запросов.
Самая серьезная опасность — риск достижения предела идентификаторов транзакций (Transaction ID Wraparound). Если старые ID вовремя не «заморозить», PostgreSQL будет вынужден аварийно остановить работу для экстренной очистки всего кластера, что приведёт к длительному простою.
Кроме того, без обновления статистики оптимизатор запросов будет строить неоптимальные планы на основе устаревших данных, что дополнительно ухудшит производительность.
Таким образом, регулярная работа VACUUM абсолютно необходима для поддержания производительности, эффективного использования дискового пространства и стабильной работы PostgreSQL.
Алгоритм работы и сложности мониторинга
Стандартный процесс VACUUM (не VACUUM FULL) включает следующие шаги:
Сканирование таблицы для поиска устаревших версий строк.
Накопление информации об этих строках в памяти (ограниченной maintenance_work_mem).
При заполнении буфера или по завершении сканирования таблицы – полное сканирование каждого индекса для удаления ссылок на найденные устаревшие строки. Этот шаг может повторяться несколько раз для больших таблиц.
Удаление устаревших версий строк из самой таблицы.
Обновление VM, FSM и статистики.
Попытка усечения таблицы в конце, если это возможно.
Основные сложности, с которыми сталкивается VACUUM и его мониторинг:
Длительные транзакции. Дело в том, что очистка не может удалить строки, которые ещё видны в каких-либо активных транзакциях. Долгие транзакции удерживают «горизонт» видимости, не позволяя VACUUM удалять старые версии строк.
Большие или раздутые индексы (Bloat). Необходимость полного сканирования каждого индекса (иногда многократно за один проход VACUUM) может сделать процесс очень долгим и ресурсоемким, особенно на таблицах с интенсивными изменениями.
Ограниченные стандартные средства мониторинга:
pg_stat_progress_vacuum показывает прогресс текущего процесса VACUUM, но данные исчезают после его завершения.
Логи сервера при log_autovacuum_min_duration содержат детальную информацию, но требуют сложного парсинга и неудобны для автоматического анализа.
pg_stat_all_tables предоставляет счетчики запусков VACUUM/ANALYZE и время последнего запуска. Начиная с PostgreSQL 18, появятся поля vacuum_total_time и analyze_total_time, показывающие суммарное время, затраченное на эти операции. Однако этого все ещё недостаточно для глубокого анализа ресурсов, потребляемых на уровне отдельных объектов.
Множество параметров настройки. Существует большое количество параметров (autovacuum_max_workers, autovacuum_vacuum_cost_delay, autovacuum_vacuum_scale_factor и другие), многие из которых можно задавать на уровне отдельных таблиц. Оценить эффект от их изменения стандартными средствами довольно сложно.
Расширенные статистики VACUUM от PostgresPro
Чтобы решить проблему нехватки данных, мы разработали патч, добавляющий детальные статистики работы VACUUM. Эти статистики собирают информацию о потреблении ресурсов на уровне отдельных таблиц и индексов.
Что они дают?
Детальный мониторинг. Понимание, сколько времени, операций ввода-вывода (чтения/записи), CPU тратится на VACUUM каждой таблицы и каждого индекса.
Выявление «проблемных» объектов. Легко найти таблицы или индексы, на которые VACUUM тратит много ресурсов, например, из-за bloat.
Оценка эффективности. Анализ влияния изменений параметров настройки VACUUM на конкретные объекты.
Низкий оверхед. Большинство этих метрик PostgreSQL и так собирает внутри себя; патч лишь сохраняет их и предоставляет для анализа. Дополнительный оверхед минимален (касался в основном статистики по VM).
В версиях Postgres Pro до 17-й эти статистики были доступны через расширение pgpro_stats. Начиная с 17-й версии Postgres Pro, они интегрированы в ядро. Патч активно продвигается в международное сообщество PostgreSQL (Алёна Рыбакина ведёт эту работу), но, к сожалению, на данный момент он ещё не принят в основную ветку PostgreSQL.
Анализ реальной системы: подозрительная аномалия
Рассмотрим пример анализа данных с боевой системы клиента Postgres Pro, использующей расширенные статистики. Данные собраны с помощью инструмента pgpro_pwr.
Наблюдение 1: Графики времени работы VACUUM
Первоначальный взгляд на графики затраченного времени VACUUM за рабочую неделю не выявил явных аномалий. Однако при анализе данных за выходные (когда нагрузка от приложений ниже) проявилась интересная картина:

Вместо ожидаемых пиков или стабильного уровня наблюдалось линейное снижение общего времени работы VACUUM в течение длительных периодов. Такое монотонное изменение часто указывает на насыщение или освобождение какого-то ресурса. Пики же на графиках часто являются артефактами сбора статистики в PostgreSQL (когда длительная операция завершается и "сбрасывает" накопленные значения).
Наблюдение 2: Корреляция с вводом-выводом и кэшем
Дальнейший анализ показал:
Снижение общего времени VACUUM чётко коррелировало со снижением времени, затраченного на чтение блоков с диска.
Одновременно наблюдался рост попаданий в кэш (Shared Buffers) для операций VACUUM.



Это означало, что VACUUM со временем стал находить всё больше нужных ему блоков данных в оперативной памяти, а не читать их с диска. Но почему?
Расследование с помощью сравнительных отчётов
Чтобы разобраться, были взяты два интервала времени: «красный» (в начале периода снижения, когда чтения были высокими) и «синий» (в конце, когда чтения снизились). С помощью сравнительного отчёта pgpro_pwr были проанализированы изменения на разных уровнях.
Уровень базы данных:
Общее время VACUUM и время чтения блоков в «красном» интервале были примерно в 3 раза выше, чем в «синем».
Количество прочитанных блоков также значительно снизилось.
Важно: Общее количество блоков, обработанных VACUUM (fetched blocks), практически не изменилось! Это исключило гипотезу о том, что VACUUM просто стал делать меньше работы.

Уровень таблиц:
Анализ статистики по таблицам (pgpro_stats_vacuum_tables) позволил выявить конкретные таблицы (в примере – связанные с очередью qrtz_triggers), где эффект был наиболее выражен.
Для этих таблиц время VACUUM и количество чтений упали драматически, а процент попаданий в кэш вырос с ~60% до 100%.
Количество запусков VACUUM для этих таблиц осталось примерно тем же.


Уровень индексов:
Анализ статистики по индексам (pgpro_stats_vacuum_indexes) показал, что основная часть времени и операций чтения VACUUM для проблемных таблиц приходилась именно на обработку их индексов.
Тенденция снижения чтений и роста попаданий в кэш наблюдалась и для индексов этих таблиц.


Разгадка: Влияние фоновой нагрузки
Поскольку сама работа VACUUM (общее количество обработанных блоков, частота запусков) существенно не менялась, причина аномалии должна была лежать вне самого процесса VACUUM. Анализ статистики выполнения SQL-запросов (pgpro_stats) дал ответ:

Обнаружились периодически выполняющиеся запросы приложения, которые в «синем» интервале стали читать на 20–25% меньше блоков с диска, чем в «красном», при той же частоте выполнения. Эти запросы перестали так активно «вымывать» данные из Shared Buffers. Освободившийся «горячий» кэш стал чаще использоваться процессом VACUUM для тех же самых таблиц и индексов. VACUUM находил нужные блоки в памяти, избегая дорогостоящих дисковых чтений, что и приводило к линейному снижению времени его работы по мере освобождения кэша от нагрузки запросов приложения.
Выводы
Этот пример наглядно демонстрирует мощь детализированной статистики VACUUM:
Чувствительность к аномалиям. Расширенные статистики позволили заметить неочевидное, но систематическое изменение в поведении VACUUM.
Глубокая детализация. Возможность анализировать ресурсы на уровне отдельных таблиц и, что особенно важно, индексов, позволила точно локализовать источник изменений и понять, что основное время тратится на обработку индексов.
Понимание взаимосвязей. Анализ статистики VACUUM в комплексе со статистикой выполнения запросов позволил выявить истинную причину аномалии, которая крылась не в самом VACUUM, а в изменении характера фоновой нагрузки приложения.
Возможность детально видеть потребление ресурсов именно процессом VACUUM на уровне таблиц и индексов остается крайне полезной для глубокого анализа и тонкой настройки производительности PostgreSQL. Использование специализированных инструментов мониторинга, таких как pgpro_pwr, которые упрощают анализ статистической информации и производительности СУБД, может значительно упростить жизнь администраторам баз данных.