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скоро
  • Уроки
  • База знаний
  • Собеседование
Часть IX — Эксплуатация и анти-паттерны

$ глава 40 · 50 минут

Наблюдаемость: карта pg_stat_*

До этой части мы вскрывали механизмы по одному: страница, снимок, горизонт, план, индекс. В проде они работают вместе, и вопрос меняется с «как устроено» на «что сейчас не так». Отвечает на него встроенная наблюдаемость - семейство системных представлений pg_stat_*.

Это не сторонний мониторинг, а интерфейс к внутренней статистике сервера. Научившись соединять несколько представлений в один отчёт, ты находишь виновника деградации за минуты, а не гадаешь. Эта глава - карта: какие представления о чём, и как ими пользоваться, не путая два их сорта.

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 ждёт.

sql
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.

sql
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. Поэтому одно тяжёлое место видно как одна строка с суммой по всем своим вызовам - в отличие от лога, где это тысячи отдельных записей.

sql
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 Как соединять представления в отчёт

Сила наблюдаемости - не в отдельном представлении, а в их сцепке. Типовая диагностика «база встала» идёт по цепочке:

  1. pg_stat_activity - кто активен, кто висит в idle in transaction, чего ждут (wait_event);
  2. если видны блокировки в wait_event - pg_locks плюс pg_stat_activity, чтобы найти blocker по pid;
  3. если виновата нагрузка запросами - pg_stat_statements топ по total_exec_time;
  4. если растёт мусор - pg_stat_user_tables по n_dead_tup и last_autovacuum, и назад к шагу 1 искать, кто держит горизонт.

Каждое представление отвечает на свой вопрос; диагноз рождается из их пересечения. Эту карту мы используем в капстоуне, когда будем расследовать деградацию по всем фронтам сразу. Полная справка - в pg-stat-map.

Уроки в sandbox

lab-40.1. Найди медленный SQL через pg_stat_statements

Создадим нагрузку, найдём виновника по суммарному времени и убедимся, что сортировка по total и по mean выводит наверх разные запросы. Сначала предскажешь, какой запрос окажется виновником, потом проверишь по числам.

  1. Сбрось статистику для чистого замера: SELECT pg_stat_statements_reset();.

  2. Создай частую дешёвую нагрузку: выполни короткий запрос по индексу много раз, например SELECT * FROM tickets WHERE ticket_no = '0000000000001'; повтори десятки раз.

  3. Создай редкий дорогой запрос: один раз выполни SELECT count(*) FROM tickets t JOIN bookings b ON b.book_ref = t.book_ref;.

  4. Предскажи, какой запрос будет наверху по 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;.

  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, по кругу.

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

  1. Почему по pg_stat_statements нельзя сказать «прямо сейчас сервер тормозит»?

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

    Потому что это накопленный счётчик: он суммирует время по всем вызовам с момента последнего сброса статистики. Большое total_exec_time может быть набрано за неделю и ничего не говорить про текущую минуту. Чтобы судить о «сейчас», нужен либо мгновенный снимок (pg_stat_activity), либо дельта pg_stat_statements между двумя замерами за короткий интервал (через pg_stat_statements_reset и повторное чтение).

  2. Чем idle in transaction опаснее, чем idle?

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

    idle - backend просто простаивает между запросами, он не держит ничего и безвреден. idle in transaction - backend открыл транзакцию и ничего не делает, но транзакция жива, а значит её снимок удерживает горизонт. Vacuum не может почистить мёртвые версии новее этого горизонта - копится bloat. Один такой забытый коннект с большим xact_age раздувает горячие таблицы так же, как долгая активная транзакция.

  3. Почему виновника нагрузки ищут по total_exec_time, а не по mean_exec_time?

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

    Потому что общую нагрузку создаёт суммарное время, а не время одного вызова. Запрос со средним 2 мс, вызванный миллион раз, даёт 2000 секунд суммарно - больше, чем разовый отчёт на 10 секунд. Сортировка по mean выведет наверх редкие тяжёлые запросы и спрячет частые дешёвые, которые на деле и съедают сервер. mean полезен вторым шагом: понять, дорог найденный виновник за вызов или берёт частотой.

  4. Зачем нужен лог медленных запросов, если есть pg_stat_statements?

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

    pg_stat_statements нормализует запросы и теряет конкретные значения параметров: он показывает «WHERE id = $1», но не на каком id запрос тормозит. log_min_duration_statement пишет в лог конкретные медленные вызовы целиком, с реальными параметрами, а auto_explain - ещё и их план. Связка такая: по pg_stat_statements находишь тяжёлый класс запросов, по логу - конкретный вызов, который можно воспроизвести и разобрать через EXPLAIN.

  5. Как по системным представлениям понять, что vacuum не справляется?

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

    Смотришь pg_stat_user_tables: большой n_dead_tup при старом last_autovacuum означает, что мусор копится, а автоочистка либо не успевает, либо заблокирована. Дальше идёшь в pg_stat_activity искать, кто держит горизонт: долгая транзакция или idle in transaction с большим xact_age, либо (если есть реплики) standby с hot_standby_feedback. Диагноз - пересечение двух представлений, а не одно число.

← Предыдущая39-logical-replicationСледующая →41-config
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки