linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it worksскоро
  • Уроки
  • База знаний
  • Собеседование
Часть III — Очистка: vacuum, freeze, wraparound

$ глава 16 · 55 минут

Автоочистка и раздувание

За три предыдущие главы мы выяснили, что VACUUM нужен постоянно: он убирает мусор, держит горизонт, морозит старые версии. Запускать его руками после каждой нагрузки невозможно. Поэтому PostgreSQL делает это сам - демоном autovacuum, который следит за таблицами и чистит их по мере накопления мусора.

Беда в том, что autovacuum невидим, пока не сломается. Работает - и хорошо. А когда настроен неправильно, симптомы выглядят как что угодно: запросы тормозят, диск пухнет, индексы перестают помогать. И почти никто не связывает это с тем, что autovacuum не успевает.

В этой главе разберём, по какой формуле autovacuum решает «пора», как измерить раздувание (bloat) числом, а не на глаз, и почему совет «отключи autovacuum, он мешает» - один из самых дорогих в мире PostgreSQL.

16.1 Что такое раздувание

Раздувание (bloat) - это место, занятое мёртвыми версиями и пустотами, которое уже не несёт полезных данных. Оно появляется естественно: каждый UPDATE и DELETE оставляет мёртвую версию, и пока VACUUM её не уберёт, она занимает страницу.

Немного раздувания - норма и даже польза: в освободившееся место ложатся новые версии (вспомним fillfactor). Проблема начинается, когда мусора становится больше, чем живых данных. Тогда:

  • таблица занимает на диске вдвое-втрое больше, чем нужно;
  • последовательное сканирование читает кучу мёртвых страниц;
  • индексы тоже раздуваются и хуже помещаются в кеш;
  • резервные копии и репликация тащат лишние гигабайты.

Раздувание - не болезнь, а симптом: VACUUM не поспевает за темпом изменений. Лечится не разовой уборкой, а настройкой автоматики.

16.2 Формула срабатывания autovacuum

Autovacuum не чистит таблицу непрерывно - это было бы расточительно. Он ждёт, пока мёртвых версий накопится достаточно. «Достаточно» - это порог, который считается по формуле:

порог = autovacuum_vacuum_threshold
      + autovacuum_vacuum_scale_factor × reltuples

По умолчанию threshold = 50, scale_factor = 0.2. То есть таблица попадает под уборку, когда число мёртвых версий превысит 50 плюс 20% от числа живых строк. Для таблицы в миллион строк это 200 050 мёртвых версий - довольно много.

sql
SELECT relname, n_live_tup, n_dead_tup,
       current_setting('autovacuum_vacuum_threshold')::int
         + current_setting('autovacuum_vacuum_scale_factor')::float
         * n_live_tup AS av_threshold
FROM pg_stat_all_tables
WHERE relname = 'bloat_demo';

Сравнивая n_dead_tup с av_threshold, можно заранее сказать, сорвётся ли autovacuum. Логика и подбор значений - в autovacuum.

16.3 Почему 20% для большой таблицы — это много

Здесь скрыта типичная ловушка. scale_factor = 0.2 отлично работает для маленьких таблиц, но плохо масштабируется. Чем больше таблица, тем больше мусора нужно накопить до срабатывания - и тем дольше она живёт раздутой между уборками.

Для таблицы в 100 миллионов строк 20% - это 20 миллионов мёртвых версий, которые будут лежать мёртвым грузом, пока порог не возьмётся. Поэтому для крупных и горячих таблиц scale_factor снижают индивидуально:

sql
ALTER TABLE big_events SET (
  autovacuum_vacuum_scale_factor = 0.02,   -- 2% вместо 20%
  autovacuum_vacuum_threshold = 1000
);

Настройка на уровне таблицы переопределяет глобальную. Это нормальный приём: горячим таблицам - агрессивный autovacuum, спокойным - дефолтный. Один общий scale_factor на всю базу почти никогда не оптимален.

16.4 Порог по вставкам: чистка таблиц, которые только растут

Долгое время autovacuum реагировал только на мёртвые версии. Таблица, в которую только вставляют (логи, события), мёртвых версий не создаёт - и autovacuum её не трогал. А зря: её страницы не попадали в карту видимости, index-only сканы по ней не работали, и заморозка откладывалась до анти-wraparound аврала.

В PostgreSQL 13 добавили отдельный порог по вставкам:

порог = autovacuum_vacuum_insert_threshold
      + autovacuum_vacuum_insert_scale_factor × reltuples

По умолчанию insert_threshold = 1000, insert_scale_factor = 0.2. Теперь append-only таблица тоже регулярно проходит VACUUM: её страницы попадают в карту видимости и постепенно замораживаются, не дожидаясь аврала. Это одно из тех изменений, которое тихо избавило многих от периодических wraparound-страхов.

16.5 Измеряем раздувание числом

«Кажется, таблица раздулась» - не диагноз. Раздувание измеряют. Точный способ - расширение pgstattuple, которое сканирует таблицу и считает долю живых, мёртвых и свободных байт:

sql
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT * FROM pgstattuple('bloat_demo');
table_len     | 1810432
tuple_count   | 2000
tuple_percent | 22.1     -- живых данных всего 22%
dead_tuple_count   | 2000
dead_tuple_percent | 22.1
free_percent  | 51.3     -- больше половины — пустота

tuple_percent около 20% при free_percent за 50% - таблица раздута вдвое-втрое. После VACUUM свободный процент вырастет (место освободилось под вставки), но table_len не уменьшится. Чтобы вернуть место ОС, нужен VACUUM FULL или переупаковка.

Подводный камень: pgstattuple сканирует таблицу целиком. На большой таблице это дорого - полное чтение всех страниц. На проде для прикидки берут облегчённый pgstattuple_approx или оценочные запросы по pg_class. Точный pgstattuple гоняют в окне, когда таблица не под пиковой нагрузкой.

16.6 VACUUM FULL и его цена

Когда таблица уже раздута и место надо вернуть, в ход идёт VACUUM FULL. Он переписывает таблицу в новый файл без мусора и отдаёт старое место ОС. Звучит хорошо, но цена высокая:

  • берёт ACCESS EXCLUSIVE блокировку - таблица недоступна на чтение и запись всё время операции;
  • требует места на диске под вторую копию таблицы на время работы;
  • перестраивает все индексы.

На большой таблице это означает многоминутный (а то и многочасовой) простой. Поэтому в проде вместо VACUUM FULL чаще берут утилиты вроде pg_repack, которые переупаковывают таблицу почти без блокировки. Но сама правильная стратегия - не доводить до раздувания, настроив autovacuum так, чтобы мусор убирался вовремя.

16.7 Поймать autovacuum за работой

Autovacuum невидим, но за ним можно подсмотреть. Два инструмента:

Когда таблицу чистили в последний раз - в статистике:

sql
SELECT relname, last_autovacuum, autovacuum_count
FROM pg_stat_all_tables
WHERE relname = 'bloat_demo';

Что прямо сейчас делает текущий проход - в прогресс-вью:

sql
SELECT relid::regclass, phase,
       heap_blks_scanned, heap_blks_total
FROM pg_stat_progress_vacuum;

pg_stat_progress_vacuum показывает фазу (сканирование таблицы, очистка индексов, и т.д.) и прогресс по блокам. По нему видно, не застрял ли VACUUM на гигантской таблице и на какой фазе он сейчас. А в логе сервера при log_autovacuum_min_duration = 0 каждый проход оставляет подробную запись - что и сколько убрано.

16.8 Почему autovacuum=off — дорогая ошибка

Самый вредный совет в мире PostgreSQL звучит так: «autovacuum съедает ресурсы и мешает нагрузке, отключи его». Логика кажется разумной - и ведёт прямиком в катастрофу.

Что произойдёт после autovacuum = off:

  • мёртвые версии перестанут убираться - раздувание поползёт вверх;
  • горизонт заморозки замрёт - возраст таблиц начнёт расти к 200 млн;
  • в какой-то момент сработает анти-wraparound autovacuum (он запускается даже при выключенном autovacuum), но уже как аврал, на пиковой нагрузке, в самый неудобный момент;
  • если и его «оптимизировать» отключением - база однажды встанет с отказом выдавать xid, и спасёт только single-user VACUUM.

Правильный ответ на «autovacuum мешает» - не выключить его, а настроить: повысить autovacuum_max_workers, снизить autovacuum_vacuum_cost_delay, задать индивидуальные scale_factor горячим таблицам. Чем активнее база пишет, тем агрессивнее должна быть автоматика - а не наоборот.

Уроки в sandbox

lab-16.1. Глазами autovacuum: пороги и bloat

Развернём «приборную панель» autovacuum: посчитаем порог срабатывания, нагенерим мёртвых версий, поймаем уборку. Потом измерим раздувание через pgstattuple и подберём scale_factor так, чтобы таблица попадала или не попадала под автоочистку. Перед каждым шагом предскажи число.

  1. Построй запрос, который для bloat_demo считает n_dead_tup и порог threshold + scale_factor * n_live_tup.

  2. Нагенерируй мёртвые версии серией UPDATE так, чтобы перешагнуть порог; предскажи, сработает ли autovacuum.

  3. Подожди и проверь last_autovacuum в pg_stat_all_tables - autovacuum должен был отметиться.

  4. Измерь раздувание: SELECT * FROM pgstattuple('bloat_demo'); - посмотри dead_tuple_percent и free_percent.

  5. Поставь таблице autovacuum_vacuum_scale_factor = 0.01 и убедись, что порог резко снизился.

  6. Сравни table_len до и после VACUUM (не FULL) - размер файла не должен уменьшиться, а free_percent вырасти.

