VACUUM, autovacuum, freeze и wraparound

Рейтинг: 45.3% · 9 голосов
Подробный курс по PostgreSQL: SQL по демобазе, типы данных и JSONB, индексы, транзакции и MVCC, VACUUM, EXPLAIN и планировщик, роли и привилегии, репликация, бэкап, настройка под нагрузку и под 1С. Актуально на 2026.
Ответить
Аватара пользователя
Egor_DBA
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

VACUUM, autovacuum, freeze и wraparound

Сообщение Egor_DBA »

Оглавление курса (47)
  1. Что такое PostgreSQL: история, философия, где применяется
  2. Урок 1. Что нового в PostgreSQL 15 и далее (15 -> 16 -> 17)
  3. Установка и первый запуск: Linux, Windows, Docker
  4. psql детально: метакоманды и работа в консоли
  5. Демобаза Авиаперевозки Postgres Pro: структура и развёртывание
  6. SELECT: проекция, фильтрация, сортировка
  7. Транзакции и ACID
  8. Уровни изоляции транзакций: Read Committed, Repeatable Read, Serializable
  9. Соединения таблиц (JOIN)
  10. Агрегация: GROUP BY, HAVING, GROUPING SETS
  11. Подзапросы и CTE (WITH), рекурсия
  12. Оконные функции
  13. Изменение данных: INSERT/UPDATE/DELETE, UPSERT, MERGE
  14. Числовые и булевы типы, NULL и трёхзначная логика
  15. Строки, текст, дата и время, таймзоны
  16. Массивы, диапазоны, enum, составные типы
  17. JSON и JSONB: операторы и индексация
  18. DDL: CREATE TABLE, схемы, ALTER
  19. Ограничения целостности
  20. Представления и материализованные представления
  21. Последовательности, IDENTITY, генерируемые столбцы
  22. Индексы: B-tree и когда индекс не используется
  23. Типы индексов: Hash, GiST, SP-GiST, GIN, BRIN
  24. Продвинутые индексы: частичные, по выражению, покрывающие
  25. Обслуживание индексов и таблиц: bloat, REINDEX, CONCURRENTLY
  26. Полнотекстовый поиск
  27. Роли и пользователи
  28. Привилегии: GRANT/REVOKE
  29. Row-Level Security и безопасность данных
  30. Подключение приложения: pg_hba.conf, SSL, пулы соединений
  31. Серверное программирование: функции и процедуры
  32. Триггеры и события
  33. Архитектура PostgreSQL: процессы и память
  34. MVCC: версии строк и видимость
  35. VACUUM, autovacuum, freeze и wraparound (вы здесь)
  36. WAL, контрольные точки и долговечность
  37. Блокировки и взаимоблокировки
  38. Планировщик запросов
  39. EXPLAIN и EXPLAIN ANALYZE: чтение планов
  40. Конфигурация сервера: ключевые параметры
  41. Настройка под нагрузку и под 1С
  42. Параллельные запросы и партиционирование
  43. Резервное копирование и восстановление
  44. Репликация: потоковая и hot standby
  45. Логическая репликация и кластерные решения
  46. Внешние данные, расширения и сертификация
  47. Разбор планов запросов на explain.tensor.ru: глубокое чтение EXPLAIN ANALYZE
Урок 34. VACUUM, autovacuum, freeze и wraparound

В прошлом уроке мы разобрали MVCC и поняли, что UPDATE и DELETE в postgresql не стирают строку на месте, а оставляют мёртвые версии. Кто-то должен их собирать, иначе таблица пухнет, индексы тормозят, а счётчик транзакций однажды переполнится и сервер встанет. За уборку отвечает VACUUM. Урок про то, как работает очистка, как её автоматизирует autovacuum, что такое заморозка строк (freeze) и почему transaction ID wraparound - самая страшная авария, которую можно довести до полной остановки базы.

Изображение

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

Каждая версия строки несёт служебные поля xmin (кто создал) и xmax (кто удалил). Версия мёртвая, когда её xmax зафиксирован и она больше не видна ни одному активному снимку. Пока мёртвые версии лежат в страницах, они занимают место и заставляют сканы перебирать лишнее. Раздувание таблицы такими версиями называют bloat.

