Добавил:
ИВТ (советую зайти в "Несортированное")rnПИН МАГА Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Database 2024 / Books / Мониторинг PostgreSQL.pdf
Скачиваний:
13
Добавлен:
20.11.2024
Размер:
6.87 Mб
Скачать

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указываетнато,что ожидания блокировокнети запрос работает. Через какое-то время выполнение запроса остановится,и можно будетувидетьпримерно следующее:

Соседние файлы в папке Books