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

2.8. Дерево блокировок

65

pid

| state

| granted

|

wait

| state_age | wait_age

---------

+--------

+---------

+

--------------------

+-----------

+----------

2291758

| active

| t

| transactionid.Lock

| 00:00:14

|

2291758

| active

| t

| transactionid.Lock

| 00:00:14

|

2291758

| active

| t

| transactionid.Lock

| 00:00:14

|

2291758

| active

| f

| transactionid.Lock

| 00:00:14

| 00:00:05

2291758

| active

| t

| transactionid.Lock

| 00:00:14

|

2291758

| active

| t

| transactionid.Lock

| 00:00:14

|

Появилась строка для блокировки, которую не удалось взять, с состоянием granted = false и отсчетом, начавшимся в wait_age. При этом значение state осталось прежним, state_age не сбросился и продолжает отсчитываться. В этом примере получается, что запрос выполнял полезную работу примерно девять секунд до начала ожидания блокировки. Фактически ожидание началось после девяти секунд работы запроса.Это поведение и демонстрируетотличие способов учета с помощью waitstart и state_change. Если вы решите повторить эксперимент, незабудьтевконцеотменитьилизакрытьтранзакцию,этопозволитзапросувовторомсеансе завершиться.

Используя учет времени на основе state_change, следует помнить о таком поведении. Например, в OLTP-нагрузке, преимущественно состоящей из коротких и быстрых запросов, оно не сильно исказит статистику, и этот способ подсчета может показаться приемлемым. А вот в случае более долгих OLAP-запросов (как в эксперименте выше) статистика может оказаться неточной,время ожидания будетнесправедливо включатьв себя время выполнения полезной работы,и способ подсчета может оказаться сомнительным.

2.8. Дерево блокировок

На практике часто бывает так, что в определенные моменты довольно много процессов находятся в ожидании блокировок,причем часть процессов оказываются заблокированы другими процессами. Если в такой ситуации найти источник блокировки и устранить его, то с высокойвероятностьюостальныепроцессысмогутнормальнопродолжитьработу.Однако,неимея подготовленных средств, в такой ситуации довольно сложно точно идентифицировать процесс,который стал причиной всех блокировок.Можно анализировать непосредственно вывод pg_stat_activity и pg_locks, но это неудобно и может занять много времени. Для определения источников блокировок нужно выявить зависимости между процессами и построить дерево зависимостей между заблокированными и блокирующими процессами. Такая визуализация позволит быстро определить источник блокировки и устранить его. Подход к решению таких проблем не является новым, и на просторах интернета можно найти разные реализации, которые позволяют выводить дерево блокировок. Такие запросы основываются на pg_stat_activity и pg_locks и, как правило, занимают несколько страниц, поэтому я не буду приводить их здесь. Такие запросы для удобства использования лучше всего оборачивать в представления.

66Глава 2. Статистика активности

В качестве отправной точки я взял этот запрос1 и немного модифицировал его. Итоговый запрос можно найти в репозитории книги, в файле playground/scripts/locktree.sql2. Ключевой особенностью запроса является использование функции pg_blocking_pids. Функция принимает идентификатор процесса и возвращает список процессов, которые его блокируют. Это довольно удобно и позволяет избежать использования pg_locks, однако, согласно документации,частый вызов этой функции может негативно сказываться на производительности СУБД, таккак функция получаеткратковременныйисключительныйдоступк общемусостояниюменеджера блокировок. Так или иначе, функция удобная, и использование ее в редких случаях поиска не должно быть проблемой. Не следует применять ее регулярно для нужд мониторинга, например для снятия метрик. В определенных условиях эксплуатации (высокая нагрузка, требования к задержкам), когда эта особенность даже для целей отладки оказывается неприемлемой,вместо pg_blocking_pids можно использовать pg_locks3.

Давайте рассмотрим пару примеров получения дерева блокировок в тестовом окружении.

pid

|

blocked_by

| state

|

wait

| wait_age | tx_age

| usename | datname | blkd

|

query

 

---------

+

-------------------

+----------

+

---------------------

+----------

+-----------

+---------

+---------

+

------

+---------------------------------------------------------------------------------------

 

 

2072376

|

{}

| active

| LWLock.WALWrite

|

| 00:00:00

| classic

| pgbench |

1

| [2072376] END;

 

2072427

|

{2072376}

| waiting

| Lock.transactionid

| 00:00:00 | 00:00:00

| maru

| pgbench |

0

| [2072427] . UPDATE pgbench_branches SET bbalance = bbalance +

3135 WHERE bid = 17;

2072386

|

{}

| active

| LWLock.WALWrite

|

| 00:00:01

| classic | pgbench |

1

| [2072386] END;

 

2072382

|

{2072386}

| waiting

| Lock.transactionid

| 00:00:01 | 00:00:01

| classic | pgbench |

0

| [2072382] . UPDATE pgbench_branches SET bbalance = bbalance +

232 WHERE bid = 15;

2072387

|

{}

| active

| LWLock.WALWrite

|

| 00:00:01

| classic | pgbench |

1

| [2072387] END;

 

2072388

|

{2072387}

| waiting

| Lock.transactionid

| 00:00:00 | 00:00:00

| classic | pgbench |

0

| [2072388] . UPDATE pgbench_branches SET bbalance = bbalance +

3169 WHERE bid = 13;

2072394

|

{}

| active

| LWLock.WALWrite

|

| 00:00:01

| classic | pgbench |

1

| [2072394] END;

 

2072391

|

{2072394}

| waiting

| Lock.transactionid

| 00:00:00 | 00:00:00

| classic | pgbench |

0

| [2072391] . UPDATE pgbench_branches SET bbalance = bbalance +

-2825 WHERE bid = 6;

2072399

|

{}

| active

| LWLock.WALWrite

|

| 00:00:01

| classic | pgbench |

1

| [2072399] END;

 

2072390

|

{2072399}

| waiting

| Lock.transactionid

| 00:00:01 | 00:00:01

| classic

| pgbench |

0

| [2072390] . UPDATE pgbench_branches SET bbalance = bbalance +

-122 WHERE bid = 7;

2072415

|

{}

| active

| LWLock.WALWrite

|

| 00:00:01

| maru

| pgbench |

1

| [2072415] END;

 

2072418

|

{2072415}

| waiting

| Lock.transactionid

| 00:00:00 | 00:00:00

| maru

| pgbench |

0

| [2072418] . UPDATE pgbench_branches SET bbalance = bbalance +

-3319 WHERE bid = 12;

2072423

|

{}

| active

| IO.WALSync

|

| 00:00:01

| maru

| pgbench |

2

| [2072423] END;

 

2072410

|

{2072423}

| waiting

| Lock.transactionid

| 00:00:01 | 00:00:01

| serral

| pgbench |

0

| [2072410] . UPDATE pgbench_tellers SET tbalance = tbalance + 1404 WHERE tid = 18;

2072416

|

{2072423}

| waiting

| Lock.transactionid

| 00:00:00 | 00:00:00

| maru

| pgbench |

0

| [2072416] . UPDATE pgbench_branches SET bbalance = bbalance +

1468 WHERE bid = 11;

2072424

|

{}

| active

| LWLock.WALWrite

|

| 00:00:01

| maru

| pgbench |

4

| [2072424] END;

 

2072408

|

{2072424}

| waiting

| Lock.transactionid

| 00:00:01 | 00:00:01

| serral

| pgbench |

3

| [2072408] . UPDATE pgbench_branches SET bbalance = bbalance +

-1605 WHERE bid = 8;

2072392

|

{2072408}

| waiting

| Lock.tuple

| 00:00:00 | 00:00:00

| classic

| pgbench |

2

| [2072392] ..

UPDATE pgbench_branches SET bbalance = bbalance + -3889 WHERE bid = 8;

2072409

|

{2072408,2072392}

| waiting

| Lock.tuple

| 00:00:00 | 00:00:00

| serral

| pgbench |

1

| [2072409] ...

UPDATE pgbench_branches SET bbalance = bbalance

+ 4620 WHERE bid = 8;

2072419

|

{2072409}

| waiting

| Lock.transactionid

| 00:00:00 | 00:00:00

| maru

| pgbench |

0

| [2072419] ....

UPDATE pgbench_tellers SET tbalance = tbalance

+ 2315 WHERE tid = 180;

Блокировки, представленные в первом примере, часто возникают при конкурентном обновлении данных. Каждая строка вывода описывает отдельный процесс и детализацию сеанса на основе pg_stat_activity.Давайте рассмотрим поля этого запроса:

pid—идентификатор процесса;

blocked_by — список процессов, которые блокируют текущий процесс. Рассчитывается на основе функции pg_blocking_pids;

state— состояние процесса с дополненным псевдосостоянием waiting на основе условия wait_event_type = 'Lock';

wait—маркер ожидания,составленный из wait_event_type и wait_event;

wait_age—длительность ожидания процесса на основе pg_locks.waitstart;

tx_age—длительностьтранзакции;

1 postgres.ai/blog/20211018-postgresql-lock-trees

2 github.com/lesovsky/postgresql-monitoring-book/blob/main/playground/scripts/locktree.sql 3 dev.to/bolajiwahab/2022-01-13-postgresql-lock-trees-56e0