sandbox с автопроверкой - открыть в песочнице

Резюме

  • Раздувание (bloat) - место, занятое мёртвыми версиями и пустотами; это симптом того, что VACUUM не поспевает за темпом изменений.
  • Autovacuum чистит таблицу, когда `n_dead_tup` превысит порог `autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × reltuples` (50 и 0.2 по умолчанию).
  • `scale_factor = 0.2` плохо масштабируется: для больших таблиц это миллионы мёртвых версий между уборками, поэтому горячим таблицам ставят индивидуальный порог.
  • С PostgreSQL 13 есть порог по вставкам (`autovacuum_vacuum_insert_threshold`), благодаря которому чистятся и append-only таблицы.
  • Раздувание измеряют через `pgstattuple` (`dead_tuple_percent`, `free_percent`); на больших таблицах берут `pgstattuple_approx` из-за полного скана.
  • Обычный VACUUM освобождает место под вставки, но не уменьшает файл; вернуть место ОС умеет `VACUUM FULL` (под `ACCESS EXCLUSIVE`) или `pg_repack` почти без блокировки.
  • `autovacuum = off` ведёт к раздуванию, застрявшему горизонту и в итоге к wraparound-авралу; правильный ответ на «мешает» - настроить агрессивнее, а не выключить.

Контрольные вопросы

  1. По какой формуле autovacuum решает, что таблицу пора чистить?

    Показать ответ

    По порогу: autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × reltuples. По умолчанию это 50 + 0.2 × (число живых строк). Когда число мёртвых версий (n_dead_tup) превышает этот порог, таблица попадает в очередь на автоочистку. Для таблицы в миллион строк порог - больше 200 тысяч мёртвых версий. Значения можно переопределить на уровне отдельной таблицы через ALTER TABLE ... SET (...).

  2. Почему стандартный scale_factor 0.2 плох для больших таблиц?

    Показать ответ

    Потому что порог пропорционален размеру таблицы. 20% от миллиона - 200 тысяч мёртвых версий, а 20% от ста миллионов - уже 20 миллионов. Чем больше таблица, тем больше мусора накопится до срабатывания, и тем дольше таблица живёт раздутой между уборками - всё это время запросы читают лишние страницы. Поэтому крупным и горячим таблицам задают индивидуальный, гораздо меньший scale_factor (например, 0.01-0.02) и/или фиксированный порог.

  3. Зачем чистить таблицу, в которую только вставляют?

    Показать ответ

    Мёртвых версий она не создаёт, но VACUUM нужен ей по двум другим причинам. Во-первых, чтобы её страницы попали в карту видимости - тогда по таблице заработают index-only сканы. Во-вторых, чтобы старые версии замораживались постепенно, а не копились до анти-wraparound аврала. Поэтому в PostgreSQL 13 добавили порог по вставкам (autovacuum_vacuum_insert_threshold), и теперь append-only таблицы тоже регулярно проходят VACUUM.

  4. Как измерить раздувание таблицы и в чём подвох на проде?

    Показать ответ

    Точно - через pgstattuple: он сканирует таблицу и возвращает долю живых данных (tuple_percent), мёртвых версий (dead_tuple_percent) и свободного места (free_percent). Низкий tuple_percent при высоком free_percent означает сильное раздувание. Подвох: точный pgstattuple читает все страницы таблицы, что на большой таблице дорого. Поэтому на проде для прикидки берут pgstattuple_approx или оценочные запросы по pg_class, а точный замер делают в спокойное окно.

  5. Почему совет «отключи autovacuum, он мешает» ведёт к катастрофе?

    Показать ответ

    Отключение autovacuum останавливает уборку мусора (раздувание растёт) и заморозку (возраст таблиц ползёт к 200 млн). Рано или поздно сработает анти-wraparound autovacuum - он запускается даже при выключенном autovacuum - но уже как аврал на пиковой нагрузке. Если и его подавить, база однажды откажется выдавать новые xid и встанет; выход - VACUUM в однопользовательском режиме. Правильная реакция на «autovacuum мешает» - сделать его агрессивнее (больше воркеров, меньше задержка, индивидуальные пороги), а не выключать.

← Предыдущая15-freeze-wraparoundСледующая →17-buffer-cache
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки