Публикация была переведена автоматически. Исходный язык: Русский
Есть типичная история про PostgreSQL, которая повторяется в разных компаниях.
Живой прод, нагрузка нормальная, графики зеленые. И вдруг - алерт: p95/p99 улетели, таймауты полезли, очередь в брокере растет, поддержка пишет “у клиента все зависло”.
И начинается классический чат:
- Это опять база?
- Нет, у нас CPU в норме.
- Ну значит сеть.
- Нет, сеть тоже норм.
- Тогда что?
Самое неприятное в таких инцидентах - они редко выглядят как “одна понятная причина”. Это не “кончился диск” и не “упала реплика”. Это состояние “вроде все работает, но работает плохо”.
Первый импульс у нас был такой же, как у всех:
- открыть pg_stat_activity
- посмотреть, кто активен
- поискать блокировки
- найти пару долгих запросов и ткнуть в них пальцем
Иногда везет: видишь один запрос, который держит лок полчаса, и все ясно.
Но чаще видишь снимок уровня:
- часть сессий idle
- часть active
- пару idle in transaction
- ожидания разные, и не понятно, что причина, а что следствие
Главная проблема - это просто фотография. А деградация почти всегда живет во времени: то накатывает, то отпускает, то совпадает с бэкапом, то с пиком нагрузки, то с фоновыми джобами.
Дальше вспоминают pg_stat_statements:
- топ по total_exec_time
- топ по calls
- топ по mean_exec_time
- топ по I/O
И снова можно найти много интересного, но часто не находится главное: “что именно сломало нам конкретное окно времени”.
Потому что инцидент - это не “в среднем за сутки”, а “вот в эти 7 минут у нас случился ад”.
Postgres в таких моментах честно умеет сказать:
- я не “торможу просто так”
- я “жду”
И если смотреть не только на запросы, а на ожидания (wait events), картинка резко становится понятнее:
- мы ждем IO - значит упираемся в диск/чтение/запись
- мы ждем Lock - значит конфликты транзакций
- мы ждем LWLock - значит внутренние точки contention
- мы ждем Client - значит клиенты не читают/пишут, сеть, приложение
- мы ждем Activity/Timeout - значит фоновые процессы, таймеры, автovacuum и так далее
Но ждать - это тоже не диагноз. Дальше нужно связать три слоя:
- что ждал Postgres
- какие запросы это создавали
- что в это время происходило на уровне ОС (CPU/IO/память)
И вот тут нам зашел pg_expecto.
pg_expecto - это комплекс для статистического анализа производительности PostgreSQL, который делает простую, но очень полезную вещь:
- собирает ожидания wait_event_type/wait_event (через pg_wait_sampling)
- подтягивает статистику запросов (через pg_stat_statements)
- добавляет метрики ОС (через vmstat и iostat)
- помогает свести это в один отчет (в том числе под Excel)
- и подходит как для расследования инцидентов, так и для нагрузочного тестирования
Философия у него правильная: меньше магии, больше прозрачности.
Не “инструмент сказал, что виноват индекс”, а:
- вот окно деградации
- вот какие ожидания выросли
- вот какие queryid и запросы дают основную долю
- вот что в это время показывал диск и CPU
У pg_expecto есть несколько вещей, без которых будет пусто:
- Утилиты ОС:
- vmstat
- iostat
- Расширения PostgreSQL:
- pg_stat_statements
- pg_wait_sampling
- shared_preload_libraries и рестарт
И важный момент: порядок библиотек важен. В рабочем варианте это выглядит так:
shared_preload_libraries = 'pg_stat_statements, pg_wait_sampling'Да, это означает рестарт PostgreSQL. Не reload.
Типовой сценарий установки (как в README проекта) примерно такой:
- Распаковать архив проекта pg_expecto-main.zip - получится папка pg_expecto-main
- Скопировать содержимое на сервер в /tmp/pg_expecto
- Под пользователем postgres создать сервисную папку:
mkdir /postgres/pg_expecto- Скопировать инсталлятор:
cp /tmp/pg_expecto/pg_expecto_install.sh /postgres/pg_expecto/- Перейти в папку:
cd /postgres/pg_expecto- Подготовить скрипт:
chmod 750 pg_expecto_install.sh- Запустить:
./pg_expecto_install.shПроверить, что оно живет и пишет логи:
tail -f /postgres/pg_expecto/sh/pg_expecto.logНиже - примеры того, какие цифры обычно начинают “прояснять голову”. Цифры демонстрационные, но формат и смысл реальные.
pg_wait_sampling дает представление pg_wait_sampling_profile, где по сэмплам можно собрать “профиль ожиданий”.
Пример запроса, который собирает топ ожиданий:
SELECT
event_type,
event,
sum(count) AS samples,
round(100.0 * sum(count) / sum(sum(count)) OVER (), 2) AS pct
FROM pg_wait_sampling_profile
GROUP BY 1,2
ORDER BY samples DESC
LIMIT 10;Пример вывода:
event_type | event | samples | pct
-----------+-------------------+-----------+------
IO | DataFileRead | 1245300 | 42.80
LWLock | BufferMapping | 512000 | 17.59
Lock | transactionid | 318000 | 10.92
Client | ClientRead | 210000 | 7.21
IO | WALWrite | 180000 | 6.18
IO | DataFileWrite | 115000 | 3.95
LWLock | WALWriteLock | 82000 | 2.81
Timeout | VacuumDelay | 60000 | 2.06
Activity | AutoVacuumMain | 42000 | 1.44
Lock | relation | 30000 | 1.03И вот это уже можно перевести на человеческий язык:
- 42.8% IO/DataFileRead - в основном читаем данные с диска
- 17.6% LWLock/BufferMapping - есть конкуренция на буферах (часто бывает при высокой конкуренции и горячих страницах)
- 10.9% Lock/transactionid - транзакции конфликтуют, кто-то кого-то держит
- 7.2% Client/ClientRead - часть времени тупо ждем клиента (например, приложение не читает результат)
Это не ответ “что чинить”, но это уже ответ “куда копать”.
Вторая важная вещь - увидеть не просто “в целом”, а “по времени”.
Если у вас доступна история сэмплов (ring buffer), можно посмотреть последние минуты:
SELECT
date_trunc('second', ts) AS ts,
event_type,
event,
count(*) AS samples
FROM pg_wait_sampling_history
WHERE ts >= now() - interval '120 seconds'
GROUP BY 1,2,3
ORDER BY ts DESC, samples DESC
LIMIT 50;Пример того, что бывает видно:
ts | event_type | event | samples
---------------------+-----------+---------------+---------
2026-01-24 12:01:58 | IO | DataFileRead | 180
2026-01-24 12:01:58 | LWLock | BufferMapping | 70
2026-01-24 12:01:57 | IO | DataFileRead | 190
2026-01-24 12:01:57 | Lock | transactionid | 55
...
2026-01-24 12:00:40 | Activity | AutoVacuumMain| 20
2026-01-24 12:00:39 | Activity | AutoVacuumMain| 18То есть можно поймать момент, когда, например:
- сначала влетел autovacuum
- потом резко вырос IO
- потом пошли блокировки
- и вот оно совпало с деградацией p99
Теперь - слой запросов. Классика через pg_stat_statements.
Например, топ по общему времени:
SELECT
queryid,
calls,
round(total_exec_time::numeric, 0) AS total_ms,
round(mean_exec_time::numeric, 2) AS mean_ms,
rows,
shared_blks_hit,
shared_blks_read,
round(blk_read_time::numeric, 0) AS blk_read_ms,
round(blk_write_time::numeric, 0) AS blk_write_ms
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 5;Пример вывода:
queryid | calls | total_ms | mean_ms | rows | hit | read | blk_read_ms | blk_write_ms
-----------+--------+----------+---------+--------+---------+---------+-------------+-------------
912340001 | 18500 | 980000 | 52.97 | 740000 | 8400000 | 1200000 | 420000 | 8000
912340002 | 240 | 410000 | 1708.33 | 12000 | 80000 | 320000 | 310000 | 5000
912340003 | 90000 | 260000 | 2.89 | 900000 | 9100000 | 90000 | 12000 | 3000
912340004 | 1500 | 180000 | 120.00 | 60000 | 300000 | 280000 | 140000 | 6000
912340005 | 80 | 160000 | 2000.00 | 4000 | 20000 | 150000 | 155000 | 2000Тут обычно видно разные типы боли:
- много вызовов, среднее не огромное, но суммарно съедает много
- мало вызовов, но каждый очень дорогой (OLAP-образные штуки)
- высокая доля shared_blks_read и blk_read_time - вероятно, упираемся в чтение
Самое вкусное начинается, когда вы связываете queryid из ожиданий с queryid из pg_stat_statements.
Например, грубая версия “какие запросы чаще всего фигурируют в ожиданиях”:
SELECT
s.queryid,
sum(p.count) AS wait_samples,
round(100.0 * sum(p.count) / sum(sum(p.count)) OVER (), 2) AS wait_pct,
round(s.total_exec_time::numeric, 0) AS total_ms,
round(s.mean_exec_time::numeric, 2) AS mean_ms,
left(regexp_replace(s.query, '\s+', ' ', 'g'), 90) AS query_short
FROM pg_wait_sampling_profile p
JOIN pg_stat_statements s ON s.queryid = p.queryid
WHERE p.queryid <> 0
GROUP BY s.queryid, s.total_exec_time, s.mean_exec_time, s.query
ORDER BY wait_samples DESC
LIMIT 10;Пример вывода:
queryid | wait_samples | wait_pct | total_ms | mean_ms | query_short
-----------+-------------+----------+----------+---------+----------------------------------------------
912340001 | 980000 | 35.20 | 980000 | 52.97 | SELECT ... FROM orders WHERE user_id = $1 ...
912340004 | 240000 | 8.61 | 180000 | 120.00 | UPDATE accounts SET balance = balance - $1...
912340002 | 210000 | 7.54 | 410000 | 1708.33 | SELECT ... FROM report JOIN ... GROUP BY ...
...И дальше расследование становится очень практичным:
- “35% всех ожиданий крутятся вокруг одного SELECT”
- смотрим его план, индексы, селективность, кэш, размер таблицы
- параллельно смотрим, какие именно ожидания доминируют у этого queryid (IO? Lock?)
Если в ожиданиях много IO, хочется быстро подтвердить, что это правда именно на уровне железа/VM.
Типовой iostat -x в момент деградации часто выглядит так:
avg-cpu: %user %system %iowait %idle
12.3 3.1 45.0 39.6
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 3200 1500 51200 24000 18.2 0.31 99.8Смысл:
- %iowait высокий - CPU стоит и ждет IO
- await вырос - запросы к диску ждут дольше
- %util почти 100% - диск реально забит
vmstat обычно вторит:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
12 5 0 12000 8000 540000 0 0 4500 1200 9000 16000 10 3 40 47 0Тут важны:
- b (blocked) - процессы стоят в ожидании IO
- wa (iowait) - снова подтверждение
И вот после такого уже тяжело спорить “это не диск”. Это диск.
Мы для себя свели это в понятную последовательность:
- Определить окно деградации по графикам (хотя бы 5-15 минут)
- Посмотреть профиль ожиданий (top wait_event_type/wait_event)
- Найти топ queryid/запросы, которые дают основную долю
- Сверить с ОС метриками (iostat/vmstat) в то же окно
- Сформулировать гипотезу в одном предложении, например:
- “мы упираемся в чтение из-за X, потому что кэш не держит рабочий набор”
- “мы упираемся в lock по transactionid из-за долгих транзакций в сервисе Y”
- “у нас contention на BufferMapping из-за высокой конкуренции на горячих страницах”
- И только после этого лезть в оптимизации: индексы, планы, параметры, архитектура, расписание фоновых задач
Несколько вещей, которые важно держать в голове:
- shared_preload_libraries = рестарт. Без этого ожидания нормально не поедут.
- Сэмплы - это статистика, а не точное время.pg_wait_sampling дает количество сэмплов, это хорошо для долей и сравнений, но это не “точно 18.231 секунды”.
- Queryid = 0 - это фон.Часть ожиданий будет у background worker-ов, checkpointer и прочего. Это тоже полезно, просто не надо удивляться.
- pg_stat_statements нужно уметь сбрасывать и снимать снапшоты.Если вы смотрите “все с момента запуска”, легко утонуть в среднем.
- Отчет сам по себе не лечит.Он превращает “ощущение” в список конкретных причин, но чинить все равно вам.
pg_expecto хорош тем, что вытаскивает расследование из режима “кажется, база” в режим “вот конкретно, что именно мы ждали, в какое время, и какие запросы это создавали”.
И после этого:
- меньше ночных гаданий
- меньше споров “это база или не база”
- больше нормальной инженерии
Репозиторий расширения:
https://gitflic.ru/project/kznalp/pg_expectoЕсть типичная история про PostgreSQL, которая повторяется в разных компаниях.
Живой прод, нагрузка нормальная, графики зеленые. И вдруг - алерт: p95/p99 улетели, таймауты полезли, очередь в брокере растет, поддержка пишет “у клиента все зависло”.
И начинается классический чат:
- Это опять база?
- Нет, у нас CPU в норме.
- Ну значит сеть.
- Нет, сеть тоже норм.
- Тогда что?
Самое неприятное в таких инцидентах - они редко выглядят как “одна понятная причина”. Это не “кончился диск” и не “упала реплика”. Это состояние “вроде все работает, но работает плохо”.
Первый импульс у нас был такой же, как у всех:
- открыть pg_stat_activity
- посмотреть, кто активен
- поискать блокировки
- найти пару долгих запросов и ткнуть в них пальцем
Иногда везет: видишь один запрос, который держит лок полчаса, и все ясно.
Но чаще видишь снимок уровня:
- часть сессий idle
- часть active
- пару idle in transaction
- ожидания разные, и не понятно, что причина, а что следствие
Главная проблема - это просто фотография. А деградация почти всегда живет во времени: то накатывает, то отпускает, то совпадает с бэкапом, то с пиком нагрузки, то с фоновыми джобами.
Дальше вспоминают pg_stat_statements:
- топ по total_exec_time
- топ по calls
- топ по mean_exec_time
- топ по I/O
И снова можно найти много интересного, но часто не находится главное: “что именно сломало нам конкретное окно времени”.
Потому что инцидент - это не “в среднем за сутки”, а “вот в эти 7 минут у нас случился ад”.
Postgres в таких моментах честно умеет сказать:
- я не “торможу просто так”
- я “жду”
И если смотреть не только на запросы, а на ожидания (wait events), картинка резко становится понятнее:
- мы ждем IO - значит упираемся в диск/чтение/запись
- мы ждем Lock - значит конфликты транзакций
- мы ждем LWLock - значит внутренние точки contention
- мы ждем Client - значит клиенты не читают/пишут, сеть, приложение
- мы ждем Activity/Timeout - значит фоновые процессы, таймеры, автovacuum и так далее
Но ждать - это тоже не диагноз. Дальше нужно связать три слоя:
- что ждал Postgres
- какие запросы это создавали
- что в это время происходило на уровне ОС (CPU/IO/память)
И вот тут нам зашел pg_expecto.
pg_expecto - это комплекс для статистического анализа производительности PostgreSQL, который делает простую, но очень полезную вещь:
- собирает ожидания wait_event_type/wait_event (через pg_wait_sampling)
- подтягивает статистику запросов (через pg_stat_statements)
- добавляет метрики ОС (через vmstat и iostat)
- помогает свести это в один отчет (в том числе под Excel)
- и подходит как для расследования инцидентов, так и для нагрузочного тестирования
Философия у него правильная: меньше магии, больше прозрачности.
Не “инструмент сказал, что виноват индекс”, а:
- вот окно деградации
- вот какие ожидания выросли
- вот какие queryid и запросы дают основную долю
- вот что в это время показывал диск и CPU
У pg_expecto есть несколько вещей, без которых будет пусто:
- Утилиты ОС:
- vmstat
- iostat
- Расширения PostgreSQL:
- pg_stat_statements
- pg_wait_sampling
- shared_preload_libraries и рестарт
И важный момент: порядок библиотек важен. В рабочем варианте это выглядит так:
shared_preload_libraries = 'pg_stat_statements, pg_wait_sampling'Да, это означает рестарт PostgreSQL. Не reload.
Типовой сценарий установки (как в README проекта) примерно такой:
- Распаковать архив проекта pg_expecto-main.zip - получится папка pg_expecto-main
- Скопировать содержимое на сервер в /tmp/pg_expecto
- Под пользователем postgres создать сервисную папку:
mkdir /postgres/pg_expecto- Скопировать инсталлятор:
cp /tmp/pg_expecto/pg_expecto_install.sh /postgres/pg_expecto/- Перейти в папку:
cd /postgres/pg_expecto- Подготовить скрипт:
chmod 750 pg_expecto_install.sh- Запустить:
./pg_expecto_install.shПроверить, что оно живет и пишет логи:
tail -f /postgres/pg_expecto/sh/pg_expecto.logНиже - примеры того, какие цифры обычно начинают “прояснять голову”. Цифры демонстрационные, но формат и смысл реальные.
pg_wait_sampling дает представление pg_wait_sampling_profile, где по сэмплам можно собрать “профиль ожиданий”.
Пример запроса, который собирает топ ожиданий:
SELECT
event_type,
event,
sum(count) AS samples,
round(100.0 * sum(count) / sum(sum(count)) OVER (), 2) AS pct
FROM pg_wait_sampling_profile
GROUP BY 1,2
ORDER BY samples DESC
LIMIT 10;Пример вывода:
event_type | event | samples | pct
-----------+-------------------+-----------+------
IO | DataFileRead | 1245300 | 42.80
LWLock | BufferMapping | 512000 | 17.59
Lock | transactionid | 318000 | 10.92
Client | ClientRead | 210000 | 7.21
IO | WALWrite | 180000 | 6.18
IO | DataFileWrite | 115000 | 3.95
LWLock | WALWriteLock | 82000 | 2.81
Timeout | VacuumDelay | 60000 | 2.06
Activity | AutoVacuumMain | 42000 | 1.44
Lock | relation | 30000 | 1.03И вот это уже можно перевести на человеческий язык:
- 42.8% IO/DataFileRead - в основном читаем данные с диска
- 17.6% LWLock/BufferMapping - есть конкуренция на буферах (часто бывает при высокой конкуренции и горячих страницах)
- 10.9% Lock/transactionid - транзакции конфликтуют, кто-то кого-то держит
- 7.2% Client/ClientRead - часть времени тупо ждем клиента (например, приложение не читает результат)
Это не ответ “что чинить”, но это уже ответ “куда копать”.
Вторая важная вещь - увидеть не просто “в целом”, а “по времени”.
Если у вас доступна история сэмплов (ring buffer), можно посмотреть последние минуты:
SELECT
date_trunc('second', ts) AS ts,
event_type,
event,
count(*) AS samples
FROM pg_wait_sampling_history
WHERE ts >= now() - interval '120 seconds'
GROUP BY 1,2,3
ORDER BY ts DESC, samples DESC
LIMIT 50;Пример того, что бывает видно:
ts | event_type | event | samples
---------------------+-----------+---------------+---------
2026-01-24 12:01:58 | IO | DataFileRead | 180
2026-01-24 12:01:58 | LWLock | BufferMapping | 70
2026-01-24 12:01:57 | IO | DataFileRead | 190
2026-01-24 12:01:57 | Lock | transactionid | 55
...
2026-01-24 12:00:40 | Activity | AutoVacuumMain| 20
2026-01-24 12:00:39 | Activity | AutoVacuumMain| 18То есть можно поймать момент, когда, например:
- сначала влетел autovacuum
- потом резко вырос IO
- потом пошли блокировки
- и вот оно совпало с деградацией p99
Теперь - слой запросов. Классика через pg_stat_statements.
Например, топ по общему времени:
SELECT
queryid,
calls,
round(total_exec_time::numeric, 0) AS total_ms,
round(mean_exec_time::numeric, 2) AS mean_ms,
rows,
shared_blks_hit,
shared_blks_read,
round(blk_read_time::numeric, 0) AS blk_read_ms,
round(blk_write_time::numeric, 0) AS blk_write_ms
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 5;Пример вывода:
queryid | calls | total_ms | mean_ms | rows | hit | read | blk_read_ms | blk_write_ms
-----------+--------+----------+---------+--------+---------+---------+-------------+-------------
912340001 | 18500 | 980000 | 52.97 | 740000 | 8400000 | 1200000 | 420000 | 8000
912340002 | 240 | 410000 | 1708.33 | 12000 | 80000 | 320000 | 310000 | 5000
912340003 | 90000 | 260000 | 2.89 | 900000 | 9100000 | 90000 | 12000 | 3000
912340004 | 1500 | 180000 | 120.00 | 60000 | 300000 | 280000 | 140000 | 6000
912340005 | 80 | 160000 | 2000.00 | 4000 | 20000 | 150000 | 155000 | 2000Тут обычно видно разные типы боли:
- много вызовов, среднее не огромное, но суммарно съедает много
- мало вызовов, но каждый очень дорогой (OLAP-образные штуки)
- высокая доля shared_blks_read и blk_read_time - вероятно, упираемся в чтение
Самое вкусное начинается, когда вы связываете queryid из ожиданий с queryid из pg_stat_statements.
Например, грубая версия “какие запросы чаще всего фигурируют в ожиданиях”:
SELECT
s.queryid,
sum(p.count) AS wait_samples,
round(100.0 * sum(p.count) / sum(sum(p.count)) OVER (), 2) AS wait_pct,
round(s.total_exec_time::numeric, 0) AS total_ms,
round(s.mean_exec_time::numeric, 2) AS mean_ms,
left(regexp_replace(s.query, '\s+', ' ', 'g'), 90) AS query_short
FROM pg_wait_sampling_profile p
JOIN pg_stat_statements s ON s.queryid = p.queryid
WHERE p.queryid <> 0
GROUP BY s.queryid, s.total_exec_time, s.mean_exec_time, s.query
ORDER BY wait_samples DESC
LIMIT 10;Пример вывода:
queryid | wait_samples | wait_pct | total_ms | mean_ms | query_short
-----------+-------------+----------+----------+---------+----------------------------------------------
912340001 | 980000 | 35.20 | 980000 | 52.97 | SELECT ... FROM orders WHERE user_id = $1 ...
912340004 | 240000 | 8.61 | 180000 | 120.00 | UPDATE accounts SET balance = balance - $1...
912340002 | 210000 | 7.54 | 410000 | 1708.33 | SELECT ... FROM report JOIN ... GROUP BY ...
...И дальше расследование становится очень практичным:
- “35% всех ожиданий крутятся вокруг одного SELECT”
- смотрим его план, индексы, селективность, кэш, размер таблицы
- параллельно смотрим, какие именно ожидания доминируют у этого queryid (IO? Lock?)
Если в ожиданиях много IO, хочется быстро подтвердить, что это правда именно на уровне железа/VM.
Типовой iostat -x в момент деградации часто выглядит так:
avg-cpu: %user %system %iowait %idle
12.3 3.1 45.0 39.6
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 3200 1500 51200 24000 18.2 0.31 99.8Смысл:
- %iowait высокий - CPU стоит и ждет IO
- await вырос - запросы к диску ждут дольше
- %util почти 100% - диск реально забит
vmstat обычно вторит:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
12 5 0 12000 8000 540000 0 0 4500 1200 9000 16000 10 3 40 47 0Тут важны:
- b (blocked) - процессы стоят в ожидании IO
- wa (iowait) - снова подтверждение
И вот после такого уже тяжело спорить “это не диск”. Это диск.
Мы для себя свели это в понятную последовательность:
- Определить окно деградации по графикам (хотя бы 5-15 минут)
- Посмотреть профиль ожиданий (top wait_event_type/wait_event)
- Найти топ queryid/запросы, которые дают основную долю
- Сверить с ОС метриками (iostat/vmstat) в то же окно
- Сформулировать гипотезу в одном предложении, например:
- “мы упираемся в чтение из-за X, потому что кэш не держит рабочий набор”
- “мы упираемся в lock по transactionid из-за долгих транзакций в сервисе Y”
- “у нас contention на BufferMapping из-за высокой конкуренции на горячих страницах”
- И только после этого лезть в оптимизации: индексы, планы, параметры, архитектура, расписание фоновых задач
Несколько вещей, которые важно держать в голове:
- shared_preload_libraries = рестарт. Без этого ожидания нормально не поедут.
- Сэмплы - это статистика, а не точное время.pg_wait_sampling дает количество сэмплов, это хорошо для долей и сравнений, но это не “точно 18.231 секунды”.
- Queryid = 0 - это фон.Часть ожиданий будет у background worker-ов, checkpointer и прочего. Это тоже полезно, просто не надо удивляться.
- pg_stat_statements нужно уметь сбрасывать и снимать снапшоты.Если вы смотрите “все с момента запуска”, легко утонуть в среднем.
- Отчет сам по себе не лечит.Он превращает “ощущение” в список конкретных причин, но чинить все равно вам.
pg_expecto хорош тем, что вытаскивает расследование из режима “кажется, база” в режим “вот конкретно, что именно мы ждали, в какое время, и какие запросы это создавали”.
И после этого:
- меньше ночных гаданий
- меньше споров “это база или не база”
- больше нормальной инженерии
Репозиторий расширения:
https://gitflic.ru/project/kznalp/pg_expecto