Ко всем новостям

Кейс команды СберТеха о доработке опенсорс-инструмента postgres_exporter для мониторинга событий, которые он не отслеживает изначально.

Публикации в СМИ
27.09.2024

Оригинал: https://habr.com/ru/companies/sberbank/articles/845616/ 

Меня зовут Станислав Епишин, я DBA в дивизионе поддержки решений в тестовых средах в СберТехе. Эту статью я написал вместе с Дмитрием Корневым, тимлидом и DBA. У Сбера есть целевая СУБД, которую разработали в СберТехе на основе open source версии PostgreSQL, — Platform V Pangolin. Наша команда перешла на Pangolin в числе первых, когда у продукта еще не было инструментов для мониторинга БД. Забегая вперед, позже появились такие решения — графическая консоль Platform V Kintsugi, расширение для сбора статистики — Performance Insights и система мониторинга IT-инфраструктуры Platform V Monitor. А поначалу мы решили мониторить базы данных связкой Grafana, Prometheus и postgres_exporter. Но, во-первых, столкнулись, с тем, что нам не хватает гибкости в использовании queries.yaml в postgres exporter. А, во-вторых, так мы не могли регистрировать события с таймаутом меньше 15 секунд. Поэтому мы тогда сделали свой инструмент для мониторинга — pangolin_exporter.

Надеюсь, что эта статья будет полезна тем, кто мониторит инфраструктуру с помощью postgres_exporter и хочет кастомизировать все под свои нужды.

Наши условия таковы: на сопровождении примерно 1200 баз данных, структура динамическая — их количество меняется, создаются новые, удаляются неактуальные. Нам требовалось реализовать динамическое распространение postgres_exporter (это инструмент PostgreSQL для сбора метрик с экземпляров кластера СУБД в формате, доступном Prometheus) по серверам, желательно с периодом 24 часа. Пробуем использовать Jenkins и сценарии Ansible для автоматизации деплоя postgres_exporter. Итак, здесь все хорошо: сценарии написаны, решение реализовано.

Далее: нам надо было получать актуальные для нас метрики, в том числе специфические для Platform V Pangolin. Мы хотели наблюдать статистику активности и выполнения запросов и функций, вводу-выводу, WAL, по доступности и актуальности снимков performance_insights и pg_profile и так далее. А также нам хотелось получать метрики по Pangolin Manager и Pangolin Pooler — это улучшенные и переработанные версии Patroni и Pgbouncer. Но, как я уже упоминал в начале, мы столкнулись с тем, что в postgres_exporter не можем гибко настроить получение метрик под себя.

Тогда мы решили взять исходный код инструмента и сделать форк на его базе. Создавая решение, мы черпали много информации из книги «Мониторинг PostgreSQL» Алексея Лесовского (спасибо Алексею за дельные и полезные идеи!).

Начинаем делать свой экспортер

Создаем pangolin_exporter из исходников последней версии postgres_exporter-0.15.0 с github. Для проекта мы использовали Microsoft VS Code, настройка под Go не вызвала каких-то трудностей. Задаем переменные окружения для сборки и запуска экспортера. Определяем переменную GOOS, при желании можно собрать версию под Windows, присвоив значение Windows, в нашем случае мы присваиваем значение Linux.

"GOOS": "linux"

Определяем переменную DATA_SOURCE_NAME, содержащую строку подключения к БД, с которой будем собирать метрики.

В учебных целях для этого примера определяем учетные данные в открытом виде.

Меняем <user>:<pass>@<hostname> на актуальные данные.

"DATA_SOURCE_NAME": "postgresql://<user>:<pass>@<hostname>:5432/postgres?sslmode=disable" Для этого добавляем пару файлов в проект: /<project_path>/cmd/postgres_exporter/.vscode/launch.json /<project_path>/cmd/postgres_exporter/.vscode/settings.json settings.json { "go.toolsEnvVars": { "GOOS": "linux" } } launch.json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Launch Package", "type": "go", "request": "launch", "mode": "auto", "program": "${fileDirname}", "env": { "DATA_SOURCE_NAME": "postgresql://<user>:<pass>@<hostname>:5432/postgres?sslmode=disable" } } ] }

Собираем бинарник из исходников:

cd /<project_path>/cmd/postgres_exporter go build -o /<project_path>/cmd/postgres_exporter/build .

Запускаем в Microsoft VS Code, для этого нужно открыть main.go и нажать F5, при необходимости пошагово отлаживаем код, расставляя breakpoints.

Далее добавляем необходимые нам метрики и редактируем существующие. Для редактирования в пакете collector, находим нужную метрику. Файлы пакета находятся в каталоге /<project_path>/collector.

Рассмотрим пример добавления метрики для определения версии Pangolin.

Планируем SQL-запрос для получения данных:

select product_version from product_version();

Версия Pangolin будет выводиться в label, метрика будет равна 1, если SQL-запрос выполнен. Добавляем новый файл в пакет collector, путь /<project_path>/collector.

package collector import ( "context" "database/sql" "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" ) const InfoPangolinSubsystem = "" func init() { registerCollector(InfoPangolinSubsystem, defaultEnabled, NewPGInfoPangolinCollector) } type PGInfoPangolinCollector struct { log log.Logger } func NewPGInfoPangolinCollector(config collectorConfig) (Collector, error) { return &PGInfoPangolinCollector{log: config.logger}, nil } var ( InfoPangolinVersion = prometheus.NewDesc( prometheus.BuildFQName(namespace_pangolin, InfoPangolinSubsystem, "version"), "Pangolin version.", []string{"product_version"}, prometheus.Labels{}, ) pgInfoPangolinQuery = `select product_version from product_version();` ) func (c PGInfoPangolinCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error { db := instance.getDB() rows, err := db.QueryContext(ctx, pgInfoPangolinQuery) if err != nil { return err } defer rows.Close() for rows.Next() { var product_version sql.NullString if err := rows.Scan(&product_version); err != nil { return err } product_versionLabel := "unknown" upMetric := 0.0 if product_version.Valid { product_versionLabel = product_version.String upMetric = 1 } else { upMetric = 0 } ch <- prometheus.MustNewConstMetric( InfoPangolinVersion, prometheus.CounterValue, upMetric, product_versionLabel, ) } if err := rows.Err(); err != nil { return err } return nil }

Запускаем экспортер, при настройках порта по умолчанию вбиваем в веб-браузер http://localhost:9187/metrics и видим нашу новую метрику:

# HELP pangolin_version Pangolin version # TYPE pangolin_version counter pangolin_version{product_version="Platform V Pangolin 5.5.0"} 1

Набредаем на баг в Postgres Exporter и другие грабли

Делая свое решение, попутно нашли баг в исходном коде postgres_exporter. Нам нужно было определить рост ожиданий в зависимости от нагрузки, чтобы понять, насколько АС подвержена риску на ПРОМе. Целиком можно было закрыть риск, проведя нагрузочное тестирование и посмотрев на события ожидания от LockManager, — это фоновый процесс в Pangolin, отвечающий за блокировки к структурам данных. Вариант решения заключался в использовании оригинальной метрики:

"pg_stat_activity_max_tx_duration" из Postgres_exporter.

"pg_stat_activity_max_tx_duration" – максимальная продолжительность выполнения активной транзакции в секундах.

Однако после изучения SQL-запроса этой метрики (см.ниже), выяснилось, что для "pg_stat_activity_max_tx_duration" возраст транзакций некорректно рассчитывается. Возраст существующих на конкретный момент транзакций должен определяться следующим образом: текущее время = время начала транзакции. А на деле вместо текущего времени подставляется время старта текущей транзакции, now ( ), то есть в нашем случае — это время начала другой транзакции, и поэтому получается некорректное значение.

now() → timestamp with time zone. Current date and time (start of current transaction);

clock_timestamp() → timestamp with time zone. Current date and time (changes during statement execution)

Некорректные значения скрывались агрегатной функцией max.

MAX(EXTRACT(EPOCH FROM now() - xact_start))::float AS max_tx_duration

Для мониторинга необходимо использовать clock_timestamp(), которая всегда показывает текущую отметку времени.

Запрос, использующийся в оригинальной версии postgres_exporter для max_tx_duration:

SELECT pg_database.datname, tmp.state, tmp2.usename, tmp2.application_name, COALESCE(count,0) as count, COALESCE(max_tx_duration,0) as max_tx_duration FROM ( VALUES ('active'), ('idle'), ('idle in transaction'), ('idle in transaction (aborted)'), ('fastpath function call'), ('disabled') ) AS tmp(state) CROSS JOIN pg_database LEFT JOIN ( SELECT datname, state, usename, application_name, count(*) AS count, MAX(EXTRACT(EPOCH FROM now() - xact_start))::float AS max_tx_duration FROM pg_stat_activity GROUP BY datname,state,usename,application_name) AS tmp2 ON tmp.state = tmp2.state AND pg_database.datname = tmp2.datname;

Что с этим делать? Мы решили выводить все ожидания из pg_stat_activity с группировкой по wait_event. Для нас был интересен кумулятивный рост ожиданий в зависимости от нагрузки. В целом ожидали получить график, где будет виден и ряд ожиданий, и их рост в зависимости от времени работы.

select wait_event, SUM(EXTRACT (EPOCH FROM clock_timestamp () - xact_start)::float) AS sum_xact_age, from pg_stat_activity where state not in ('idle') and wait_event is not null group by wait_event

Внедрили этот запрос в наш форк. Новую метрику назвали "pangolin_activity_max_seconds_xact_age". Вывели ее в Grafana. Нас интересовали таймауты от 5 мс и выше.

Здесь проявился главный недостаток связки Grafana+Prometheus и других подобных связок, например, Grafana+VictoriaMetrics (их мы тоже использовали).

В Prometheus есть параметр scrape_interval (интервал сбора метрик). Минимальное значение, которое у нас получилось настроить, — 15 секунд. Если интересующее нас событие длится от нескольких миллисекунд до 15 секунд, то Prometheus с некоторой вероятностью не фиксирует его. Это минусы мониторинга подобными связками. С ними не получится видеть объективную картину по wait_event.

Что решили с этим? В тот момент в команде Pangolin только появился собственный графический инструмент Platform V Kintsugi. Начали использовать его для получения картины по wait_event. Kintsugi — инструмент для оперативного анализа и диагностики СУБД и сопутствующей инфраструктуры. Он дополняет систему мониторинга, становясь рядом, чтобы пользователи понимали, когда их СУБД тормозит и почему это происходит.

Kintsugi позволил нам фиксировать 100% событий, которые нам нужны, но только в разрезе сессий, на тот момент задачу с регистрацией нужных нам таймаутов он не решал. Можно было попробовать использовать pg_profile и pg_wait_sampling.

- pg_profile — расширение Postgres, собирает статистику и создает снимки по собранной статистике, отдаленно напоминает AWR-отчеты в ORACLE.

- pg_wait_sampling — расширение Postgres для периодического сбора статистики по событиям ожидания.

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

Да, еще к этому моменту у Platform V, цифровой платформы СберТеха, появилась система мониторинга IT-инфраструктуры Platform V Monitor. Этот продукт мониторит в том числе и Pangolin, но, честно говоря, мы пока не успели протестировать его для наших задач. А в ближайшее время будем пробовать новый функционал Platform V Kintsugi – за время подготовки статьи там появилась опция создания собственных метрик, теперь сможем создавать различные особые метрики и наблюдать за ними.

Вместо заключения

Наше решение можно взять здесь и использовать. Будет здорово, если инструмент окажется вам полезным.

Надеюсь, что читать вам было так же интересно, как и нам искать и перебирать решения для сбора метрик :). Будем рады, если наш опыт пригодится тем, кто задумывается о кастомизации postgres_exporter. Если появились вопросы о деталях решения, которые мы могли упустить, или есть желание поделиться своим опытом, приходите в комментарии.