Learning Platform
Урок 06.05 · 23 мин
Продвинутый
monitoringbloatpgstattuplepg_stat_user_tablesalerts

Все предыдущие уроки модуля были про как Postgres борется с dead tuples. Этот — про то, как заметить, что борьба идёт не так. Без мониторинга bloat растёт молча: запросы постепенно замедляются, диск медленно заполняется, и в один прекрасный день — алерт «БД упала с no space left». Хороший мониторинг ловит проблему за дни или недели до этого момента.

Инструментов два эшелона:

  1. Дешёвые, всегда доступные: pg_stat_user_tables, pg_class, pg_stat_activity. Дают приближённую картину почти в реальном времени, не нагружают базу. С них начинают.
  2. Точные, тяжёлые: extension pgstattuple. Делает полный скан страниц, возвращает реальный bloat в байтах. Запускать раз в сутки на топ-N таблиц.

Уровень 1: pg_stat_user_tables

Системная вьюшка, в которой по каждой таблице есть приближённые счётчики:

КолонкаЧто значит
n_live_tupОценка числа живых строк (обновляется ANALYZE и VACUUM)
n_dead_tupЧисло dead tuples, не убранных VACUUM
n_mod_since_analyzeСколько изменений с прошлого ANALYZE
last_vacuumВремя последнего ручного VACUUM
last_autovacuumВремя последнего autovacuum
last_analyzeВремя последнего ANALYZE
vacuum_countВсего ручных VACUUM с reset’а статистики
autovacuum_countВсего autovacuum-проходов

Главная метрика — dead ratio = n_dead_tup / (n_live_tup + n_dead_tup). На здоровой OLTP-таблице это число должно быть в районе 0.05-0.15 (5-15%). Если оно стабильно > 0.3 — bloat растёт, autovacuum не справляется.

Топовый запрос диагностики:

Топ-таблиц по числу dead tuples с расчётом dead ratio. На реальном production это первый запрос при подозрении на bloat:

PostgreSQL

В pglite после инициализации этот запрос показывает таблицы customers и orders с почти нулём dead — потому что autovacuum в pglite не работает. Но добавим UPDATE:

После 20K UPDATE на orders dead ratio резко вырастает; смотрим, как это видно через pg_stat_user_tables:

PostgreSQL

После 20K UPDATE на таблице 100K строк ожидаем dead_pct ≈ 17-20% — типичный сигнал «autovacuum давно не приходил».

Уровень 2: pgstattuple

Extension pgstattuple (поставляется в contrib-пакетах) делает то, чего pg_stat_user_tables не может: проходит по всем страницам таблицы и считает реальное распределение байтов:

КолонкаЧто значит
table_lenРазмер таблицы в байтах
tuple_countСколько живых кортежей
tuple_lenСколько байт занимают живые кортежи
tuple_percenttuple_len / table_len × 100 (полезная часть)
dead_tuple_countСколько dead tuples
dead_tuple_lenСколько байт занимают dead tuples
dead_tuple_percentdead_tuple_len / table_len × 100 (мусор)
free_spaceСколько байт «дырок» в страницах
free_percentfree_space / table_len × 100

Пример вызова:

SELECT * FROM pgstattuple('orders');

Это сканирует всю таблицу — на 100 GiB занимает минуты и читает все страницы. Поэтому на production обычно ставится ночное джоба, которая обходит топ-20 таблиц по размеру и пишет результаты в табличку для дашборда.

Для быстрой оценки без полного скана есть pgstattuple_approx (PG 9.5+):

SELECT * FROM pgstattuple_approx('orders');

Использует Visibility Map, чтобы пропускать all-visible-страницы — на холодных таблицах работает в сотни раз быстрее, с погрешностью в пределах нескольких процентов. На современных кластерах это умолчание.

В pglite extension pgstattuple не установлен по умолчанию, поэтому это упражнение мы делаем умозрительно — но в реальной БД это первый инструмент после pg_stat_user_tables показал подозрительно высокий dead ratio.

Оценочный запрос без extension

Иногда extension’ы недоступны (managed service, политика безопасности). Тогда — оценочный bloat. В сообществе используется запрос из check_postgres / pgexperts, который считает «ожидаемый» размер таблицы исходя из reltuples, средней длины кортежа и оверхедов, и сравнивает с реальным pg_relation_size.

Упрощённая версия:

Оценочный bloat на основе reltuples и pg_relation_size. Не точный, но даёт картину 'на глаз':

PostgreSQL

Множитель 100 — это «среднее число байт на строку», и его нужно заменить реальной средней длиной для вашей таблицы. Полноценные query из pgexperts используют pg_attribute для подсчёта более точного значения; они громоздкие, в этот урок их не вставим, но в production-репозитории такой запрос обычно живёт в monitoring/bloat.sql.

Эшелоны мониторинга bloat

Слева — постоянный мониторинг (cheap), справа — точные измерения (heavy). Алерт уровня 1 → расследование уровнем 2.

