Pull to refresh

PostgreSQL, RED, Golden Signals: руководство к действию

Reading time 9 min
Views 5.3K

Методы наблюдения Golden Signals и RED являются шаблонами при построении мониторинга сервисов и определяют ключевые метрики которые нужны при наблюдении. Раньше об этих методах знали исключительно администраторы мониторинга или SRE-инженеры. Сейчас тема инструментирования приложений уже не является чем-то новым и об этих методах знают более-менее все.

В этом посте я порассуждаю о том как в мониторинге покрыть PostgreSQL используя методы RED и Golden Signals. Подсистема мониторинга в Postgres реализована в те времена когда RED и Golden Signals еще не было и на мой скромный взгляд в ней есть некоторые недостатки и с ходу натянуть RED или Golden Signals на Postgres может показаться непростой задачей. В этом посте я постараюсь коротко рассмотреть возможности которые предоставляет Postgres для реализации наблюдения по методам RED/Golden Signals и дам конкретные направления к тому чтобы реализовать это. К тому же это на так сложно как можно подумать.

Я относительно давно знаком с RED и Golden Signals и есть несколько причин почему стоит использовать эти методы:

  • мониторинг по этим методам позволяет быстро (но при этом поверхностно) определить все ли в порядке с сервисом. 

  • при наличии широкого покрытия другими метриками, при исследовании проблемы можно углубляться в нужном направлении исключая менее важные.

  • они более-менее универсальны и применимы как к веб-сервисам, так и к системным сервисам которые не ориентированы на прямую работу с пользователем.

  • являются хорошим началом чтобы закрыть базовые потребности в мониторинге для любого сервиса.

В общем случае, если перед тобой непонятный сервис который надо замониторить, то просто берем RED или Golden Signals и как по списку настраиваем сбор нужной информации и отдачу метрик. На выходе получаем базовый минимум (в виде дашборда) который в целом дает достаточное представление о том хорошо или плохо работает сервис. Далее уже можно реализовать более детальные или сервис-специфичные вещи. Но впрочем у этих методов есть и недостатки, поэтому не стоит думать что эти методы закроют все возможные потребности в метриках.

Что ж надеюсь получилось убедительно, давайте перейдем к Postgres'у.

Суть RED и Golden Signals  это измерение количественных характеристик проходящего через сервис трафика, на примере RED это:

  • Request rate - количество запросов в секунду.

  • Request errors - количество ошибочных запросов в секунду.

  • Request duration - время затраченное на выполнение запросов (в идеале гистограмма с распределением количества запросов по времени выполнения).

В Golden Signals примерно всё тоже самое (и вообще он появился первым), но другими словами (Latency, Traffic, Errors), плюс там присутствует Saturation - объем работы который не может быть обслужен в данный момент, но его надо сделать, поэтому он поставлен в очередь.

Несмотря на то что оба этих метода были предложены для наблюдения за HTTP (микро)сервисами, их можно использовать и для наблюдения за другими запросо-ориентированными сервисами, включая и СУБД. Любую СУБД можно представить как сервис который обслуживает запросы от клиентов (Requests), запросы могут выполняться как успешно так и с ошибками (Errors), запросы выполняются определенное время (Duration), доступ к ресурсам осуществляется конкурентно с использованием блокировок и доступ может быть ограничен в результате чего выстроится очередь ожидающих (Saturation).

Небольшое отступление. Перед тем как продолжить, стоит определиться что будет подразумеваться под "запросом". Обычный SQL-запрос или SQL-транзакция? Под отдельным запросом будет считаться SQL-запрос, т.к. приложение оперирует именно отдельными командами и лишь при необходимости может выстраивать их в транзакции, следовательно 1 запрос = 1 SQL-команда. Также с точки зрения собственной наблюдаемости приложение может должно иметь собственные RED метрики и дополнительно снабжать каждую SQL-команду метаданными о ней (request_id, информация об отправителе, метод/контроллер и т.п.).

Requests

Начнем по порядку, R - requests. Запросы это клиентская активность, нет клиентов - нет запросов. Для наблюдения за клиентской активностью в Postgres есть несколько представлений (views). Но пока интерес представляет лишь одна из них.

pg_stat_statements. Это отдельное расширение которое нужно отдельно настроить и включить. В этом представлении собрана per-statement статистика, и для каждого statement (запроса) есть поле calls которое показывает количество вызовов этого конкретного запроса. Сумма всех calls показывает количество обработанных запросов. Важно отметить что в pg_stat_statements фиксируются только успешно выполненные запросы.