2.8. Дерево блокировок

67

usename—имя пользователя;

datname—имя базы данных;

blkd—количество других процессов,заблокированных данным;

query—текст запроса. Содержит идентификатор процесса и его уровень в своей ветви дерева блокировок.

Все процессы можно условно разделить на группы, в которых есть основной источник блокировки, который никем не блокируется, и есть заблокированные процессы, которые могут также блокировать других участников группы. Давайте более внимательно рассмотрим последнюю группу, где главным процессом, который заблокировал остальных участников, является процесс с идентификатором 2072424.Потексту запроса видно,что это фиксациятранзакции (END, или COMMIT), сам процесс находится в активном состоянии и его маркер ожидания — LWLock.WALWrite. Это указывает на то, что происходит запись в WAL-журнал, и пока она не завершится, клиент не получит возможность отправлять другие команды или открывать транзакции.Такоеповедениехарактерноврежимесинхроннойфиксации(см.synchronous_commit), который используется по умолчанию.По полю blkd можно увидеть,что фиксациятранзакции блокирует еще четыре процесса, которые описаны в строках ниже. По полям pid и blocked_by можно проследить взаимосвязи процессов и кто кого блокирует. Также глубину блокировки удобноотслеживатьпосимволуточкивполеquery.Потекстузапросавэтомжеполевидно,что трипроцессапытаютсяобновитьоднуитужестрокупоусловиюWHEREbid=8.Вероятнейвсего, эта строка была обновлена втранзакции,которая фиксируется вданный момент,чем и вызвана очередь ожидания. Поля state и wait указывают на то, что все четыре процесса находятся в ожидании блокировок. Судя по полю wait_age, время ожидания составляет меньше одной секунды — это вполне приемлемо для тестовой рабочей нагрузки, а вот в производственной нагрузкедажетакие короткие ожидания могутстатьпричиной задержек в приложениях.Поля usename и datname указывают, от имени каких пользователей и в какой базе данных возникли блокировки.

Рассмотримещеодинпримердереваблокировок,которыйможетвозникнутьвтестовомокружении.

pid | blocked_by | state | wait | wait_age | tx_age | usename | datname | blkd | query

------+------------+---------+--------------------+----------+----------+----------+---------+------+-----------------------------------------------------------------------------------------------...

161

| {}

| active |

IO.WALSync

|

| 00:00:00 | postgres | pgbench |

20

|

[161]

TRUNCATE

pgbench_history ;

 

 

 

 

1923

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | serral

| pgbench |

0

|

[1923]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (15,

13, 1518573,...

1924

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | serral

| pgbench |

2

|

[1924]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (46,

6, 589135, -...

1925

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:01 | serral

| pgbench |

1

|

[1925]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (106, 12, 100334,...

1929

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | serral

| pgbench |

0

|

[1929]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (93,

18, 1330287,...

1936

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | maru

| pgbench |

0

|

[1936]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (99,

7, 914556, 1...

1942

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | maru

| pgbench |

3

|

[1942]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (61,

20, 669763, ...

1957

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | classic

| pgbench |

0

|

[1957]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (104, 4, 880250, ...

1963

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | classic

| pgbench |

0

|

[1963]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (162, 8, 1371725,...

1964

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | classic

| pgbench |

0

|

[1964]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (69,

9, 71583, 18...

1971

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:01 | classic

| pgbench |

1

|

[1971]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (44,

14, 1331579,...

1976

| {161}

| waiting |

Lock. relation

| 00:00:00 | 00:00:00 | classic

| pgbench |

2

|

[1976]

. INSERT

INTO pgbench_history (tid, bid,

aid, delta,

mtime)

VALUES (60,

17, 1238351,...

955

| {1942}

| waiting |

Lock.transactionid | 00:00:00 | 00:00:00 | pgbench

| pgbench |

1

|

[955]

.. UPDATE pgbench_branches SET bbalance = bbalance +

-2769 WHERE bid = 20;

1926

| {1976}

| waiting |

Lock.transactionid | 00:00:00 | 00:00:00 | serral

| pgbench |

0

|

[1926]

.. UPDATE pgbench_branches SET bbalance = bbalance +

-410 WHERE bid = 17;

1927

| {1971}

| waiting |

Lock.transactionid | 00:00:00 | 00:00:00 | serral

| pgbench |

0

|

[1927]

.. UPDATE pgbench_branches SET bbalance = bbalance +

-4047 WHERE bid = 14;

1930

| {1924}

| waiting |

Lock.transactionid | 00:00:00 | 00:00:00 | serral

| pgbench |

1

|

[1930]

.. UPDATE pgbench_branches SET bbalance = bbalance +

-3100 WHERE bid = 6;

1938

| {1925}

| waiting |

Lock.transactionid | 00:00:00 | 00:00:00 | maru

| pgbench |

0

|

[1938]

.. UPDATE pgbench_branches SET bbalance = bbalance +

608 WHERE bid = 12;

 

1955

| {1976}

| waiting |

Lock.transactionid | 00:00:00 | 00:00:00 | classic

| pgbench |

0

|

[1955]

.. UPDATE pgbench_branches SET bbalance = bbalance +

1745 WHERE bid = 17;

1966

| {1942}

| waiting |

Lock.transactionid | 00:00:00 | 00:00:00 | classic

| pgbench |

0

|

[1966]

.. UPDATE pgbench_branches SET bbalance = bbalance +

-4835 WHERE bid = 20;

1931

| {955}

| waiting |

Lock.transactionid | 00:00:00 | 00:00:00 | serral

| pgbench |

0

|

[1931]

... UPDATE pgbench_tellers SET tbalance = tbalance +

-4006 WHERE tid = 182;

1972

| {1930}

| waiting |

Lock.tuple

| 00:00:00 | 00:00:00 | classic

| pgbench |

0

|

[1972]

... UPDATE pgbench_branches SET bbalance

= bbalance + -3771

WHERE bid =

6;

68Глава 2. Статистика активности

Вэтомпримереисточникомблокировкивыступаетпроцессскомандойопустошениятаблицы (TRUNCATE). Это довольно быстрая операция, заключающаяся в замене файла данных на пустой, однако сначала ей нужно дождаться завершения ранее запущенных запросов к целевой таблице. В момент опустошения таблица блокируется, поэтому все процессы, обращающиеся в нее, должны дождаться завершения команды. Поле blkd показывает, что процесс TRUNCATE (pid = 161) блокирует выполнение еще двадцати процессов. Примерно половина из них пытаются вставить строки в опустошаемую таблицу — у всех этих процессов в поле blocked_by указанидентификаторпроцессаTRUNCATE.Другаячастьпроцессов—этооперацииобновления, которые, в свою очередь, ожидают выполнения вставок. Подтвердить это можно, сопоставив значения идентификаторов blocked_by с полем pid процессов, выполняющих вставку. Также можносопоставитьзначения поляbidвзапросах—онобудетсовпадатьвотдельныхцепочках ожидающих запросов: например, процесс 955 обновляет строку с bid = 20 и ожидает завершения вставки в процессе 1942, который сам ожидает завершения команды TRUNCATE. Состояние и маркеры ожидания процесса,выполняющего команду TRUNCATE,показывают,что процесс ак- тивенисинхронизируетWAL-сегментсфайломнадиске(вызовомfsyncилидругимспособом, который указан в параметре wal_sync_method).Еще одним способом определения причин ожиданийявляетсяисследованиезаписейоблокировкахвжурналесообщенийСУБД(приусловии, что включен параметр log_lock_waits и ожидание составило больше, чем значение параметра deadlock_timeout,по умолчанию равное одной секунде).

Оба рассмотренных случая относятся к рабочей нагрузке в тестовом окружении, которое характеризуется короткими транзакциями и такими же короткими блокировками. На практике все может быть намного сложнее, особенно если источником блокировок выступают бездействующие транзакции,которые могут удерживать блокировки непредсказуемо долго и потенциально приводить к аварийным ситуациям.Порядок действия в таких ситуациях,как правило,сводится к принудительному завершению процессов.В критической ситуации важно быстросориентироватьсяинайтитоткорневойпроцесс,завершениекоторогоразрешитситуацию. При недостатке информации часто приходится без разбора завершать множество процессов. В случае же использования запросов,подобных рассмотренному,можно с большейточностью определить и устранить источник блокировки, позволив остальным процессам продолжить работу.

Резюме

Статистика клиентской активности позволяет понять,что происходит в СУБД.

СУБД предоставляет три важных источника данных о клиентской активности: представ-

ления pg_stat_activity,pg_locks и pg_stat_database.

И администраторы БД, и инструменты мониторинга получают данные из представлений с помощью SQL-запросов.

Клиентский сеанс в процессе существования может пребывать в разных состояниях.

Резюме 69

Транзакционная активность помогает быстро оценить нагрузку в кластере баз данных.

Важно вовремя отслеживать и устранять опасные ожидания и блокировки.

Следует устранять или минимизировать нахождение транзакций в бездействующем состоянии.

Продолжительность клиентской активности прямо влияет на производительность.

Ожидание блокировок снижает производительность СУБД и приложений.

Дерево блокировок позволяет быстро выявить источник блокировок.

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