[PostgreSQL, SQL, Администрирование баз данных] PostgreSQL Antipatterns: анализируем блокировки — SELF JOIN vs WINDOW
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Ранее мы уже научились перехватывать блокировки из лога сервера PostgreSQL. Давайте теперь положим их в БД и разберем, какие фактические ошибки и проблемы производительности можно допустить на примере их простейшего анализа.
В логах у нас отражается всего 3 вида событий, которые могут происходить с блокировкой:
- ожидание блокировки
LOG: process 38162 still waiting for ExclusiveLock on advisory lock [225382138,225386226,141586103,2] after 100.047 ms
- получение блокировки
LOG: process 38162 acquired ExclusiveLock on advisory lock [225382138,225386226,141586103,2] after 150.741 ms
- взаимоблокировка
ERROR: deadlock detected
deadlock'и исключим из анализа — это просто ошибки, и попробуем выяснить, сколько всего времени мы потеряли из-за блокировок за конкретный день на определенном хосте.
Для начала нам понадобится таблица, куда мы будем собирать все такие записи:
CREATE TABLE lock(
dt -- ключ хронологического секционирования
date
, host -- сервер, на котором возникла блокировка
uuid
, pid -- PID процесса из строки лога
integer
, ts -- момент события
timestamp
, event -- { lock-wait | lock-acquire | deadlock-detect }
lockevent
, type -- { relation | extend | ... }
locktype
, mode -- { AccessShare | RowShare | ... }
lockmode
, lock -- объект блокировки
uuid
, exectime -- продолжительность
numeric(32,2)
);
Более подробно про организацию секционирования в нашей системе мониторинга можно прочитать в статье «Пишем в PostgreSQL на субсветовой: 1 host, 1 day, 1TB», а про различные типы и режимы блокировок — в «DBA: кто скрывается за блокировкой».
Как слышится, так и пишется
Попробуем ответить на вопрос, вынесенный в начало статьи, простейшим способом.
Что такое время ожидания блокировки? Ну, очевидно же, — это время ее получения для каждого случая ее ожидания:
- берем каждый случай ожидания (lock-wait)
- для него находим ближайшую «снизу» по времени запись получения (lock-acquire) «этой же» (lock, pid, mode) блокировки — то есть на тот же объект, в том же процессе, с тем же режимом
Тип блокировки (type) в нашем случае можно опустить, поскольку он однозначно определяется самим объектом (lock).
Дальше останется только просуммировать полученные результаты.
SELECT
ts
, pid
, event
, type
, mode
, lock
, exectime
, T.*
FROM
lock lc
, LATERAL (
SELECT
exectime waittime
FROM
lock
WHERE
(
dt
, host
, lock
, pid
, mode
, event
) = (
'2020-06-19'::date
, lc.host
, lc.lock
, lc.pid
, lc.mode
, 'lock-acquire'
) AND
ts >= lc.ts
ORDER BY
ts
LIMIT 1
) T
WHERE
(
lc.dt
, lc.host
, lc.event
) = (
'2020-06-19'::date
, 'e828a54d-7e8a-43dd-b213-30c3201a6d8e'::uuid
, 'lock-wait'::lockevent
);
Все просто и ясно! А что нам покажет EXPLAIN?..
Оу… пришлось прочитать почти 900MB данных, причем почти все — из-за поиска связанной записи для каждой блокировки, не очень красиво.
Но является ли этот запрос вообще корректным для нашей задачи? Нет! Посмотрим внимательно в собранные данные:
Ой… Оказывается, сервер «жалуется» в логи на одну и ту же неполученную блокировку иногда много-много раз. А это означает, что мы учли время ее ожидания кратно количеству таких записей в логе, что совсем не соответствует желаемому.
Помни о цели!
Собственно, а зачем мы вообще для каждой записи ожидания ищем связанную? Мы же хотим узнать, сколько заняло ожидание, а оно прямо записано в lock-acquire. Так давайте сразу отбирать только их, тогда будет всего лишь один Index Scan — правильно?
Почти, да не совсем. Дело в том, что под нагрузкой лог-процесс может пропускать любые из записей — хоть о начале ожидания блокировке, хоть о факте ее получения:
Так неужели нет способа за одно чтение сразу получить все нужные нам данные?
Window Functions: семерых одним ударом
На помощь нам придут оконные функции.
А конкретнее — модель выделения «цепочек» в готовой выборке из статьи «SQL HowTo: собираем «цепочки» с помощью window functions».
Сначала поймем, что условием окончания «цепочки» — то есть сегмента подряд идущих по ключу (host, lock, pid, mode) записей блокировки — для нас является или явное возникновение event = 'lock-acquire' или (что очень редко, но бывает) начало нового сегмента блокировки того же объекта, чья длительность (exectime) начала считаться заново.
Также надо учесть тот факт, что время может совпадать для нескольких записей лога даже с одного PID. В этом случае надо дополнительно сортировать по exectime, чтобы получить правильную последовательность:
-- формируем условие окончания блокировки
WITH lc AS (
SELECT
*
, CASE
WHEN event = 'lock-wait' THEN
exectime > coalesce(lead(exectime) OVER(PARTITION BY lock, pid, mode ORDER BY ts, exectime), 0) -- "перелом" времени ожидания
ELSE TRUE -- 'lock-acquire' - блокировка получена
END cond -- условие окончания "цепочки"
FROM
lock lc
WHERE
event <> 'deadlock-detect' AND -- исключаем все deadlock
(
lc.dt
, lc.host
) = (
'2020-06-19'::date
, 'e828a54d-7e8a-43dd-b213-30c3201a6d8e'::uuid
)
)
-- оставляем только "последние" записи - их exectime и есть время ожидания "всей" блокировки
SELECT
ts
, pid
, event
, type
, mode
, lock
, exectime
FROM
lc
WHERE
cond;
Теперь мы прочитали всего 8MB данных (в 100 раз меньше!), чуть-чуть уменьшив итоговое время выполнения.
Его можно уменьшить еще, если создать индекс, идеально подходящий под OVER (то есть включающий lock, pid, mode, ts, exectime), избавившись от Sort-узла. Но обычно поле в индексе «за timestamp» делать не стоит.
===========
Источник:
habr.com
===========
Похожие новости:
- [DevOps, PostgreSQL, Администрирование баз данных, Системное администрирование] Готовим PostgreSQL в эпоху DevOps. Опыт 2ГИС. Павел Молявин
- [IT-инфраструктура, Open source, Администрирование баз данных, Высокая производительность, Хранение данных] Что нужно знать об архитектуре ClickHouse, чтобы его эффективно использовать. Алексей Зателепин (2018г)
- [Администрирование баз данных, Законодательство в IT, Информационная безопасность] Хакер взламывает базы данных MongoDB и требует выкуп с угрозами слить персданные и сообщить в органы защиты GDPR
- [MySQL, Python, Машинное обучение] Web server for Machine Learning 'VKF-solver'
- [Машинное обучение, MySQL, Python] Web-сервер машинного обучения «ВКФ-решатель»
- [PostgreSQL, Ruby on Rails, Разработка мобильных приложений] Оптимизация SQL запросов или розыск опасных преступников
- [Microsoft SQL Server, SQL, Алгоритмы] Быстрый поиск без индекса (перевод)
- [IT-стандарты, Интерфейсы, Спортивное программирование, Функциональное программирование] Минимизация кликов и горячие клавиши для жизни разработчика + Темнее Тёмной Темноты
- [SQL, SQLite, Веб-аналитика, Разработка на Raspberry Pi, Хранилища данных] Сколько данных может обработать Raspberry Pi быстро
- [NoSQL, Open source] Создатель СУБД Redis уходит от сопровождения проекта
Теги для поиска: #_postgresql, #_sql, #_administrirovanie_baz_dannyh (Администрирование баз данных), #_postgresql, #_dba, #_explain, #_bazy_dannyh (базы данных), #_sql_tips_and_tricks, #_window_functions, #_blog_kompanii_tenzor (
Блог компании Тензор
), #_postgresql, #_sql, #_administrirovanie_baz_dannyh (
Администрирование баз данных
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 16:11
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Ранее мы уже научились перехватывать блокировки из лога сервера PostgreSQL. Давайте теперь положим их в БД и разберем, какие фактические ошибки и проблемы производительности можно допустить на примере их простейшего анализа. В логах у нас отражается всего 3 вида событий, которые могут происходить с блокировкой:
deadlock'и исключим из анализа — это просто ошибки, и попробуем выяснить, сколько всего времени мы потеряли из-за блокировок за конкретный день на определенном хосте. Для начала нам понадобится таблица, куда мы будем собирать все такие записи: CREATE TABLE lock(
dt -- ключ хронологического секционирования date , host -- сервер, на котором возникла блокировка uuid , pid -- PID процесса из строки лога integer , ts -- момент события timestamp , event -- { lock-wait | lock-acquire | deadlock-detect } lockevent , type -- { relation | extend | ... } locktype , mode -- { AccessShare | RowShare | ... } lockmode , lock -- объект блокировки uuid , exectime -- продолжительность numeric(32,2) ); Более подробно про организацию секционирования в нашей системе мониторинга можно прочитать в статье «Пишем в PostgreSQL на субсветовой: 1 host, 1 day, 1TB», а про различные типы и режимы блокировок — в «DBA: кто скрывается за блокировкой».
Как слышится, так и пишется Попробуем ответить на вопрос, вынесенный в начало статьи, простейшим способом. Что такое время ожидания блокировки? Ну, очевидно же, — это время ее получения для каждого случая ее ожидания:
Тип блокировки (type) в нашем случае можно опустить, поскольку он однозначно определяется самим объектом (lock). Дальше останется только просуммировать полученные результаты. SELECT
ts , pid , event , type , mode , lock , exectime , T.* FROM lock lc , LATERAL ( SELECT exectime waittime FROM lock WHERE ( dt , host , lock , pid , mode , event ) = ( '2020-06-19'::date , lc.host , lc.lock , lc.pid , lc.mode , 'lock-acquire' ) AND ts >= lc.ts ORDER BY ts LIMIT 1 ) T WHERE ( lc.dt , lc.host , lc.event ) = ( '2020-06-19'::date , 'e828a54d-7e8a-43dd-b213-30c3201a6d8e'::uuid , 'lock-wait'::lockevent ); Все просто и ясно! А что нам покажет EXPLAIN?.. Оу… пришлось прочитать почти 900MB данных, причем почти все — из-за поиска связанной записи для каждой блокировки, не очень красиво. Но является ли этот запрос вообще корректным для нашей задачи? Нет! Посмотрим внимательно в собранные данные: Ой… Оказывается, сервер «жалуется» в логи на одну и ту же неполученную блокировку иногда много-много раз. А это означает, что мы учли время ее ожидания кратно количеству таких записей в логе, что совсем не соответствует желаемому. Помни о цели! Собственно, а зачем мы вообще для каждой записи ожидания ищем связанную? Мы же хотим узнать, сколько заняло ожидание, а оно прямо записано в lock-acquire. Так давайте сразу отбирать только их, тогда будет всего лишь один Index Scan — правильно? Почти, да не совсем. Дело в том, что под нагрузкой лог-процесс может пропускать любые из записей — хоть о начале ожидания блокировке, хоть о факте ее получения: Так неужели нет способа за одно чтение сразу получить все нужные нам данные? Window Functions: семерых одним ударом На помощь нам придут оконные функции. А конкретнее — модель выделения «цепочек» в готовой выборке из статьи «SQL HowTo: собираем «цепочки» с помощью window functions». Сначала поймем, что условием окончания «цепочки» — то есть сегмента подряд идущих по ключу (host, lock, pid, mode) записей блокировки — для нас является или явное возникновение event = 'lock-acquire' или (что очень редко, но бывает) начало нового сегмента блокировки того же объекта, чья длительность (exectime) начала считаться заново. Также надо учесть тот факт, что время может совпадать для нескольких записей лога даже с одного PID. В этом случае надо дополнительно сортировать по exectime, чтобы получить правильную последовательность: -- формируем условие окончания блокировки
WITH lc AS ( SELECT * , CASE WHEN event = 'lock-wait' THEN exectime > coalesce(lead(exectime) OVER(PARTITION BY lock, pid, mode ORDER BY ts, exectime), 0) -- "перелом" времени ожидания ELSE TRUE -- 'lock-acquire' - блокировка получена END cond -- условие окончания "цепочки" FROM lock lc WHERE event <> 'deadlock-detect' AND -- исключаем все deadlock ( lc.dt , lc.host ) = ( '2020-06-19'::date , 'e828a54d-7e8a-43dd-b213-30c3201a6d8e'::uuid ) ) -- оставляем только "последние" записи - их exectime и есть время ожидания "всей" блокировки SELECT ts , pid , event , type , mode , lock , exectime FROM lc WHERE cond; Теперь мы прочитали всего 8MB данных (в 100 раз меньше!), чуть-чуть уменьшив итоговое время выполнения. Его можно уменьшить еще, если создать индекс, идеально подходящий под OVER (то есть включающий lock, pid, mode, ts, exectime), избавившись от Sort-узла. Но обычно поле в индексе «за timestamp» делать не стоит. =========== Источник: habr.com =========== Похожие новости:
Блог компании Тензор ), #_postgresql, #_sql, #_administrirovanie_baz_dannyh ( Администрирование баз данных ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 16:11
Часовой пояс: UTC + 5