- •Предисловие
- •Об этой книге
- •Глава 1. Обзор статистики
- •Внутреннее устройство PostgreSQL
- •Установка соединений и работа сеансов
- •Запросы как базовая единица рабочей нагрузки
- •Планирование и выполнение запросов
- •Ввод-вывод при выполнении запросов
- •Журнал сообщений СУБД
- •Репликация изменений
- •Архивирование журнала предзаписи
- •Фоновая синхронизация данных
- •Автоочистка
- •Интерфейс статистики
- •Статистика как отправная точка инструментов мониторинга
- •Особенности статистики
- •Тестовое окружение
- •Глава 2. Статистика активности
- •Ключ к пониманию происходящего в СУБД
- •Взаимодействие клиента и сервера
- •Источники информации об активности
- •Представление pg_stat_activity
- •Представление pg_locks
- •Особенности pg_stat_activity и pg_locks
- •Представление pg_stat_database
- •Подключенные клиенты
- •Отслеживание клиентских сеансов
- •Транзакционная активность
- •Статусы завершения сеансов
- •Состояния сеансов
- •Отслеживание состояний
- •Ожидания и блокировки
- •Отслеживание состояний с учетом ожиданий
- •Взаимоблокировки
- •Бездействующие транзакции
- •Время выполнения запросов и транзакций
- •Отслеживание времени ожидания блокировок
- •Использование pg_locks.waitstart
- •Использование pg_stat_activity.state_change
- •Дерево блокировок
- •Глава 3. Выполнение запросов и функций
- •Зачем нужен мониторинг запросов
- •Расширение pg_stat_statements
- •Метаданные запроса
- •Планирование запроса
- •Исполнение запроса
- •Сквозная идентификация с queryid
- •Построение отчетов на основе pg_stat_statements
- •Представление pg_stat_statements_info
- •Выполнение процедур и функций
- •Глава 4. Базы данных
- •Иерархия объектов СУБД
- •Кластер баз данных
- •Табличные пространства
- •Базы данных
- •Схемы
- •Таблицы и индексы
- •TOAST
- •События в кластере баз данных
- •Рабочая нагрузка в отношении таблиц и индексов
- •Ошибки и нежелательные события
- •Функции для работы с объектами СУБД
- •Определение размеров объектов СУБД
- •Размещение объектов в файловой системе
- •Глава 5. Область общей памяти и ввод-вывод
- •Анализ общей памяти
- •Представление pg_buffercache
- •Представление pg_shmem_allocations
- •Анализ памяти клиентских процессов
- •Оценка использования SLRU-кешей
- •Ввод-вывод в контексте объектов СУБД
- •Базы данных
- •Ввод-вывод в контексте выполнения запросов
- •Временные файлы
- •Уровень баз данных
- •Ввод-вывод при выполнении запросов
- •Отслеживание в журнале сообщений
- •Отслеживание активных временных файлов
- •Ввод-вывод фоновых процессов
- •Глава 6. Журнал упреждающей записи
- •Отслеживание активности в журнале
- •Представление pg_stat_wal
- •Представление pg_stat_statements
- •Архивирование журнала
- •Представление pg_stat_archiver
- •Очередь архивирования
- •Глава 7. Репликация
- •Обзор репликации
- •Инструменты отслеживания репликации
- •Представление pg_stat_replication
- •Представление pg_stat_wal_receiver
- •Cлоты репликации и pg_replication_slots
- •Публикации и подписки
- •Конфликты восстановления
- •Глава 8. Очистка
- •Введение в очистку
- •Особенности очистки на практике
- •Когда выполняется автоочистка?
- •Статистика выполнения очистки
- •Счетчик транзакций и предотвращение ошибок, связанных с его зацикливанием
- •Раздувание таблиц и индексов
- •Отслеживание активных процессов очистки
- •Представление pg_stat_activity
- •Представление pg_stat_progress_vacuum
- •Глава 9. Ход выполнения операций
- •Представление pg_stat_progress_analyze
- •Представление pg_stat_progress_basebackup
- •Представление pg_stat_progress_cluster
- •Представление pg_stat_progress_create_index
- •Представление pg_stat_progress_copy
- •Предметный указатель
2.7. Отслеживание времени ожидания блокировок |
61 |
Рис. 2.9.Длительность выполнения транзакций
2.7. Отслеживание времени ожидания блокировок
Вовремяожиданияпроцесснеможетвыполнятьполезнуюработудотехпор,поканеисчезнет причина ожидания. Продолжительное ожидание само по себе неэффективно с точки зрения использования ресурсов и времени. Выявляя и устраняя участки, в которых происходит ожидание, можно ускорить выполнение рабочей нагрузки и общую производительность системы. Далее речь пойдет о времени, проведенном на блокировках, поскольку нет гарантированно точного способа определить время ожидания конкретного события.
Использование pg_locks.waitstart
Для оценки времени ожидания понадобится поле pg_locks.waitstart. Если процесс не находится в ожидании, то значение этого поля будет отсутствовать. Если процесс не смог взять блокировку, он переходит в ожидание, и это поле показывает время перехода в ожидание. Здесь есть неявная связь с другим полем этого же представления: на ожидание блокировки указывает не только waitstart, но и флаг granted. Однако они не полностью согласованы, и waitstart может в течение короткого периода содержать NULL, когда поле granted уже установлено в false.
При оценке времени ожидания можно использовать как минимум два подхода к расчету (на практике можно встретитьи больше).Время ожидания всех процессов натекущий момент можно получить следующим запросом:
62 |
Глава 2. |
Статистика активности |
|
|
||||
|
# SELECT |
|
|
|
|
|
|
|
|
a.pid, a.state, l.granted, |
|
|
|
||||
|
a.wait_event ||'.'|| a.wait_event_type AS wait, |
|
||||||
|
clock_timestamp() - l.waitstart AS wait_age |
|
|
|||||
|
FROM pg_stat_activity a, pg_locks l |
|
|
|||||
|
WHERE a.pid = l.pid |
|
|
|
|
|||
|
AND NOT l.granted; |
|
|
|
|
|||
|
pid |
| state |
| |
granted | |
wait |
| |
wait_age |
|
|
--------- |
+-------- |
+ |
--------- |
+-------------------- |
|
+----------------- |
|
|
2405179 |
| active | |
f |
| |
transactionid.Lock |
| 00:00:00.284314 |
||
|
2406471 |
| active | |
f |
| |
transactionid.Lock |
| 00:00:00.277662 |
||
|
2405394 |
| active | |
f |
| |
transactionid.Lock |
| 00:00:00.018215 |
||
|
2406467 |
| active | |
f |
| |
transactionid.Lock |
| 00:00:00.060994 |
||
|
2406472 |
| active | |
f |
| |
tuple.Lock |
| 00:00:00.240659 |
||
|
2406466 |
| active | |
f |
| |
transactionid.Lock |
| 00:00:00.125218 |
||
Первый вариант подсчета сводится к суммированию всех ожиданий всех процессов. В этом случае получается общее время ожидания, и чем оно больше, тем хуже ситуация, особенно в системах с большой конкурентностью. Вариант подходит для общей оценки того, сколько времени СУБД тратит на ожидание.
Второй вариант — это учет лишь максимального времени ожидания среди всех процессов. В этом случае получится картина только по одному процессу, который ждет дольше всех остальных.Вариантподходитдля оперативного мониторинга,чтобы понимать,что в конкретный момент есть (или был) конкретный процесс,который находился в ожидании конкретный интервал времени.
Убрав в исходном запросе лишние поля и обернув его в CTE, можно получить оба значения и заодно посчитать количество ждущих процессов:
# WITH q AS (
SELECT clock_timestamp() - l.waitstart AS wait_age FROM pg_stat_activity a, pg_locks l
WHERE a.pid = l.pid AND NOT l.granted
)SELECT count(*),
coalesce(max(wait_age), '0'::interval) AS max, coalesce(sum(wait_age), '0'::interval) AS sum
FROM |
q; |
|
|
|
count |
| |
max |
| |
sum |
------- |
+----------------- |
|
+ |
---------------- |
6 |
| 00:00:00.284314 | 00:00:01.007062 |
|||
Вэтом выводе видно, что в момент опроса было шесть процессов в ожидании, самый долгий ждалоколо284миллисекунд,асуммарновсеклиентыСУБДпрождалипримернооднусекунду.
Вконтексте синтетической нагрузки тестового окружения такие цифры могут казаться незначительными, однако в реальных окружениях значения могут быть совсем другого порядка. Своевременное обнаружение и сокращение времени ожидания позволит увеличить производительность приложений и СУБД.
2.7. Отслеживание времени ожидания блокировок |
63 |
Текущая реализация используемого агента по умолчанию собирает максимальное время ожидания среди всех процессов. Статистику ожиданий и график (рис. 2.10) можно получить с помощью запроса:
# postgres_activity_max_seconds{service_id="primary",state="waiting"}
Рис. 2.10.Длительность ожидания блокировок
Ожидания блокировок не превышают секунды, и для тестового окружения это приемлемо. Но есть высокая вероятность, что на самом деле ситуация чуть хуже, чем это представляется из графика,так как pg_locks не аккумулирует статистику и исключает возможность получить полную картину по блокировкам.
Использование pg_stat_activity.state_change
В PostgreSQL до версии 14 отсутствовало поле pg_locks.waitstart,однако необходимость в отслеживании времени ожидания была всегда. В этих версиях СУБД можно использовать менее точный способ с использованиемполя pg_stat_activity.state_change.Поле содержитотметку времени перехода состояния с предыдущего на текущее.Если ожидание включено в активное состояние, то при наличии у процесса маркера ожидания можно учесть время, проведенное
вактивном состоянии, как время, проведенное в ожидании. Недостаток способа заключается
втом, что после перехода в активное состояние ожидание может начаться не сразу, а спустя какое-то время, но весь период будет зачтен как проведенный в ожидании. Это легко продемонстрировать на версии 14,где есть правильный источник pg_locks.waitstart.
Для демонстрации потребуется небольшая таблица, например та, что создается стандартным сценарием pgbench.В эксперименте эта таблица содержит 50000 строк.Понадобится открыть
64Глава 2. Статистика активности
три сеанса к СУБД: первый и второй сеансы—для воспроизведения тестового сценария с блокировкой запроса, третий — для отслеживания статистики. В первом сеансе нужно открыть транзакцию и обновить последнюю строку в тестовой таблице. Транзакцию при этом следует оставить открытой:
#BEGIN;
#UPDATE pgbench_accounts SET abalance = abalance + 1 WHERE aid = 50000;
Во втором сеансе понадобится предварительно узнать pid процесса,после чего запустить полное обновление всей таблицы.
#SELECT pg_backend_pid();
2291758
#UPDATE pgbench_accounts SET abalance = abalance + 1;
В третьем сеансе следует взять статистику из pg_stat_activity и pg_locks. В качестве условия указываем вывод строк только для второго сеанса с идентификатором 2291758. Для удобства отслеживания после первого выполнения запроса стоит запустить метакоманду \watch 1, которая будет повторять запрос раз в секунду.
# SELECT
a.pid, a.state, l.granted, a.wait_event ||'.'|| a.wait_event_type AS wait, (clock_timestamp() - a.state_change)::interval(0) AS state_age, (clock_timestamp() - l.waitstart)::interval(0) AS wait_age
FROM pg_stat_activity a, pg_locks l WHERE a.pid = l.pid AND a.pid = 2291758;
В выводе запроса нас интересуют поля state_age и wait_age, которые и будут демонстрировать отличие способов в подсчете времени ожидания процесса. Полное обновление таблицы будетвыполнятьсядотех пор,пока не будетдостигнута последняя строка,которая ранее была обновлена во все еще незакрытой транзакции.
pid |
| |
state | |
granted | |
wait |
| state_age |
| wait_age |
|
--------- |
+ |
--------+ |
--------- |
+ |
----------------- |
+----------- |
+---------- |
2291758 |
| |
active | |
t |
| WALWrite.LWLock |
| 00:00:08 |
| |
|
2291758 |
| |
active | |
t |
| WALWrite.LWLock |
| 00:00:08 |
| |
|
2291758 |
| |
active | |
t |
| WALWrite.LWLock |
| 00:00:08 |
| |
|
2291758 |
| |
active | |
t |
| WALWrite.LWLock |
| 00:00:08 |
| |
|
Здесь видно, что у процесса состояние active и время смены состояния state_age увеличивается.Значение granted=trueуказываетнато,что ожидания блокировокнети запрос работает. Через какое-то время выполнение запроса остановится,и можно будетувидетьпримерно следующее:
