Люди, имеющие опыт работы с коммерческими СУБД, привыкли к тому, что могут получить ответ на вопрос “Чем прямо сейчас занимается конкретная сессия?” Или ещё лучше “Чего ждала каждая сессия 5 минут назад?” Долгое время PostgreSQL не имел таких средств диагностики и DBA приходилось выкручиваться разной степени изощрённости способами. О том, как это делаем мы, я рассказывал на pgday.ru. Этот доклад я читал не один, а вместе с Ильдусом Курбангалиевым из PostgrePro. И Ильдус как раз рассказывал об инструменте, который позволяет ответить на вопросы выше.
Строго говоря, это далеко не первая попытка реализовать то, что люди привыкли
называть интерфейсом [событий] ожиданий, но все предыдущие не были доведены
до какого-либо разумного состояния, оставаясь proof of concept патчами. А вот
pg_stat_wait
вполне себе доступен в виде набора патчей к текущей стабильной
ветке 9.4 и
разрабатываемой нынче 9.6 (актуальные версии стоит искать в pgsql-hackers@).
После довольно продолжительного тестирования и исправления ряда багов мы не просто посчитали эти патчи полезными, но даже пригодными для использования в бою. Довольно долго мы катили эти изменения в production и ничего такого не случилось :)
Установка
До того, как всё это попадёт в ядро PostgreSQL, нужно пересобирать postgres.
Пересборку в виде ./configure && make && sudo make install
, думаю, описывать
смысла нет - лучше посмотреть в
документации.
После этого в shared_preload_libraries
надо будет добавить pg_stat_wait
.
Кроме того, в postgresql.conf
можно добавить следующие опции:
waits_monitoring = on
- включение функциональности как таковой,pg_stat_wait.history = on
- хранение истории ожиданий,pg_stat_wait.history_size = 1000000
- количество событий в истории,pg_stat_wait.history_period = 1000
- как часто сохранять события ожидания в историю (мс).
После этого стоит запустить PostgreSQL и сказать CREATE EXTENSION
pg_stat_wait
. После этого всё начнёт работать.
Возможности
А что именно начнёт работать? Первым делом стоит посмотреть, что входит в состав расширения:
rpopdb01g/postgres M # \dxS+ pg_stat_wait
Objects in extension "pg_stat_wait"
Object Description
---------------------------------------------------------
function pg_is_in_trace(integer)
function pg_start_trace(integer,cstring)
function pg_stat_wait_get_current(integer)
function pg_stat_wait_get_history()
function pg_stat_wait_get_profile(integer,boolean)
function pg_stat_wait_make_test_lwlock(integer,integer)
function pg_stat_wait_reset_profile()
function pg_stop_trace(integer)
function pg_wait_class_list()
function pg_wait_event_list()
view pg_stat_wait_current
view pg_stat_wait_history
view pg_stat_wait_profile
view pg_wait_class
view pg_wait_event
view pg_wait_events
(16 rows)
rpopdb01g/postgres M #
Давайте посмотрим, какие события ожидания pg_stat_wait
умеет мониторить:
rpopdb01g/postgres M # SELECT version();
version
---------------------------------------------------------------------------------------------------------------
PostgreSQL 9.4.5 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11), 64-bit
(1 row)
rpopdb01g/postgres M # SELECT class_name, count(event_name)
FROM pg_wait_events GROUP BY 1 ORDER BY 2 DESC;
class_name | count
------------+-------
LWLocks | 52
Storage | 9
Locks | 9
Network | 3
Latch | 1
CPU | 1
(6 rows)
rpopdb01g/postgres M #
Можно увидеть, что мониторинг ожиданий для 9.4 знает 52 типа легковесных блокировок, а например, для диска умеет отслеживать следующие вещи:
rpopdb01g/postgres M # SELECT * FROM pg_wait_events WHERE class_id = 3;
class_id | class_name | event_id | event_name
----------+------------+----------+------------
3 | Storage | 0 | SMGR_READ
3 | Storage | 1 | SMGR_WRITE
3 | Storage | 2 | SMGR_FSYNC
3 | Storage | 3 | XLOG_READ
3 | Storage | 4 | XLOG_WRITE
3 | Storage | 5 | XLOG_FSYNC
3 | Storage | 6 | SLRU_READ
3 | Storage | 7 | SLRU_WRITE
3 | Storage | 8 | SLRU_FSYNC
(9 rows)
rpopdb01g/postgres M #
Под “умеет отслеживать” понимается тот факт, что можно посмотреть:
- Чего прямо сейчас ждёт конкретный процесс и как долго?
- Сколько раз конкретный процесс повисал в ожидании каждого события и как много времени суммарно провёл в ожидании?
- Чего ждал конкретный процесс какое-то время назад?
Для ответов на эти вопросы есть представления pg_stat_wait_current
,
pg_stat_wait_profile
и pg_stat_wait_history
соответственно. Лучше всего
рассмотреть на примерах.
pg_stat_wait_current
rpopdb01g/postgres M # SELECT pid, class_name, event_name, wait_time
FROM pg_stat_wait_current WHERE class_id NOT IN (4, 5)
ORDER BY wait_time DESC;
pid | class_name | event_name | wait_time
-------+------------+---------------+-----------
23510 | LWLocks | BufferLWLocks | 17184
23537 | LWLocks | BufferLWLocks | 9367
23628 | LWLocks | BufferLWLocks | 9366
23502 | LWLocks | BufferLWLocks | 3215
23504 | LWLocks | BufferLWLocks | 2846
23533 | LWLocks | BufferLWLocks | 2788
23514 | LWLocks | BufferLWLocks | 2658
23517 | LWLocks | BufferLWLocks | 2658
23532 | LWLocks | BufferLWLocks | 2641
23527 | LWLocks | BufferLWLocks | 2507
23952 | Storage | SMGR_READ | 2502
23518 | Storage | XLOG_FSYNC | 1576
23524 | LWLocks | WALWriteLock | 1027
(13 rows)
rpopdb01g/postgres M #
Мы исключаем ожидания сети и латчей, поскольку время их ожидания обычно на несколько порядков больше времени ожидания остальных классов. Ну и это далеко не все столбцы, которые есть в представлении:
smcdb01d/postgres M # SELECT * FROM pg_stat_wait_current
WHERE class_id IN (2, 3) LIMIT 2;
-[ RECORD 1 ]-----------------------------
pid | 12107
sample_ts | 2015-11-16 10:36:59.598562+03
class_id | 2
class_name | Locks
event_id | 4
event_name | Transaction
wait_time | 24334
p1 | 5
p2 | 255593733
p3 | 0
p4 | 0
p5 | 0
-[ RECORD 2 ]-----------------------------
pid | 1266
sample_ts | 2015-11-16 10:36:59.598562+03
class_id | 3
class_name | Storage
event_id | 0
event_name | SMGR_READ
wait_time | 1710
p1 | 1663
p2 | 16400
p3 | 20508
p4 | 0
p5 | 220036
smcdb01d/postgres M #
Параметры p1
-p5
— это текстовые поля. Например, для heavy-weight блокировок
они дают примерно ту же информацию, что можно найти в pg_locks
, а для
событий дискового I/O можно понять, из каких базы, отношения, блока мы ожидали чтения.
pg_stat_wait_profile
Например, можно посмотреть, сколько времени база тратила в каждом из типов ожиданий:
rpopdb01g/postgres M # SELECT class_name, sum(wait_time) AS wait_time,
sum(wait_count) AS wait_count FROM pg_stat_wait_profile
GROUP BY class_name ORDER BY wait_time DESC;
class_name | wait_time | wait_count
------------+--------------+------------
Network | 144196945815 | 11877848
Latch | 90164921148 | 3521073
LWLocks | 2648490737 | 10501900
Storage | 977430136 | 36444251
CPU | 68890774 | 365699457
Locks | 74 | 1
(6 rows)
rpopdb01g/postgres M #
Или, например, какие легковесные блокироки являются самыми горячими:
rpopdb01g/postgres M # SELECT event_name, sum(wait_time) AS wait_time,
sum(wait_count) AS wait_count FROM pg_stat_wait_profile
WHERE class_id = 1 AND wait_time != 0 AND wait_count != 0
GROUP BY event_name ORDER BY wait_time DESC;
event_name | wait_time | wait_count
----------------------+------------+------------
LockMgrLWLocks | 1873294341 | 3870685
WALWriteLock | 1039279117 | 859101
BufferLWLocks | 299153931 | 7356555
BufFreelistLock | 7466923 | 75484
ProcArrayLock | 2321769 | 34355
CLogControlLock | 778148 | 21286
WALInsertLocks | 456224 | 7451
BufferMgrLocks | 107374 | 8447
XidGenLock | 84914 | 2506
UserDefinedLocks | 1875 | 7
CLogBufferLocks | 868 | 80
SInvalWriteLock | 11 | 3
CheckpointerCommLock | 1 | 1
(13 rows)
Time: 29.388 ms
rpopdb01g/postgres M #
Эти два примера хорошо показывают, что время ожидания не всегда коррелирует с количеством самих ожиданий, а потому семплирование без учёта времени ожиданий может давать не слишком правильную картину мира.
pg_stat_wait_history
Это представление позволяет увидеть, чего ожидал конкретный процесс в прошлом. Глубина хранения и интервал семплирования данных настраивается, как описано выше.
xivadb01e/postgres M # SELECT sample_ts, class_name, event_name, wait_time
FROM pg_stat_wait_history WHERE pid = 29585 ORDER BY sample_ts DESC LIMIT 10;
sample_ts | class_name | event_name | wait_time
-------------------------------+------------+-----------------+-----------
2015-11-16 10:56:28.544052+03 | LWLocks | BufferMgrLocks | 983997
2015-11-16 10:56:27.542938+03 | LWLocks | CLogControlLock | 655975
2015-11-16 10:56:26.850302+03 | LWLocks | WALInsertLocks | 979516
2015-11-16 10:56:25.849207+03 | LWLocks | WALInsertLocks | 207418
2015-11-16 10:56:24.848059+03 | LWLocks | WALInsertLocks | 923916
2015-11-16 10:56:23.846909+03 | LWLocks | WALInsertLocks | 753185
2015-11-16 10:56:22.845808+03 | LWLocks | WALInsertLocks | 877707
2015-11-16 10:56:21.844718+03 | LWLocks | WALInsertLocks | 778897
2015-11-16 10:56:20.843562+03 | LWLocks | CLogControlLock | 991267
2015-11-16 10:56:19.842464+03 | LWLocks | CLogControlLock | 1001059
(10 rows)
xivadb01e/postgres M #
Трассировка сессии
Все описанные выше представления рассчитаны на то, что они могут быть включены
всегда, т.е. они сделаны с минимальным overhead’ом по производительности. Но
бывают случаи, когда семплирования раз в pg_stat_wait.history_period
недостаточно и нужно увидеть все события ожидания процесса. В этом случае стоит
использовать функции для трассировки, например, так:
rpopdb01g/postgres M # SELECT pg_backend_pid();
pg_backend_pid
----------------
5399
(1 row)
rpopdb01g/postgres M # SELECT pg_start_trace(5399, '/tmp/5399.trace');
INFO: 00000: Trace was started to: /tmp/5399.trace
LOCATION: StartWait, wait.c:259
pg_start_trace
----------------
(1 row)
rpopdb01g/postgres M # SELECT pg_is_in_trace(5399);
pg_is_in_trace
----------------
t
(1 row)
-- some activity
rpopdb01g/postgres M # SELECT pg_stop_trace(5399);
INFO: 00000: Trace was stopped
LOCATION: StartWait, wait.c:265
pg_stop_trace
---------------
(1 row)
rpopdb01g/postgres M #
Будет создан обычный текстовый файл, где на каждое событие ожидания будет записываться две строки, например:
start 2015-11-16 11:17:26.831686+03 CPU MemAllocation 0 0 0 0 0
stop 2015-11-16 11:17:26.831695+03 CPU
start 2015-11-16 11:17:26.831705+03 LWLocks BufferLWLocks 122 1 0 0 0
stop 2015-11-16 11:17:26.831715+03 LWLocks
start 2015-11-16 11:17:26.831738+03 Network WRITE 0 0 0 0 0
stop 2015-11-16 11:17:26.831749+03 Network
start 2015-11-16 11:17:26.831795+03 Network READ 0 0 0 0 0
stop 2015-11-16 11:17:26.831808+03 Network
start 2015-11-16 11:17:26.831825+03 Storage SMGR_READ 1663 13003 12763 0 13
stop 2015-11-16 11:17:26.831844+03 Storage
Вместо заключения
Интерфейс ожиданий — долгожданная функциональность в PostgreSQL, которая позволяет значительно лучше понимать, что именно происходит с базой. Прямо сейчас эта функциональность толкается в ядро PostgreSQL, чтобы начиная с 9.6 не требовалось пересобирать postgres для её работы.
На всякий случай скажу, что незадолго до того, как Ильдус представил свою реализацию на pgsql-hackers@, идею сделать wait interface озвучил Robert Haas. И очевидно, эту идею поддержали многие. Для того, чтобы это случилось, уже принято пару подготовительных патчей, например, Refactoring of LWLock tranches.
Очень надеемся, что мы увидим это в 9.6.
Comments
comments powered by Disqus