VACUUM проходит по страницам, помечает место от мёртвых версий свободным и кладёт его в карту свободного пространства (FSM), чтобы будущие вставки переиспользовали дыры. Важно: обычный VACUUM не возвращает место операционной системе и не сжимает файл, он лишь освобождает место внутри для повторного использования. Поэтому после массового DELETE размер файла не падает, но новые строки лягут в очищенные страницы.

Чтобы не сканировать всю таблицу каждый раз, postgresql держит visibility map - битовую карту по две позиции на страницу: all-visible (на странице нет версий, невидимых кому-либо) и all-frozen. VACUUM пропускает страницы all-visible и за счёт этого работает инкрементально. Та же карта включает index-only scan: если страница all-visible, данные берутся прямо из индекса, без обращения к таблице.

Теперь про заморозку. Номер транзакции (XID) - это 32-битное число, оно ходит по кругу примерно в 4 миллиарда значений. Видимость определяется сравнением "старше/моложе", и чтобы старые строки не оказались вдруг из будущего после оборота счётчика, их xmin заменяют отметкой "заморожено навсегда" - такая версия видна всем без сравнения XID. Это и есть freeze. Если запустить заморозку слишком поздно, наступает transaction ID wraparound: чтобы не показать данные из будущего, postgresql аварийно запрещает новые транзакции, и база уходит в read-only до ручного VACUUM. Доводить до этого нельзя.

Autovacuum - фоновый демон, который сам запускает VACUUM и ANALYZE по таблицам, где накопилось достаточно изменений. Порог считается как autovacuum_vacuum_threshold плюс autovacuum_vacuum_scale_factor, умноженный на число строк: по умолчанию 50 + 0.2 * reltuples, то есть примерно при 20 процентах изменённых строк. Отдельная ветка - антивраппераунд-автовакуум: он стартует принудительно, когда возраст самой старой незамороженной транзакции доходит до autovacuum_freeze_max_age (по умолчанию 200 млн), и его нельзя пропустить.

SQL и примеры

Посмотрим, сколько мёртвых версий накопила таблица и когда её чистили в последний раз. Запрос к статистике по схеме bookings демобазы Авиаперевозки:

Код: Выделить всё

SELECT relname, n_live_tup, n_dead_tup,
       last_autovacuum, last_autoanalyze
FROM pg_stat_user_tables
WHERE schemaname = 'bookings'
ORDER BY n_dead_tup DESC;
Колонка n_dead_tup - кандидаты на уборку. Спровоцируем bloat и уберём его вручную на ticket_flights (перелёты по билетам, самая крупная таблица):

Код: Выделить всё

SELECT pg_size_pretty(pg_relation_size('bookings.ticket_flights'));
UPDATE bookings.ticket_flights SET amount = amount + 0;
SELECT pg_size_pretty(pg_relation_size('bookings.ticket_flights'));
VACUUM (VERBOSE, ANALYZE) bookings.ticket_flights;
После UPDATE файл вырос: каждая строка получила новую версию, старые стали мёртвыми. VACUUM файл не уменьшит, но место внутри освободит - следующий UPDATE уже не будет так раздувать таблицу. Чтобы реально вернуть место ОС, нужен VACUUM FULL, который перезаписывает таблицу в новый файл:

Код: Выделить всё

VACUUM FULL bookings.ticket_flights;
VACUUM FULL берёт блокировку ACCESS EXCLUSIVE: таблица недоступна на чтение и запись всю операцию. На проде по живой таблице это почти всегда плохая идея, берите pg_repack.

Проверим возраст транзакций - насколько близко до wraparound:

Код: Выделить всё

SELECT relname, age(relfrozenxid) AS xid_age
FROM pg_class
WHERE relkind = 'r'
ORDER BY age(relfrozenxid) DESC
LIMIT 10;
age(relfrozenxid) - сколько транзакций прошло с момента, до которого таблица заморожена. Когда оно приближается к autovacuum_freeze_max_age, антивраппераунд-автовакуум обязан вмешаться. Настройку автовакуума можно задать на отдельную горячую таблицу, не трогая весь сервер:

Код: Выделить всё

ALTER TABLE bookings.ticket_flights
  SET (autovacuum_vacuum_scale_factor = 0.02,
       autovacuum_vacuum_cost_delay = 2);
Теперь автовакуум по этой таблице сработает уже при 2 процентах изменений, а не при 20.

