MVCC (Multi-Version Concurrency Control)

  • При UPDATE или DELETE старая версия строки (tuple) не удаляется физически.
  • Она помечается как «мёртвая» (t_xmax = XID_удалившей_транзакции).
  • Новые транзакции её не видят, но она остаётся на диске.

Проблема: со временем таблица раздувается (bloat) → растёт размер на диске, замедляются сканирования.

VACUUM - находит мёртвые tuple’ы и помечает их пространство как свободное для повторного использования.

VACUUM

1. Сканирует таблицу постранично

  • Читает каждую 8 КБ-страницу таблицы (heap).
  • Для каждой страницы:
    • Проверяет каждый tuple.
    • Определяет, жив ли он с точки зрения текущего snapshot’а и oldestXmin.

2. Определяет, какие tuple’ы можно удалить

Tuple считается мёртвым и подлежащим очистке, если:

  • t_xmax ≠ 0 (был удалён/обновлён),
  • И транзакция t_xmax завершена,
  • И никакая активная транзакция не может его видеть (т.е. t_xmax < oldestXmin).

oldestXmin — это самый старый XID среди всех активных транзакций в кластере (берётся из ProcArray).

3. Помечает мёртвые tuple’ы как свободное пространство

  • Внутри страницы:
    • Удаляет указатели на мёртвые tuple’ы из line pointer array.
    • Обновляет free space map (FSM) — карту свободного места.
  • Физически данные не стираются, но пространство становится доступным для новых строк.

FSM — Free Space Map (Карта свободного места)

  • Битовая/байтовая карта, которая показывает, сколько свободного места есть на каждой странице таблицы или индекса.
  • Позволяет PostgreSQL быстро найти страницу с достаточным местом для вставки новой строки — без сканирования всей таблицы.

Где хранится?

  • Отдельный файл рядом с таблицей:
    12345_fsm (где 12345 — RELFILENODE таблицы).

  • В памяти: кэшируется в shared_buffers.

  • Обновляется при INSERT, UPDATE, DELETE и VACUUM.

  • Не хранит точное расположение свободного места — только объём.

  • Если FSM повреждена — PostgreSQL перестраивает её автоматически.

  • если видите частые «heap page full» в логах — возможно, FSM не успевает обновляться (редко, но бывает при экстремальной нагрузке).

4. Обновляет служебные структуры

  • Visibility Map (VM):
    • Если вся страница содержит только видимые для всех строки, устанавливает бит «all-visible».
    • Это позволяет пропускать страницу при будущих VACUUM’ах и ускоряет VACUUM и SELECT.
  • Статистика:
    • Обновляет счётчики в pg_stat_user_tables: n_dead_tup, n_live_tup.

Visibility Map — (Карта видимости) - Битовая карта, где каждый бит соответствует одной странице таблицы.

  • Бит = 1, если все строки на этой странице видимы для всех транзакций (т.е. нет мёртвых или невидимых строк).

  1. Ускоряет VACUUM: - Если бит = 1, VACUUM пропускает эту страницу — не нужно читать её содержимое.
  2. Ускоряет SELECT при index-only scan:
  • Если индекс содержит все нужные колонки и страница помечена как «all-visible»,
    → PostgreSQL не читает саму таблицу (heap), а берёт данные только из индекса.

Где хранится?

  • Отдельный файл: 12345_vm
  • Также кэшируется в shared_buffers.

Как обновляется?

  • При VACUUM — устанавливает бит в 1, если страница полностью видима.
  • При UPDATE/DELETEсбрасывает бит в 0, потому что на странице появилась невидимая строка.

В production почти всегда используется обычный VACUUM.
VACUUM FULL — только для экстренных случаев (огромный bloat, нехватка диска).

VACUUM — это “уборщик мусора” MVCC, который:

  • Находит мёртвые версии строк,
  • Помечает их место как свободное,
  • Обновляет служебные карты (FSM, VM),
  • Позволяет PostgreSQL повторно использовать место внутри файлов,
  • Не блокирует работу приложения (в обычном режиме).

autoVACUUM

PostgreSQL автоматически запускает VACUUM через Autovacuum:

  • Autovacuum Launcher — следит за таблицами.
  • Autovacuum Worker — выполняет VACUUM и ANALYZE.

Запускается по формуле

мёртвые_строки autovacuum_vacuum_threshold + (autovacuum_vacuum_scale_factor * общее_число_строк)
# Пример: в таблице 10 000 строк → `VACUUM` запустится при ~2050 мёртвых строк.
 
# Когда именно autovacuum решит, что таблица нуждается в очистке мёртвых строк
autovacuum_vacuum_threshold = 50
	# Обсолютный минимум мёртвых строк, при котором может сработать `VACUUM`. Гарантирует, что даже очень маленькие таблицы будут очищаться.
	# Позволяет масштабировать порог под размер таблицы
	
autovacuum_vacuum_scale_factor = 0.2
	# Доля от общего числа строк, которая добавляется к порогу.
	# Позволяет масштабировать порог под размер таблицы.
	#                  Живых строк  Порог мёртвых строк
	#	Маленькая      1 000        `50 + 0.2 × 1000 = 250`
	#	Средняя        100 000      `50 + 0.2 × 100 000 = 20 050`
	#	Большая        10 000 000   `50 + 0.2 × 10 000 000 = 2 000 050`
	# тем реже запускается `VACUUM`, но больше строк очищается за раз
 

Проверить текущие значения:

-- Глобальные настройки
SHOW autovacuum_vacuum_scale_factor;
SHOW autovacuum_vacuum_threshold;
 
-- Для конкретной таблицы
SELECT relname,
       reloptions
FROM pg_class
WHERE relname = 'orders';

По умолчанию:

  • autovacuum_vacuum_threshold = 50
  • autovacuum_vacuum_scale_factor = 0.2 (20%)

-- Как проверить, что VACUUM работает?
-- Статистика по таблицам
SELECT schemaname, tablename,
       n_tup_ins, n_tup_upd, n_tup_del,
       n_dead_tup,
       last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
 
-- Прогресс выполнения VACUUM
SELECT * FROM pg_stat_progress_vacuum;