Эта публикация — перевод второй статьи известного специалиста в области Oracle Фрица Хугланда на заданную тему (первую статью я перевёл ранее см. Использование bpftrace для трассировки выполнения запросов в postgres) Это то, чего не существует в PostgreSQL на момент написания статьи. Однако добавить это (timed wait events) чрезвычайно просто, причем с незначительными накладными расходами. Накладные расходы — это классическая причина, по которой PostgreSQL не добавляет "Timed wait events". Мы можем косвенно добавить эти времена ожидания, добавив пробу (probe) к функциям запуска и завершения события ожидания (функции pgstat_report_wait_start() и pgstat_report_wait_end()), перед компиляцией и линковкой исходного кода. Имея такую пробу, мы можем использовать bpftrace (или SystemTap), чтобы отслеживать события начала и окончания ожидания, а также просматривать и засекать время событий ожидания. Добавить такие пробы просто. Необходимо добавить всего 5 линий кода в исходный код PostgreSQL, чтобы появились 2 новые пробы: начала события ожидания и окончания события ожидания (wait event start and wait event end). Добавьте пробы в файл, который содержит функции pgstat_report_wait_start() and pgstat_report_wait_end(); (это файл — src/include/utils/wait_event.h) Добавьте эти пробы в список проб в файле src/backend/utils/probes.d: Далее необходимо добавить опцию(ключ) для включения dtrace при компиляции (прим. переводчика configure --enable-dtrace ...), а также скомпилировать и слинковать исходный код PostgreSQL. Чтобы подтвердить добавление новых проб, вы можете использовать команду bpftrace -l для вывода списка проб: Вот они! Я расширил скрипт query-analyzer.bt с помощью событий ожидания и получил: query-analyzer-waits.bt. Теперь я могу использовать bpftrace и пробы, чтобы увидеть, на что расходуется время, включая события ожидания: Я выполнил select now(); в psql, мы можем видеть, что используется simple query protocol (Query start/Query done). Выполнение завершилось ожиданием, но поскольку информация о классе ожидания и имени доступна только при запуске ожидания, я не могу увидеть это здесь. Это client.CLIENT_READ ожидание. Для такого небольшого, простого запроса вообще не возникает событие ожидания. Это немного странно. Я уверен, что результат выполнения отправляется обратно моему клиенту (psql). Получается, что эта отправка не посчитана? Я почти уверен, что это сделано намеренно, потому что если я отправлю клиенту много данных, можно сгенерировать client.CLIENT_WRITE ожидание. Событие клиент.CLIENT_WRITE, по-видимому, является событием, которое показывает время, затраченное на отправку данных клиенту (для этого отключите пейджинг в psql: \pset pager off): Также интересно: когда я создаю таблицу, вызов WAL write(io.WAL_WRITE) и fsync() (io.WAL_SYNC) происходит, когда фаза выполнения завершена (фаза выполнения [execute] сообщает о своем времени до io.WAL_WRITE и io.WAL_SYNC): И если я вставляю данные, функция fsync() также выполняется вне фазы выполнения: А вот как выглядит использование TEMP'a: Почти такое же количество Temp IO можно увидеть с помощью explain analyse (buffers). Первый сеанс для входа в базу данных должен (повторно) создать relcache. Это делается до того, как сеанс начнет что-либо выполнять. Вот как это выглядит в моей базе данных: В моей базе данных без пользовательских таблиц это генерирует 2 события io.RELATION_MAP_READ и 71 событие io.DATA_FILE_READ. Они кешируются в операционной системе, и поэтому у них указанная выше мизерная задержка (задержка указана в микросекундах!). Вполне вероятно, что это происходит не только при первом входе в систему, но и при изменении таблиц, требующем обновления relcaches, которые являются файлами. Скорее всего, они находятся за пределами фазы выполнения и, следовательно, эти события потенциально трудно обнаружить. Фактически, операции чтения relcache также записывают init-файлы relcache при первом обращении к базе данных. Однако нет события io.RELATION_MAP_WRITE. Мне было интересно, есть ли какой-то механизм для проверки того, что существующие файлы все еще действительны, но быстрое исследование при помощи strace -fp показывает, что файлы инициализации relcache записываются, только незаметно (из-за отсутствия io.RELATION_MAP_WRITE). Когда я выполняю первый запрос, серверная часть должна заполнить свой каталог, который также генерирует некоторые события io.DATA_FILE_READ: Важно, что они выполняются на этапе rewrite. Следовательно, фаза rewrite занимает больше времени. Эта фаза в основном не попадает в отчёты по производительности и не видна в pg_stat_statements.