Уровень 1: pg_stat_user_tablesпостоянно, дёшево
Метрикиn_dead_tup, dead_ratio, last_autovacuum
Алертыdead_ratio > 30%, last_autovacuum > 24h
Частотаопрос каждую минуту, hourly агрегация
Уровень 2: pgstattuple(_approx)точно, тяжело
Метрикиреальный dead_percent в байтах
Триггерзапускается на топ-20 таблиц ночью
РешениеVACUUM FULL / pg_repack / тюнинг autovacuum

Какие алерты ставить

Минимальный набор алертов для production:

  1. n_dead_tup > 1_000_000 на пользовательской таблице. Корректировать порог под размер БД.
  2. dead_ratio > 30% на таблице с n_live_tup > 100_000. Маленькие таблицы пропускаем (там 30% — пустяк).
  3. last_autovacuum IS NULL или last_autovacuum < now() - 24h для таблицы с n_dead_tup > 100_000. Значит autovacuum не приходил — нужно расследовать.
  4. age(relfrozenxid) > 100M для любой таблицы (см. урок про wraparound). Половина default autovacuum_freeze_max_age — alert, ¾ — критикал.
  5. pg_stat_activity.state = 'idle in transaction' старше 10 минут. Это первый подозреваемый при любой проблеме с vacuum’ом.
  6. pg_replication_slots.xmin старше 1 часа. Слот держит xmin → vacuum не отгружает мусор.
  7. Скорость роста pg_database_size: если бд растёт быстрее, чем за прошлую неделю, и нет очевидного источника — bloat. Trend-alert работает лучше пороговых.

В Prometheus/Grafana мире эти алерты обычно выражаются через postgres_exporter: он экспортирует pg_stat_user_tables_n_dead_tup, pg_stat_user_tables_last_autovacuum, и pg_class_age_relfrozenxid. На них и навешиваются alertmanager rules.

Daily query: топ-bloated таблицы

В production-репозитории часто живёт скрипт, который ежедневно (cron / pg_cron) делает следующее:

  1. Берёт топ-20 пользовательских таблиц по pg_relation_size.
  2. На каждой запускает pgstattuple_approx.
  3. Пишет результаты в служебную таблицу monitoring.bloat_history.
  4. Если для какой-то таблицы dead_tuple_percent рос больше N дней подряд — алерт инженеру дежурному.

Без такого скрипта вы узнаёте о проблеме когда диск кончился. С ним — за неделю до того, как это станет проблемой.

Проверка знанийKnowledge check
У вас алерт: на orders n_dead_tup = 2_000_000, dead_ratio = 35%, last_autovacuum = 3 часа назад. Postgres 14, autovacuum в дефолте. Что делать первым шагом и что проверить?
ОтветAnswer
Первое: НЕ запускать VACUUM сразу — нужно понять причину. Шаги: (1) Проверить pg_stat_activity на наличие idle in transaction старше нескольких минут — это первая и главная причина: VACUUM работает, но не может освободить место, потому что снапшот держит xmin. SELECT state, query, xact_start, query_start, age(backend_xmin) FROM pg_stat_activity WHERE state != 'idle' ORDER BY xact_start. (2) Проверить pg_replication_slots — если есть слоты с активным xmin, они тоже блокируют. (3) Проверить, что autovacuum для orders включён (pg_class.reloptions). (4) Посмотреть pg_stat_progress_vacuum — если автовакуум как раз сейчас работает и читает большую таблицу, может всё уже нормально, просто медленно. После выяснения причины: если виновата длинная транзакция — закрыть её и повторить через 10 минут; если autovacuum просто не справляется — снизить scale_factor на этой таблице (ALTER TABLE orders SET (autovacuum_vacuum_scale_factor = 0.05)) и поднять cost_limit. Только если ничего не помогает — ручной VACUUM ANALYZE orders в окне низкой нагрузки.

Чек-лист

  • Эшелон 1 — pg_stat_user_tables: дёшево, постоянно. n_dead_tup, dead_ratio, last_autovacuum — основные метрики.
  • Эшелон 2 — pgstattuple/_approx: точно, тяжело. На топ-20 таблиц ночью.
  • Алерты: dead_ratio > 30%, last_autovacuum > 24h, age(relfrozenxid) > 100M, idle in transaction > 10 min, replication slot xmin > 1h.
  • При тревоге: сначала проверять long transactions и replication slots, потом тюнинг autovacuum, и только потом — ручной VACUUM/VACUUM FULL/pg_repack.
  • Daily script для топ-bloated: pgstattuple_approx → history → trend-alert.
  • Без мониторинга bloat не виден до момента «no space left». С мониторингом — за дни/недели.
top и htop: что показывают и как читать Системные таблицы ClickHouse: полная карта диагностики

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. В чём принципиальная разница между `pg_stat_user_tables.n_dead_tup` и результатом `pgstattuple('table')`?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5