40.1 Два сорта статистики, которые нельзя путать
Представления pg_stat_* делятся на две природы, и смешать их -
самая частая ошибка при диагностике.
- Мгновенный снимок - что происходит прямо сейчас.
pg_stat_activity,pg_locks,pg_stat_replication,pg_stat_progress_vacuum. Читаешь - видишь текущее состояние сервера в этот момент. - Накопленные счётчики - сумма с момента последнего сброса
статистики.
pg_stat_user_tables,pg_statio_user_tables,pg_stat_database,pg_stat_statements. Число само по себе мало о чём говорит.
Главное следствие: по накопленным счётчикам нельзя сказать «сейчас плохо». Можно сказать только «между двумя замерами выросло вот это». Смысл накопленного счётчика - в дельте за интервал, а не в абсолютном значении. Снял замер, подождал, снял второй, вычел - вот это и есть нагрузка за период.
40.2 pg_stat_activity: кто что делает сейчас
Первый экран при «база тормозит». Строка на каждое соединение: состояние, текущий запрос, время начала транзакции, чего backend ждёт.
SELECT pid, state, wait_event_type, wait_event,
now() - xact_start AS xact_age, left(query, 60) AS q
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY xact_age DESC NULLS LAST;
Состояния, которые читаешь сразу:
active- backend прямо сейчас выполняет запрос;idle- простаивает между запросами, безвреден;idle in transaction- открыл транзакцию и ничего не делает. Вот это тревога: такой backend держит горизонт и мешает vacuum, не выполняя никакой полезной работы. Большойxact_ageу такой строки- прямой кандидат на причину растущего bloat.
40.3 Здоровье таблиц: нужен ли vacuum, берётся ли индекс
pg_stat_user_tables отвечает на два повседневных вопроса. Первый -
нужен ли таблице vacuum: n_live_tup против n_dead_tup и когда
приходил last_autovacuum. Второй - читается ли таблица сканом или
индексом: seq_scan против idx_scan.
SELECT relname, n_live_tup, n_dead_tup,
seq_scan, idx_scan, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;
Большой n_dead_tup при старом last_autovacuum - мусор копится, а
автоочистка не успевает или заблокирована горизонтом. Большой
seq_scan на большой таблице, по которой ожидался индекс, - сигнал,
что индекс не берётся или его нет.
Рядом pg_statio_user_tables про кеш: heap_blks_hit (нашли в
буфере) против heap_blks_read (читали с диска). Низкая доля
попаданий на горячей таблице - либо мало shared_buffers, либо таблица
слишком велика для кеша.
40.4 pg_stat_statements: главный отчёт о нагрузке
Это расширение - основной инструмент поиска медленного SQL. Оно
агрегирует выполнение по нормализованному тексту запроса: WHERE id = 1 и WHERE id = 2 схлопываются в один WHERE id = $1. Поэтому
одно тяжёлое место видно как одна строка с суммой по всем своим
вызовам - в отличие от лога, где это тысячи отдельных записей.
SELECT queryid, calls, round(total_exec_time) AS total_ms,
round(mean_exec_time, 1) AS mean_ms, left(query, 70) AS q
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
40.4.1 Подводный камень: сортировать по total, а не по mean
Соблазн - искать «самый медленный запрос» по mean_exec_time. Это
уводит не туда. Запрос на 10 секунд, выполненный раз в сутки, съедает
меньше ресурса, чем запрос на 2 миллисекунды, выполненный миллион раз.
Поэтому виновника нагрузки ищут по total_exec_time - суммарному
времени по всем вызовам. Именно он показывает, на что сервер реально
тратит время. mean полезен потом, когда виновник найден и надо
понять, дорог он за вызов или берёт частотой.
40.5 Лог медленных запросов: конкретика к агрегату
pg_stat_statements говорит «какой класс запросов жрёт время», но
теряет конкретные значения параметров. Дополняет его
log_min_duration_statement - порог в миллисекундах, выше которого
каждый медленный запрос пишется в лог сервера целиком, с реальными
параметрами.
Связка работает так: по pg_stat_statements находишь тяжёлый нормализованный запрос, по логу - конкретный вызов с теми самыми значениями, на которых он тормозит. Дальше его уже можно воспроизвести и разобрать через EXPLAIN.
Расширение auto_explain идёт ещё дальше: пишет в лог сам план
медленного запроса, без ручного воспроизведения. Полезно, когда
запрос тормозит только на проде и только иногда.
40.6 Как соединять представления в отчёт
Сила наблюдаемости - не в отдельном представлении, а в их сцепке. Типовая диагностика «база встала» идёт по цепочке:
pg_stat_activity- кто активен, кто висит вidle in transaction, чего ждут (wait_event);- если видны блокировки в wait_event -
pg_locksплюсpg_stat_activity, чтобы найти blocker по pid; - если виновата нагрузка запросами -
pg_stat_statementsтоп по total_exec_time; - если растёт мусор -
pg_stat_user_tablesпо n_dead_tup и last_autovacuum, и назад к шагу 1 искать, кто держит горизонт.
Каждое представление отвечает на свой вопрос; диагноз рождается из их пересечения. Эту карту мы используем в капстоуне, когда будем расследовать деградацию по всем фронтам сразу. Полная справка - в pg-stat-map.
Уроки в sandbox
lab-40.1. Найди медленный SQL через pg_stat_statements
Создадим нагрузку, найдём виновника по суммарному времени и убедимся, что сортировка по total и по mean выводит наверх разные запросы. Сначала предскажешь, какой запрос окажется виновником, потом проверишь по числам.
Сбрось статистику для чистого замера:
SELECT pg_stat_statements_reset();.Создай частую дешёвую нагрузку: выполни короткий запрос по индексу много раз, например
SELECT * FROM tickets WHERE ticket_no = '0000000000001';повтори десятки раз.Создай редкий дорогой запрос: один раз выполни
SELECT count(*) FROM tickets t JOIN bookings b ON b.book_ref = t.book_ref;.Предскажи, какой запрос будет наверху по total_exec_time, а какой по mean_exec_time. Затем посмотри:
SELECT calls, round(total_exec_time) total_ms, round(mean_exec_time,2) mean_ms, left(query,50) q FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;.Сравни с сортировкой по mean: тот же запрос, но
ORDER BY mean_exec_time DESC. Убедись, что наверху разные строки - в этом и смысл выбора метрики.
sandbox с автопроверкой - открыть в песочнице
Резюме
- Представления pg_stat_* бывают двух сортов: мгновенные снимки (состояние сейчас) и накопленные счётчики (смысл - в дельте между замерами, не в абсолюте).
- pg_stat_activity - первый экран при «база тормозит»; idle in transaction с большим возрастом транзакции держит горизонт и мешает vacuum.
- pg_stat_user_tables отвечает, нужен ли vacuum (n_dead_tup, last_autovacuum) и берётся ли индекс (seq_scan против idx_scan).
- pg_stat_statements агрегирует по нормализованному тексту запроса - одно тяжёлое место видно одной строкой с суммой по всем вызовам.
- Виновника нагрузки ищут по total_exec_time, а не по mean: частый дешёвый запрос съедает больше редкого дорогого.
- log_min_duration_statement и auto_explain дают конкретику (значения параметров, план) к агрегату из pg_stat_statements.
- Диагноз рождается из сцепки представлений: activity → locks → statements → user_tables, по кругу.
Контрольные вопросы
Почему по pg_stat_statements нельзя сказать «прямо сейчас сервер тормозит»?
Показать ответ
Потому что это накопленный счётчик: он суммирует время по всем вызовам с момента последнего сброса статистики. Большое total_exec_time может быть набрано за неделю и ничего не говорить про текущую минуту. Чтобы судить о «сейчас», нужен либо мгновенный снимок (pg_stat_activity), либо дельта pg_stat_statements между двумя замерами за короткий интервал (через pg_stat_statements_reset и повторное чтение).
Чем idle in transaction опаснее, чем idle?
Показать ответ
idle - backend просто простаивает между запросами, он не держит ничего и безвреден. idle in transaction - backend открыл транзакцию и ничего не делает, но транзакция жива, а значит её снимок удерживает горизонт. Vacuum не может почистить мёртвые версии новее этого горизонта - копится bloat. Один такой забытый коннект с большим xact_age раздувает горячие таблицы так же, как долгая активная транзакция.
Почему виновника нагрузки ищут по total_exec_time, а не по mean_exec_time?
Показать ответ
Потому что общую нагрузку создаёт суммарное время, а не время одного вызова. Запрос со средним 2 мс, вызванный миллион раз, даёт 2000 секунд суммарно - больше, чем разовый отчёт на 10 секунд. Сортировка по mean выведет наверх редкие тяжёлые запросы и спрячет частые дешёвые, которые на деле и съедают сервер. mean полезен вторым шагом: понять, дорог найденный виновник за вызов или берёт частотой.
Зачем нужен лог медленных запросов, если есть pg_stat_statements?
Показать ответ
pg_stat_statements нормализует запросы и теряет конкретные значения параметров: он показывает «WHERE id = $1», но не на каком id запрос тормозит. log_min_duration_statement пишет в лог конкретные медленные вызовы целиком, с реальными параметрами, а auto_explain - ещё и их план. Связка такая: по pg_stat_statements находишь тяжёлый класс запросов, по логу - конкретный вызов, который можно воспроизвести и разобрать через EXPLAIN.
Как по системным представлениям понять, что vacuum не справляется?
Показать ответ
Смотришь pg_stat_user_tables: большой n_dead_tup при старом last_autovacuum означает, что мусор копится, а автоочистка либо не успевает, либо заблокирована. Дальше идёшь в pg_stat_activity искать, кто держит горизонт: долгая транзакция или idle in transaction с большим xact_age, либо (если есть реплики) standby с hot_standby_feedback. Диагноз - пересечение двух представлений, а не одно число.