Однако с pg_stat_statements есть нюанс. У расширения есть настраиваемый режим трекинга - параметр pg_stat_statements.track указывает о том как выполнять подсчет. У параметра доступно 2 значения. Первое значение "top" указывает считать только прямые вызовы запросов. И второе значение "all" указывает считать вызовы вложенные в хранимые процедуры и функции. С точки зрения наблюдения за запросами предпочтительно иметь значение "top", т.к. если клиент отправляет запрос на вызов хранимой функции, то все вложенные в функцию запросы, выполняются на стороне СУБД и не могут считаться отдельными запросами от приложения.

Чтобы получить данные достаточно сделать довольно простой запрос к pg_stat_statements.

SELECT sum(calls) FROM pg_stat_statements;

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

А еще в голову может прийти мысль использовать pg_stat_activity или pg_stat_database. Отбрасываем эту мысль, pg_stat_activity не подходит для подсчета запросов, т.к. показывает снимки (snapshot) активности в момент обращения к ней, и то что происходит между обращениями остается неизвестным. Во второй же хоть и есть интересное поля типа xact_commit, но оно не совсем подходит для нашей задачи т.к. оперирует транзакциями. Впрочем, мы воспользуемся этим представлением дальше.

Errors

Для оценки количества ошибок или там скажем "запросов которые завершились ошибкой" вернемся к ранее упомянутой pg_stat_database. В этом представлении есть поле xact_rollback которое является счетчиком откатов транзакций. Откат как правило случается из-за ошибки запроса внутри транзакции, либо приложение может вызвать откат явно. Поэтому достаточно всего лишь одной ошибки внутри транзакции чтобы привести к откату всей транзакции. А сейчас важный момент, отдельно выполненная SQL-команда (даже если она явно не обернута в блок BEGIN .. END) также считается транзакцией и ошибка при выполнении команды также будет фиксироваться в xact_rollback. Отсюда следует что поля xact_rollback достаточно чтобы фиксировать ошибки при выполнении запросов.

Для получения информации опять же не требуется глубоких познаний в SQL.

SELECT sum(xact_rollback) FROM pg_stat_database;

Однако тут есть небольшой недостаток, такой подход показывает ошибки только связанные с обработкой запросов. Но в течение всего времени функционирования СУБД, могут возникать и другие ошибки не связанные с обработкой запросов. Хотя такие ошибки случаются редко, но всё равно важно знать о них. К сожалению пока в Postgres нет представления которое бы предоставляло удобный интерфейс к статистике ошибок и такая информация может быть получена только из журналов.

Duration

Для подсчета времени выполнения запросов нам снова понадобится pg_stat_statements и конкретно поле total_time. Это поле показывает в миллисекундах время выполнения (включая ожидание) для каждого типа запросов. Соответственно чтобы получить суммарное время выполнения запросов нужно сложить total_time всех запросов.

SELECT sum(total_time) FROM pg_stat_statements;

Значение будет в миллисекундах, при желании можно домножить, т.к. например в Prometheus принято приводить время к секундам. В идеале конечно хотелось бы иметь гистограмму с распределением количества запросов по временным диапазонам, но pg_stat_statements под капотом выполняется свою агрегацию и наружу отдает только агрегаты. Помимо total_time еще есть min_time, max_time, mean_time, stddev_time. Начиная с версии 13 добавлены новые поля которые показывают время планирования запросов. Но это уже DBA-specific вещи, о них может как-нибудь в другой раз.

Saturation

Последний пункт это насыщенность (saturation), которого нет в RED методе, но который присутствует в Golden Signals. Напомню что насыщенность (она же сатурация) это ситуация когда выполнение запроса отложено в очередь, следовательно в контексте обработки запросов в СУБД нам важно видеть ситуации когда запросы не обрабатываются (при этом нет и ошибок).

Немного теории

Количество одновременных подключений к базе ограничено значением параметра max_connections. Однако на практике редко кто доходит до этого ограничения, обычно проблемы начинаются раньше. Чтобы достичь состояния сатурации, достаточно выполнить два условия: 1) нужны idle транзакции 2) нужен более-менее устойчивый tps с запросами на запись внутри. Если выполнить эти два условия, то проблемная ситуация как правило выглядит так: приложение в транзакции обновляет строки в таблице, оставляет транзакцию открытой (чтоб вернуться к ней позже), но уже не возвращается. Конкурентные обновления встают в очередь и ждут когда завершится первая транзакция. Новые запросы приходят и встают в очередь вытесняя всех остальных, до тех пор пока не будет достигнут лимит max_connections, после чего все новые запросы будут отклонены с ошибкой. Либо пока первая транзакция не завершит работу сама или принудительно администратором.

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

Почему не самое лучшее?

До этого момента для снятия статистики рассматривались счетчики (COUNTER) которые в фоновом режиме накапливают информацию. Счетчики удобны тем что "не теряют" информацию (сброс счетчика или его переполнение не в счет, это явление все-таки редкое). Если значение счетчика менялось быстро или наоборот медленно это можно увидеть построив частотный график. Представление pg_stat_activity по сути представляет собой набор GAUGES (шкала) и показывает актуальные данные только в момент обращения. Следовательно неизвестно что происходило в момент между обращениями. А происходить могло много чего - например при OLTP нагрузке, в одной секунде могут быть выполнены тысячи запросов/транзакций и при работе с GAUGE мы просто этого не узнаем. Это существенный недостаток метрик типа GAUGES.

Если закрыть глаза на недостатки GAUGE, то в целом минутной гранулярности при снятии значений нам хватит чтобы фиксировать проблемы. А если есть возможность снимать значения чаще, то это хорошо. Итак нам понадобится:

  • количество и состояние клиентов (active, idle in transaction, waiting).

SELECT
	count(*) FILTER (WHERE state IS NOT NULL) AS total,
	count(*) FILTER (WHERE state = 'idle') AS idle,
	count(*) FILTER (WHERE state IN ('idle in transaction', 'idle in transaction (aborted)')) AS idle_in_xact,
	count(*) FILTER (WHERE state = 'active') AS active,
	count(*) FILTER (WHERE wait_event_type = 'Lock') AS waiting,
	count(*) FILTER (WHERE state IN ('fastpath function call','disabled')) AS others
FROM pg_stat_activity WHERE backend_type = 'client backend';

Также потребуется информация о том как долго транзакции находятся в простое и как долго находится в ожидании самый первый в очереди. Для этого потребуются поля xact_start - время начала транзакции, и state_change - время перехода из предыдущего состояния в текущее - по сути это время перехода в active режим с последующим ожиданием.

  • максимальное время простоя внутри транзакции.

SELECT coalesce(max(extract(epoch FROM clock_timestamp() - xact_start)),0) AS max_idle_seconds
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'idle in transaction (aborted)');
  • максимальное время ожидания клиентов.

SELECT coalesce(max(extract(epoch FROM clock_timestamp() - state_change)),0) AS max_idle_seconds
FROM pg_stat_activity
WHERE wait_event_type = 'Lock';

Как вы могли заметить здесь запросы уже чуть подлиннее предыдущих, но в целом ничего сложного. Все полученные значения также нужно обернуть в метрики. Через метрики насыщенности важно:

  • отслеживать массовые появления состояний idle in transactions и waiting и оперативно устранять причины их появления.

  • отслеживать любое даже единичное появления долгих idle транзакций.

  • отслеживать появление долгого ожидания клиентов.

Итого

Чтобы добавить метрики для наблюдения за Postgres'ом по методам RED или Golden Signals понадобится базовое знание SQL и несколько представлений. Нужные источники данных находятся в разных представлениях, что слегка неудобно, но и не так страшно. Итак, request rate берется на основе pg_stat_statements.calls. Request errors на основе pg_stat_database.xact_rollback. Request duration на основе pg_stat_statements.total_time. Saturation на основе state, wait_event_type, xact_start, state_change из pg_stat_activity.

На выходе у вас должно получиться 6 метрик, на основе которых можно составить небольшой обзорный дашборд из пары-тройки панелей и графиков которые в будущем можно снабдить drilldown-ссылками на более детальные дашборды/графики. По сути это отправная точка начиная с которой можно двигаться дальше и постепенно расширять мониторинг Postgres'а.

Примерно в таком ключе можно организовать дашборд.
Примерно в таком ключе можно организовать дашборд.

Как пользоваться таким дашбордом?

  1. смотрим на Requests. Если их меньше или больше обычного, ищем причины почему так. Откуда и почему пришла нагрузка, не сломался ли кэш или наоборот почему так мало нагрузки, живы ли приложения.

  2. смотрим на Errors. Нету ошибок хорошо, есть ошибки - повод заглянуть в кибану/логи и посмотреть что за ошибки и устранить их.

  3. смотрим на Duration. Если растет время, идем разбираться с запросами. Какие запросы выполняются дальше остальных и почему.

  4. смотрим на Saturation. Появились idle транзакции или waiting и ползут вверх, идем в БД и разбираемся откуда они. Для системного решения скорей всего придется чинить приложение, в качестве временного ad-hoc решения принудительно завершаем idle транзакции.

Вот как-то так.

Еще добавлю что мы в Weaponry отслеживаем эти вещи и если ваш Postgres злоупотребляет ошибками, idle транзакциями или блокировками, то Weaponry подскажет что делать.

Если вы еще хотите узнать что-то о мониторинге около-Postgres'а, пишите об этом в комментариях.

Tags:
Hubs:
+13
Comments 11
Comments Comments 11

Articles