В PostgreSQL 16 у VACUUM появился параметр BUFFER_USAGE_LIMIT (и глобальный vacuum_buffer_usage_limit), ограничивающий вытеснение кэша уборкой, а очистка индексов научилась идти параллельно. В PostgreSQL 17 список мёртвых TID переписали на структуру TidStore (адаптивное радиксное дерево): память на вакуум упала кратно, исчез старый лимит в 1 ГБ, индексы чаще проходятся за один заход, а прогресс в pg_stat_progress_vacuum теперь меряется в байтах (dead_tuple_bytes).

Частые грабли
  • Отключают autovacuum "чтобы не мешал нагрузке" - и через месяц получают чудовищный bloat и заморозку всего разом. Не выключайте, а настраивайте.
  • Думают, что обычный VACUUM уменьшит файл на диске. Нет: место отдаёт ОС только VACUUM FULL или pg_repack, обычный лишь освобождает его внутри.
  • Запускают VACUUM FULL на проде в часы нагрузки и ловят полную блокировку таблицы на чтение и запись.
  • Долгая открытая транзакция или брошенный replication slot держат "горизонт": autovacuum видит старые версии как ещё нужные и не убирает их, bloat растёт несмотря на работающий демон.
  • Игнорируют рост age(relfrozenxid), пока база не уйдёт в read-only по wraparound. К этому моменту лечить тяжело и долго.
  • Ставят scale_factor 0.2 для таблицы в сотни миллионов строк: 20 процентов - это десятки миллионов мёртвых версий до пробуждения автовакуума. Для крупных таблиц снижайте порог.
  • Путают ANALYZE и VACUUM: ANALYZE обновляет статистику для планировщика, но мёртвые версии не убирает.
Мини-лаба
  • В psql подключитесь к демобазе и снимите размер: SELECT pg_size_pretty(pg_total_relation_size('bookings.boarding_passes'));
  • Посмотрите n_dead_tup и last_autovacuum для этой таблицы в pg_stat_user_tables.
  • Сделайте массовый UPDATE без изменения данных: UPDATE bookings.boarding_passes SET seat_no = seat_no; и снова замерьте размер и n_dead_tup.
  • Выполните VACUUM (VERBOSE) bookings.boarding_passes; и прочитайте в выводе, сколько версий removable и сколько страниц пропущено по visibility map.
  • Сравните размер до и после: убедитесь, что обычный VACUUM файл не уменьшил.
  • Выполните VACUUM FULL bookings.boarding_passes; и сравните размер ещё раз - теперь он должен упасть.
  • Посмотрите age(relfrozenxid) и сравните со SHOW autovacuum_freeze_max_age; прикиньте запас до антивраппераунд-вакуума.
Контрольные вопросы
  • Чем мёртвая версия строки отличается от живой и по каким полям это определяется?
  • Почему обычный VACUUM не уменьшает размер файла на диске, а VACUUM FULL уменьшает, и какой ценой?
  • Как считается порог срабатывания autovacuum и что задают threshold и scale_factor?
  • Что такое freeze, зачем замораживают строки и чем грозит transaction ID wraparound?
  • Какую роль играет visibility map в ускорении VACUUM и в работе index-only scan?
  • Почему долгая открытая транзакция или брошенный слот репликации мешают autovacuum убирать мёртвые версии?
👍4 ❤️2 🔥 😄 🤔1
Аватара пользователя
wireguard2025
Сообщения: 1
Зарегистрирован: 05 июн 2026, 16:58

Re: VACUUM, autovacuum, freeze и wraparound

Сообщение wireguard2025 »

А как поймать, какая именно транзакция держит горизонт и не даёт чистить? У меня n_dead_tup растёт, а autovacuum вроде пашет. Смотреть в pg_stat_activity по backend_xmin или есть способ проще?
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
pyan23
Сообщения: 2
Зарегистрирован: 29 май 2026, 20:31

Re: VACUUM, autovacuum, freeze и wraparound

Сообщение pyan23 »

Поставил scale_factor 0.02 на горячую таблицу как в примере и bloat реально перестал расти, спасибо. Только cost_delay сначала забыл, autovacuum съедал диск, с delay полегчало.
👍3 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
MVCC: версии строк и видимость
Следующая глава →
WAL, контрольные точки и долговечность

Все главы курса «PostgreSQL: от первого запроса до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: autovacuum не успевает как настроить postgresqlнастройка postgresql под 1с и высокую нагрузку

Вернуться в «PostgreSQL: от первого запроса до продакшена»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя