- •Предисловие
- •Об этой книге
- •Глава 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.5. Состояния сеансов |
53 |
За годы практики я и некоторые мои коллеги пришли к тому, что среди всех типов событий ожидания есть особая группа,которую можно выделить среди остальных и рассматривать как отдельное состояние, равноценное значениям из pg_stat_activity.state. Речь о группе событий с типом Lock: ожидания этой группы указывают на то, что выполнение команды остановлено и ожидает получения тяжелой блокировки. Группа включает в себя несколько ожиданий, которые болееточно определяютпричину ожидания.Одним из примеров можетслужитьожидание transactionid,указывающее на ожидание завершения соседней конкурентнойтранзакции. События группы Lock прямо указывают, что как процесс, так и клиентское приложение вместовыполненияполезнойработынаходятсявожидании,иэтосамопосебенегативновлияет на производительность приложения и СУБД.
Отслеживание состояний с учетом ожиданий
Для учета состояния ожиданий блокировок есть два способа. Первый и предпочтительный заключается в использовании событий ожидания. С помощью условия wait_event_type = 'Lock' процессы можно идентифицировать как находящиеся в ожидании блокировок:
# SELECT
CASE WHEN wait_event_type = 'Lock' THEN 'waiting' ELSE state
END AS state, count(*)
FROM pg_stat_activity WHERE backend_type = 'client backend' GROUP BY 1 ORDER BY 2 DESC;
state |
| count |
|
--------------------- |
+ |
------- |
idle |
| |
31 |
waiting |
| |
3 |
active |
| |
3 |
idle in transaction |
| |
1 |
Второй способ чуть сложнее и представляет, скорее,академический интерес.Способ заключается в соединении с pg_locks и использовании флага granted. Однако следует учесть, что для одного процесса в pg_locks будет несколько блокировок (несколько строк) и учитывать нужно только те,которые не захвачены:
#SELECT
CASE WHEN NOT l.granted
THEN 'waiting' ELSE a.state END AS state,
count(*)
FROM pg_stat_activity a
LEFT JOIN pg_locks l ON a.pid = l.pid AND NOT l.granted WHERE a.backend_type = 'client backend'
GROUP BY 1 ORDER BY 2 DESC;
54 |
Глава 2. |
Статистика активности |
||
|
|
state |
| count |
|
|
--------------------- |
|
+ |
------- |
|
idle |
|
| |
24 |
|
active |
|
| |
8 |
|
waiting |
|
| |
3 |
|
idle in transaction | |
1 |
||
Результаты двух вариантов не совсем совпадают, потому что выполнены в разное время. Но первый вариант проще,поскольку позволяет обойтись без соединения.
Есть как минимум два способа реализовать метрики, показывающие состояние клиентов для мониторинга:
1.Отдельно показывать состояния на основе поля state и количество процессов со значением Lock в поле wait_event_state.Втаком случае состояние active будетвключать в себя процессы,находящиеся в ожидании блокировок.
2.Исключать процессы,находящиеся в ожидании стипом события Lock,из числа процессов в состоянии active.
Внутренняяреализацияметрикиpostgres_activity_connections_in_flightиспользуетвторой способивключаетвсебяметкуstate.Помимостандартныхсостояний,меткаможетсодержать и дополнительное состояние waiting, которое как раз указывает на процессы, находящиеся
вожидании блокировок:
#sum by (state) (postgres_activity_connections_in_flight{service_id="primary"})
На рис. 2.8 изображен пример графика на основе этого запроса.
Рис. 2.8.Количество клиентов по состояниям
2.5. Состояния сеансов |
55 |
На графике есть процессы почти во всех возможных состояниях, в том числе и в ожидании блокировок. Объединяя возможности полей state и wait_event_type, можно собрать воедино информацию о состоянии клиентских сеансов.
Взаимоблокировки
Взаимоблокировка (deadlock) — это ситуация из нескольких блокировок, которая не может разрешиться сама собой, поскольку ее участники дошли до тупикового состояния, в котором все они заблокированы друг другом, перешли в ожидание и не могут продолжать работу. Для разрешения ситуации необходимо освободить часть блокировок путем принудительного завершения одного из участников взаимоблокировки.
В случае обычных блокировок ожидание можетдлиться непредсказуемо долго.Со взаимоблокировками все намного проще: их обнаружение и разрешение выполняется автоматически, без необходимости вмешательства со стороны администратора. Если блокировку не удалось взять за время deadlock_timeout (по умолчанию одна секунда), сеанс проверяет, не является ли он участником взаимоблокировки. В ходе проверки определяются участники конфликта. Если среди участников есть процесс очистки, то ему будет отправлен сигнал о завершении. Исключение составляют процессы очистки с целью предотвращения зацикливания счетчика транзакций (to prevent wraparound).Если процессов очистки нет,то сеанс,начавший проверку, принудительно отменяет выполнение своего собственного запроса, ради которого и потребовалось взять блокировки. Если запрос выполнялся в транзакции, вся транзакция становится ошибочной и ее следует завершить откатом. Остальные участники взаимоблокировки могут дальше продолжить работу, однако нет гарантий, что взаимоблокировка не возникнет снова.
На практике в самом простом варианте взаимоблокировки возникают при перекрестной попытке взять блокировки на одни и те же ресурсы из разных сеансов. Например, первая транзакция пытается захватить блокировку на ресурс, захваченный второй транзакцией, а вторая транзакция пытается захватить ресурс, взятый первой транзакцией. Основной вред взаимоблокировок заключается втом,что при их постоянном возникновении существенно уменьшается производительность (из-за снижения конкурентности) и появляется необходимость в по- втореоперации(запросаилитранзакции)из-заеепринудительнойотмены.Однако,учитывая автоматический характер разрешения взаимоблокировок,наносимый ими вред существенно меньше, чем в случае обычных блокировок, которые могут останавливать работу соседних сеансов на непредсказуемо долгое время.
К сожалению, универсального решения со стороны СУБД не существует, поскольку проблема заключается в неоптимальном порядке выполнения команд при конкурентной работе приложения с СУБД. Чтобы полностью решить проблему, необходимо изменить подход в работе с данными со стороны именно приложения.
Для отслеживания взаимоблокировок используют счетчик pg_stat_database.deadlocks, свой для каждой БД. В случае возникновения взаимоблокировки информация о ней записывается в журнал активности:
56 |
Глава 2. Статистика активности |
[UPDATE] ERROR: deadlock detected [UPDATE] DETAIL:
Process 3498160 waits for ShareLock on transaction 809; blocked by process 3498137. Process 3498137 waits for ShareLock on transaction 808; blocked by process 3498160. Process 3498160: update t1 set a = 1 where a = 2;
Process 3498137: update t1 set a = 2 where a = 1; [UPDATE] HINT: See server log for query details.
[UPDATE] CONTEXT: while updating tuple (0,3) in relation "t1" [UPDATE] STATEMENT: update t1 set a = 1 where a = 2;
Взависимостиотзначенияlog_line_prefix содержимоежурналаможетдополнятьсяидругойполезной информацией (время, пользователь, база данных, идентификаторы транзакций и пр.).
Ввыводе есть ключевое сообщение о том, что зафиксировано появление взаимоблокировки — deadlock detected. Далее следуют информация о том, какой процесс находился в ожидании, каким процессом была вызвана блокировка, и тексты запросов, которые выполнялись в обоих сеансах в момент обнаружения взаимоблокировки. Здесь стоит учитывать, что это текущие запросы, а в реальности конфликтующая блокировка могла быть захвачена другим запросом той же транзакции, выполнение которого предшествовало текущему. Для полного анализа необходимо включать протоколирование всех запросов (log_statement = all или log_min_duration_statement = 0), полностью записывать ход выполнения запросов в транзакции и проверять порядок их выполнения.
Бездействующие транзакции
В идеальном случае последовательность команд, составляющих транзакцию, должна выполняться непрерывно, без пауз или задержек — клиент, получив результат, должен немедленно отправитьследующую команду.Однако на практике получается,что между отправкой команд приложение выполняет какую-то работу, в то время как СУБД ожидает от него продолжения (маркер ожидания Client.ClientRead).Транзакция в состоянии ожидания команды отклиента называется бездействующей (idle) транзакцией. Состояние простоя idle in transaction в абсолютном большинстве случаев возникает из-за клиента и лишь в редких случаях из-за окружения,в котором работаетприложение (операционная система или сетевое окружение).Сточки зрения приложения паузы между отправкой команд могут быть вызваны такими причинами, как:
•вычисления на основе результатов предыдущих команд;
•обращение к удаленному сервису (кеш,очередь,БД,API и т. п.);
•недостаток системных ресурсов и их выделение операционной системой (память);
•приостановка приложения при исчерпании установленного ограничения на использова-
ние ресурсов (Kubernetes requests/limits);
•возникновение ошибок и обработка исключений.
2.5. Состояния сеансов |
57 |
Это неполный список, и причины, по которым могут появляться простои в транзакциях, могут быть самыми разными и не всегда очевидными. В большинстве случаев ответственность за образование бездействующих транзакций возлагается на приложение. Хороший пример — обработка ошибок. В случае ошибки правильным действием со стороны приложения является завершение транзакции откатом. В худшем случае может возникать утечка транзакций — ситуация,когда приложение теряет контроль над транзакцией и она остается незавершенной в течение неопределенно долгого времени. Возможны и другие сценарии. Например, пользо- вательподключаетсякБДивыполняеткакие-токоманды.Пользователюможетпотребоваться начатьтранзакцию, выполнить в ней команду и затем сделать откат. Или IDE, в которой работает пользователь, при выполнении команд может неявно открывать транзакцию. Пользователь отвлекается на другую задачу,и ранее открытая транзакция остается незавершенной.
Пребывание клиентских сеансов в состоянии idle in transaction имеет свои негативные эффекты, и некоторые из них в зависимости от условий эксплуатации СУБД могут привести
каварии:
•Удерживание соединения с БД. Когда приложение открывает транзакцию, предполагается, что оно будет выполнять последовательность команд. В случае использования пулов соединений на стороне приложения соединение с открытой транзакцией не может использоваться другим потоком выполнения. При достижении ограничения на число одновременно открытых соединений на стороне пула,приложение заблокируется до освобождения хотя бы одного соединения.
•Устаревшие версии строк, эффект раздувания и падение производительности. При высокой интенсивности на запись размер таблиц и индексов растет из-за появления нескольких версий одних и тех же строк. Устаревшие версии строк не только занимают место на диске, но и негативно влияют на производительность. При чтении и поиске данных процессу приходится обрабатывать больше страниц в поисках нужной версии строки,что приводит к замедлению запроса и избыточному использованию ресурсов CPU, памяти и ввода-вывода.Для регулярного удаления устаревших версий строк и освобождения места используется автоочистка. Рабочие процессы автоочистки сканируют таблицы и индексы, находят версии строк, которые не нужны ни одной из транзакций, и удаляют их. Ностарыеверсиистрокмогутпонадобитьсядолгимтранзакциям(втомчислебездействующим), поэтому автоочистка пропускает удаление таких версий. В случае же успешного удаления освобожденное место продолжает принадлежать таблице (или индексу). Получается,что размер таблиц и индексов увеличивается,но после очистки не уменьшается— это и называется эффектом раздувания. В особо запущенных случаях объем освобожденного пространства внутри таблиц и индексов может превышать общий объем полезных данных.
•Блокировки, исчерпание доступных подключений к СУБД. При высокой интенсивности на запись команды в транзакции могут удерживать блокировки строк. Соседние транзакции могут захотеть получить доступ к этим же строкам и при попытке взять блокировку будут ждать, пока блокировку не отпустит бездействующая транзакция. Ожидающие процессы не могут использоваться приложением. При высокой нагрузке приложение будет
