Как Grafana, Prometheus и Oracle ORDS помогли нам быстрее выявлять инциденты в биллинге

Kate

Administrator
Команда форума
Привет! Я работаю в компании Bercut, которая входит в группу компаний Ростелеком и более 20 лет занимается разработкой и поддержкой ПО для операторов сотовой и фиксированной связи. Прошел путь от инженера в отделе сопровождения до менеджера продукта. В последние годы работаю ведущим специалистом в отделе администрирования (Senior DBA) и знаю все про работу высоконагруженных биллинговых базах данных, обслуживающих от сотен тысяч до десятков миллионов абонентов. Сегодня я хочу рассказать про решение по комплексному мониторингу биллинговой системы у оператора связи на основе Grafana, Prometheus и Oracle ORDS.

Почему нам понадобилось новое решение​

Сейчас биллинговая система на стороне оператора связи ― это мегасложный комплекс с огромным количеством разных модулей и подсистем. Появляются новые модули, добавляются взаимосвязи между ними. Еще быстрее растет число бизнес-критичных функций. Все это нужно контролировать и своевременно выявлять и устранять проблемы. Используются жесткие SLA, временные рамки на ликвидацию аварий очень узкие. Поэтому чем раньше сбой будет обнаружен, проведен первичный анализ и сама проблема локализована, тем быстрее удастся исправить ситуацию.

Для настройки, управления и мониторинга всех продуктов в Bercut используется модуль Bercut ATOMS, с агентами на хостах и графической консолью MIB Explorer для администраторов ПО Bercut. В консоли специалисты операторов и Bercut выполняют все действия по настройке компонентов систем, трассировку, мониторинг производительности и т.д. Консоль имеет и встроенные средства для отображения графиков. С появлением Prometheus и Grafana, ставшими де-факто стандартами в ИТ, и возможности объединения мониторинга всех систем, системного и прикладного ПО, серверов, СХД, SAN, сетевой инфраструктуры в одном приложении мы также стали активно использовать Grafana. Ребята сделали MIB Exporter для экспорта метрик модулей систем в Prometheus. Коллега разработал Solaris Exporter для мониторинга серверов Oracle SPARC. Сейчас Prometheus и Grafana установлены у большинства наших заказчиков, и все перечисленное доступно через несколько десятков дашбордов.

Мониторинг метрик бизнес-логики биллинговой системы в БД Oracle, который я когда-то написал на PL/SQL, исторически был реализован обособленно. Он обладал необходимой функциональностью для формирования алертов и анализа инцидентов, но графического дашборда не имел. Конечно, мы делали попытки использовать Oracle Enterprise Manager, Zabbix и др., но это были скорее кастомные решения. Настройка отдельной метрики через User defined metrics/Metric extensions в OEM довольно трудоемка и посильна больше DBA, нежели администраторам комплекса.

При этом вопрос функционирования и нагрузки на БД Oracle периодически возникал при проблемах и отбоях: "Сережа, что с базой?" Инженеры в большинстве случаев не имеют доступа в Oracle Enterprise Manager и самостоятельно оценить нагрузку, факт конкуренции или блокировок в БД не могут. Т.е. требовался еще и простой "монитор", посмотрев на который коллеги могли бы самостоятельно проверить, не отличается ли текущая активность БД и нагрузка от той, что была час, день или неделю назад.

В общем, требовалось объединить моторинг работы БД и мониторинг бизнес-процессов внутри в БД в единое решение.

Почему мы выбрали Grafana, Prometheus и Oracle ORDS​

Глядя на то, как Grafana отлично справляется с визуализацией метрик с оборудования, инфраструктуры и серверов приложений, я решил, что было бы неплохо сделать дашборд и для мониторинга компекса на уровне БД. И, по моим ощущениям и отзывам коллег, получилось неплохо. Grafana ― это opensource решение, позволяющее легко создать дашборд с отображением метрики из разных источников. В Grafana можно объединить информацию по активности БД, нагрузке на сервере БД с ключевыми показателями работы биллинговой системы на одном экране, при этом интервал окна или временной период меняется всего несколькими кликами. Но не буду забегать вперед, начнем по порядку.

Grafana не умеет напрямую использовать БД Oracle как источник метрик, поэтому требовалась дополнительная прослойка. С учетом уже имеющегося у брата опыта, для сбора биллинговых метрик и хранилища истории был выбран Prometheus. В качестве экспортера из БД Oracle я исследовал возможности OracleDB Exporter. Oracledb_exporter представляет собой небольшой сервис, написанный на Ruby и имеющий конфигурационный файл на простейшем TOML языке. В конфигурационном файле содержатся SQL запросы, которые сервис будет запускать при поступлении запроса со стороны Prometheus. Однако я в нем быстро разочаровался по нескольким причинам:

  • Необходимость настройки отдельного экземпляра сервиса oracledb_exporter для каждой БД, если их несколько.
  • Необходимость настройки отдельного экземпляра сервиса oracledb_exporter для сбора метрик с разным интервалом опроса. Некоторые метрики нужно собирать очень часто, и они являются легкими с точки зрения выполнения запроса в БД; другие отрабатывают довольно долго, но нет необходимости в их частом запуске ― порой достаточно и раза в сутки.
  • Появление новой технологии (TOML) и точки настройки мониторинга для наших заказчиков и инженеров Bercut. Для кастомизации и добавления новых метрик коллегам придется изучать TOML.
  • И самое важное ― сложность в поддержке и обновлении метрик. C развитием продукта или по результатам каких-то инцидентов мы добавляем новые метрики; заказчики часто добавляют свои. Возникает проблема с применением изменений к конфигурационному TOML-файлу.
В то же время полностью от oracledb_exporter я не отказываюсь и продолжаю его использовать для получения некоторых метрик с экземпляра БД и Oracle ASM ― иметь статистику о топе ожиданий БД и % свободного пространства в ASM дисковых группах на дашборде биллинговой системы довольно важно.

В результате поиска и исследований выбор пал на решение на базе Oracle Rest Data Services и собственную разработку на PL/SQL. ORDS позволяет легко и непринужденно реализовать API в БД с взаимодействием по протоколу HTTP(s). Архитектура решения для мониторинга биллинговой системы представлена на рис. 1

Рис.1 Архитектура решения для мониторинга биллинговой системы
Рис.1 Архитектура решения для мониторинга биллинговой системы
Prometheus, согласно настройкам, каждую минуту опрашивает ORDS сервис, который на каждый запрос запускает в БД PL/SQL процедуру, а она в свою очередь в HTML формате отдает текущие значения заранее рассчитанных метрик. Учитывая то, что часть метрик по большим или высоконагруженным таблицам могут рассчитываться минуты, а некоторые метрики не требует частого обновления, вариант заранее собрать и подготовить к публикации метрики оказался лучшим решением. Также это избавляет от необходимости иметь конфигурацию с несколькими экспортерами с различными интервалами опроса и таймаутами, что упрощает общую конфигурацию.

Полученные значения метрик Prometheus сохраняет в свою БД и хранит данные 2 недели. Также значения метрик сохраняются в БД Victoria Metrics на значительно более длительный срок.

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

Далее я более детально опишу все элементы схемы: сверху вниз, слева направо.

Вычисление метрик на стороне БД​

Вычисление и хранение метрик реализовано в виде набора таблиц и пакета на PL/SQL.

Рис. 2 Схема данных
Рис. 2 Схема данных
Основной таблицей схемы является METRIC_DICT, в которой хранятся список метрик, параметры расписания запуска и SQL запрос для сбора метрики. Ряд метрик могут иметь несколько значений, например, метрика для контроля свободного места в табличном пространстве, ГБ" будет иметь несколько значений ― название табличного пространства в качестве метки и размер в качестве значения. Подобная реализация позволит в дальнейшем отображать все значения в Grafana на одном графике как одну метрику.
Справочник метрик выглядит так:

Рис.3 Справочник метрик
Рис.3 Справочник метрик
В PL/SQL пакете реализован ряд процедур. Две процедуры периодически запускаются при помощи Oracle Scheduler Jobs. Процедура вычисления метрик выбирает те метрики, у которых наступило время следующего запуска NEXT_RUN, и выполняет SQL оператор. Чтобы исключить или минимизировать влияние мониторинга на работу БД, запросы по большим и/или высоконагруженным таблицам выполняются с использованием dblink на резервном сервере БД ― Oracle Active Standby DB, открытой для чтения в режиме Real Time Apply. При этом отслеживается факт отставания Apply Lag, и, если он более заданной величины, запросы выполняются на Primary DB. Значение метрик сохраняется в таблицы *CURRENT*, а перед этим предыдущие значения переносятся в таблицы *HIST*, для истории. Метрики, по которым значение превышает установленный порог, также сохраняются в таблицу METRIC_ALERTS. Второй worker предназначен для формирования email-уведомлений администраторам. При появлении алерта администраторы получат сообщение со списком проблемных метрик и значениями остальных метрик (справочно). Механизм оставлен для совместимости с предыдущим решением и, возможно, в будущем будет заменен на Prometheus Alertmanager.

Публикация метрик на стороне приложений и сервисов​

На серверах приложений установлен Linux или Solaris node exporter, который собирает метрики ОС (cpu, memory, disk usage & utilization), а также MIB Exporter, собирающий и публикующий параметры работы сервисов и модулей биллинговой системы.

Установка Oracle Rest Data Services​

Oracle Rest Data Services ― это Java-EE приложение как развитие Oracle HTTP сервера и mod_plsql. Оно предоставляет широкие возможности по настройке, безопасности и кешированию для RESTful сервисов. Может работать в составе серверов приложений, например, Oracle Weblogic и Apache Tomcat, либо в standalone режиме.

ORDS скачиваем тут https://www.oracle.com/database/technologies/appdev/rest-data-services-downloads.html Описанные ниже настройки выполнялись для версии 20.4.3

root@prometheus# mkdir /opt/oracle
root@prometheus# chown oracle:eek:install /opt/oracle
root@prometheus# su - oracle
oracle@prometheus$ mkdir /opt/oracle/ords
oracle@prometheus$ mkdir /opt/oracle/ords/params
oracle@prometheus$ unzip ords-20.4.3.050.1904.zip -d /opt/oracle/ords
oracle@prometheus$ cd /opt/oracle/ords
Для установки и настройки ORDS нужен JDK. Если его нет, его нужно установить.

oracle@prometheus$ which java
/usr/bin/which: no java in (/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin)
oracle@prometheus$

root@prometheus# yum install java
...
Installed:
java-1.8.0-openjdk.x86_64 1:1.8.0.292.b10-1.el7_9
root@prometheus# exit

oracle@prometheus$ java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (build 25.292-b10, mixed mode)
Установка выполняется в интерактивном режиме, либо в silent, с использованием parameter file /opt/ords-20.4.3/params/ords_params.properties

Я планирую сделать Ansible playbook для автоматического развертывания, поэтому использовал второй вариант. Файл параметров для установки в минимальном варианте:

oracle@prometheus$ cat /opt/oracle/ords/params/ords_params.properties

db.connectionType=customurl
db.customURL=jdbc:eek:racle:thin:mad:(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)HOST=192.168.17.172)PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
standalone.mode=true
standalone.http.port=7778
standalone.use.https=false
standalone.context.path=/ords
standalone.scheme.do.not.prompt=true
rest.services.ords.add=false
plsql.gateway.add=true
migrate.apex.rest=false
rest.services.apex.add=false
db.password=prometheus
db.username=prometheus
resource.templates.enabled=false
Все параметры с комментариями (справочно):

#### Connection type: basic,tns or customurl
db.connectionType=customurl

### Parameters for "basic" connection type

#db.hostname=localhost
#db.port=1521
#db.servicename=myservice
#db.sid=

### Parameters for "tns" connection type

#db.tnsDirectory=/path/to/tnsfolder
#db.tnsAliasName=

### Parameters for "customurl" connection type

db.customURL=jdbc:eek:racle:thin:mad:(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))

### Parameters for Installing Oracle REST Data Services in Standalone Mode

standalone.mode=true
standalone.http.port=7778
## Specifies the location of Application Express images.
## If you are using Application Express, specify the location of Application Express images.
#standalone.static.images=/path/to/images
standalone.use.https=false
#standalone.https.port=8443
## Specifies the Secure Socket Layer (SSL) certificate hostname.
#standalone.ssl.host=mysecurehost
#standalone.use.ssl.cert=true
#standalone.ssl.cert.path=
#standalone.ssl.key.path=
standalone.context.path=/ords
standalone.scheme.do.not.prompt=true


### Parameters for Installing Oracle REST Data Services

## Specifies whether to install the Oracle REST Data Services schema.
rest.services.ords.add=false
## Specifies the ORDS_METADATA default tablespace
#schema.tablespace.default=USERS
## Specifies the ORDS_METADATA temporary tablespace
#schema.tablespace.temp=TEMP
## Specifies the password for ORDS_PUBLIC_USER.
#user.public.password=
## Specifies the ORDS_PUBLIC_USER default tablespace
#user.tablespace.default=USERS
## Specifies the ORDS_PUBLIC_USER temporary tablespace.
#user.tablespace.temp=TEMP

### Parameters for Configuring Application Express
## Specifies whether to configure Oracle REST Data Services for Application Express.
plsql.gateway.add=false
## Specifies the PL/SQL gateway username. For Application Express, you must specify APEX_PUBLIC_USER
#db.username=APEX_PUBLIC_USER
#db.password=
migrate.apex.rest=false
## Specifies whether to configure Oracle REST Data Services for Application Express RESTful Services.
## Set this value to true if you want to use APEX RESTful Services. Default - false
rest.services.apex.add=false
## Specifies the password for APEX_LISTENER.
## If rest.services.apex.add is set to true, you must provide a password for APEX_LISTENER
#user.apex.listener.password=
## Specifies the password for APEX_REST_PUBLIC_USER.
## If rest.services.apex.add is set to true, you must provide a password for APEX_REST_PUBLIC_USER.
#user.apex.restpublic.password=
Установка в silent режиме:

oracle@prometheus$ java -jar ords.war configdir /opt/oracle/ords
2021-06-03T09:07:01.282Z INFO Set config.dir to /opt/oracle/ords in: /opt/oracle/ords/ords.war

oracle@prometheus$ java -jar ords.war install --silent --parameterFile /opt/oracle/ords/params/ords_params.properties
2021-06-03T10:11:16.537Z INFO HTTP and HTTP/2 cleartext listening on host: localhost port: 7778
2021-06-03T10:11:16.568Z INFO Disabling document root because the specified folder does not exist: /opt/oracle/ords/ords/standalone/doc_root
2021-06-24T14:21:33.956Z INFO Configuration properties for: |apex||
cache.caching=false
cache.directory=/tmp/apex/cache
cache.duration=days
cache.expiration=7
cache.maxEntries=500
cache.monitorInterval=60
cache.procedureNameList=
cache.type=lru
db.connectionType=customurl
db.customURL=jdbc:eek:racle:thin:mad:(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
debug.debugger=false
debug.printDebugToScreen=false
error.keepErrorMessages=true
error.maxEntries=50
jdbc.DriverType=thin
jdbc.InactivityTimeout=1800
jdbc.InitialLimit=3
jdbc.MaxConnectionReuseCount=1000
jdbc.MaxLimit=10
jdbc.MaxStatementsLimit=10
jdbc.MinLimit=1
jdbc.statementTimeout=900
log.logging=false
log.maxEntries=50
misc.compress=
misc.defaultPage=apex
security.disableDefaultExclusionList=false
security.maxEntries=2000
db.password=******
db.username=prometheus
resource.templates.enabled=true
2021-06-24T14:21:33.958Z WARNING *** jdbc.MaxLimit in configuration |apex|| is using a value of 10, this setting may not be sized adequately for a production environment ***
2021-06-24T14:21:33.959Z WARNING *** jdbc.InitialLimit in configuration |apex|| is using a value of 3, this setting may not be sized adequately for a production environment ***
2021-06-03T10:11:18.909Z INFO Oracle REST Data Services initialized
Oracle REST Data Services version : 20.4.3.r0501904
Oracle REST Data Services server info: jetty/9.4.35.v20201120

^C
oracle@prometheus$
Сервис стартует в режиме standalone, для его остановки и дальнейшей настройки используется Ctrl-C.

В БД создается пользователь с минимальными привилегиями:

SQL> create user prometheus identified by prometheus default tablespace users temporary tablespace temp;

SQL> grant connect to prometheus;
Настройка соединения с БД. Используется файл параметров /opt/oracle/ords/params/db_params.properties

db.connectionType=customurl
db.customURL=jdbc:eek:racle:thin:mad:(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
error.keepErrorMessages=true
error.maxEntries=50
jdbc.DriverType=thin
# Session timeout
jdbc.InactivityTimeout=1800
# Number of connections to create
jdbc.InitialLimit=3
# Max number of times to reuse a connection before create new
jdbc.MaxConnectionReuseCount=1000
# Max number of connections
jdbc.MaxLimit=10
# Max statement to cache in connection
jdbc.MaxStatementsLimit=10
# Min number of connections
jdbc.MinLimit=1
jdbc.statementTimeout=900
db.password=prometheus
db.username=prometheus
resource.templates.enabled=false
rest.services.ords.add=false
plsql.gateway.add=true
Создание подключения к БД:

oracle@prometheus$ java -jar ords.war setup --database db2104 --silent -parameterFile /opt/oracle/ords/params/db_params.properties
2021-06-03T11:16:32.532Z INFO reloaded pools: [|apex||, |db2104||]
Создание маппинга URL к созданной конфигурации БД:

oracle@prometheus$ java -jar ords.war map-url --type base-path /db2104 db2104
2021-06-03T11:16:50.151Z INFO Creating new mapping from: [base-path,/db2104] to map to: [db2104, null, null]
Запуск сервиса:

oracle@prometheus$ java -jar ords.war standalone
2021-06-03T11:17:18.238Z INFO HTTP and HTTP/2 cleartext listening on host: localhost port: 7778
2021-06-03T11:17:18.274Z INFO Disabling document root because the specified folder does not exist: /opt/oracle/ords/ords/standalone/doc_root
2021-06-03T11:17:20.875Z INFO Configuration properties for: |db2104||
db.connectionType=customurl
db.customURL=jdbc:eek:racle:thin:mad:(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
db.password=******
db.username=prometheus
resource.templates.enabled=true

2021-06-03T11:17:20.877Z WARNING *** jdbc.MaxLimit in configuration |db2104|| is using a value of 10, this setting may not be sized adequately for a production environment ***
2021-06-03T11:17:20.877Z WARNING *** jdbc.InitialLimit in configuration |db2104|| is using a value of 3, this setting may not be sized adequately for a production environment ***
2021-06-03T11:17:23.884Z INFO Oracle REST Data Services initialized
Oracle REST Data Services version : 20.4.3.r0501904
Oracle REST Data Services server info: jetty/9.4.35.v20201120
Для удобства в ОС создан сервис, который будет автоматом подниматься при старте сервера. Создан конфигурационный файл:

root@prometheus# cd /etc/systemd/system
root@prometheus# vi billing_monitoring.service

[Unit]
Description=Bercut Billing Monitoring (ORDS)
Wants=network-online.target
After=network-online.target

[Service]
User=oracle
Group=bercut
Type=simple
ExecStart=/bin/java -jar /opt/oracle/ords/ords.war standalone

#systemd-escape

KillMode=process
RemainAfterExit=no
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
root@prometheus# systemctl daemon-reload
root@prometheus#
root@prometheus#
root@prometheus# systemctl start billing_monitoring.service
root@prometheus#
root@prometheus#
root@prometheus# systemctl -l status billing_monitoring.service
● billing_monitoring.service - Bercut Billing Monitoring (ORDS)
Loaded: loaded (/etc/systemd/system/billing_monitoring.service; disabled; vendor preset: disabled)
Active: active (running) since Thu 2021-06-03 14:46:08 MSK; 18s ago
Main PID: 26605 (java)
CGroup: /system.slice/billing_monitoring.service
└─26605 /bin/java -jar /opt/oracle/ords/ords.war standalone

Jun 03 14:46:13 prometheus java[26605]: db.connectionType=customurl
Jun 03 14:46:13 prometheus java[26605]: db.customURL=jdbc:eek:racle:thin:mad:(DESCRIPTION=(FAILOVER=on)(LOAD_BALANCE=off)(CONNECT_TIMEOUT=5)(TRANSPORT_CONNECT_TIMEOUT=3)(RETRY_COUNT=3)(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.172)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.17.173)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=monitor)))
Jun 03 14:46:13 prometheus java[26605]: db.password=******
Jun 03 14:46:13 prometheus java[26605]: db.username=prometheus
Jun 03 14:46:13 prometheus java[26605]: resource.templates.enabled=true
Jun 03 14:46:13 prometheus java[26605]: 2021-06-03T11:46:13.419Z WARNING *** jdbc.MaxLimit in configuration |db2104|| is using a value of 10, this setting may not be sized adequately for a production environment ***
Jun 03 14:46:13 prometheus java[26605]: 2021-06-03T11:46:13.420Z WARNING *** jdbc.InitialLimit in configuration |db2104|| is using a value of 3, this setting may not be sized adequately for a production environment ***
Jun 03 14:46:16 prometheus java[26605]: 2021-06-03T11:46:16.164Z INFO Oracle REST Data Services initialized
Jun 03 14:46:16 prometheus java[26605]: Oracle REST Data Services version : 20.4.3.r0501904
Jun 03 14:46:16 prometheus java[26605]: Oracle REST Data Services server info: jetty/9.4.35.v20201120
root@prometheus# ^C

Публикация метрик через HTTP средствами ORDS​

Для публикации метрик и их значений по HTTP на стороне БД реализована PL/SQL процедура. Она публикует метрики с одним и несколькими значениями (метками), публикует список текущих алертов ― метрик, у которых значение превышает установленный порог; и значения установленных порогов в виде метрик с тем же названием и префиксом 'th_', для возможности настройки Alertmanager в последующем.

CREATE OR REPLACE PROCEDURE SMASTER.get_prometheus_metrics (i_debug NUMBER DEFAULT 0)
IS
/******************************************************************************
NAME: get_prometheus_metrics
PURPOSE: Generating Prometheus formatted metrics from the Invoice DB

REVISIONS:
Ver Date Author Description
--------- ---------- --------------- ------------------------------------
1.7 20/07/2021 SGolikov 1. Добавлены метрики со значениями порогов
1.6 15/07/2021 SGolikov 1. Исправена ошибка - в списке открытых алертов отсуствовали labeled алерты
1.5 09/07/2021 SGolikov 1. Добавлен параметр i_debug, с которым формируется только 1, 2, 3 или 4 части или все (0)
2. Добавлено удаление " из имени метки
1.4 30/06/2021 SGolikov 1. Метка host заменена на hostname
1.3 29/06/2021 SGolikov 1. Вызов вокрера для сбора метрик realtime=1
1.2 25/06/2021 SGolikov 1. Добавлена метрика с числом метрик с ошибками расчета
1.1 25/06/2021 SGolikov 1. Добавлен вывод текущих активных метрик
1.0 28/04/2021 SGolikov 1. Created this procedure.
******************************************************************************/
v_db_name VARCHAR2 (100);
v_db_host VARCHAR2 (100);
v_failed_count NUMBER;

CURSOR c_single_metrics IS
SELECT LOWER (c.metric_name) metric_name,
c.VALUE,
metric_type,
d.metric_desc
FROM metric_current c, metric_dict d
WHERE c.metric_name = d.metric_name AND enabled = 1 AND VALUE != -1;

--
CURSOR c_multi_metrics IS
SELECT LOWER (c.metric_name) metric_name,
LOWER (c.label_name) label_name,
TRIM (REGEXP_REPLACE (REGEXP_REPLACE (LOWER (c.label_value), '(\")'), '\s{2,}', ' ')) label_value,
c.VALUE,
metric_type,
d.metric_desc
FROM metric_current_labeled c, metric_dict d
WHERE c.metric_name = d.metric_name AND enabled = 1;

CURSOR c_open_alerts IS
SELECT LOWER (c.metric_name) metric_name,
c.VALUE,
c.threshold,
metric_type,
d.metric_desc
FROM metric_alerts c, metric_dict d
WHERE REGEXP_REPLACE (c.metric_name, '#.*') = d.metric_name AND enabled = 1 AND VALUE != -1;

CURSOR c_metric_thresholds IS
SELECT LOWER (c.metric_name) metric_name,
c.threshold,
metric_type,
d.metric_desc
FROM metric_current c, metric_dict d
WHERE c.metric_name = d.metric_name AND enabled = 1 AND metric_type = 'gauge' AND c.threshold IS NOT NULL;
--

--
BEGIN
-- Сбор метрик с realtime=1
inv_mon.worker (i_realtime => 1);
--
OWA.num_cgi_vars := 0;
HTP.init ();
OWA_UTIL.mime_header ('text/plain', TRUE, 'utf-8');

SELECT SYS_CONTEXT ('USERENV', 'DB_NAME') db_name, SYS_CONTEXT ('USERENV', 'SERVER_HOST') HOST
INTO v_db_name, v_db_host
FROM DUAL;

IF i_debug IN (0, 1)
THEN
--
-- Invoice single value metrics
--
FOR rec_single_metrics IN c_single_metrics
LOOP
HTP.PRINT ('# HELP ' || rec_single_metrics.metric_name || ' ' || rec_single_metrics.metric_desc);
HTP.PRINT ('# HELP ' || rec_single_metrics.metric_name || ' ' || rec_single_metrics.metric_type);
HTP.PRINT (rec_single_metrics.metric_name || '{db_name="' || v_db_name || '",hostname="' || v_db_host || '"} ' || rec_single_metrics.VALUE);
END LOOP;
END IF;

IF i_debug IN (0, 2)
THEN
--
--
-- Invoice multiple value metrics
--
FOR rec_multi_metrics IN c_multi_metrics
LOOP
HTP.PRINT ('# HELP ' || rec_multi_metrics.metric_name || ' ' || rec_multi_metrics.metric_desc);
HTP.PRINT ('# HELP ' || rec_multi_metrics.metric_name || ' ' || rec_multi_metrics.metric_type);
HTP.PRINT (
rec_multi_metrics.metric_name
|| '{db_name="'
|| v_db_name
|| '",hostname="'
|| v_db_host
|| '",'
|| rec_multi_metrics.label_name
|| '="'
|| rec_multi_metrics.label_value
|| '"} '
|| rec_multi_metrics.VALUE);
END LOOP;
END IF;

IF i_debug IN (0, 3)
THEN
--
--
-- Invoice open alerts
--
FOR rec_open_alerts IN c_open_alerts
LOOP
HTP.PRINT ('# HELP open_alert Metrics with a value greater than the threshold');
HTP.PRINT ('# HELP open_alert gauge');
HTP.PRINT (
'open_alert{db_name="'
|| v_db_name
|| '",hostname="'
|| v_db_host
|| '",metric_name="'
|| rec_open_alerts.metric_name
|| '",threshold="'
|| rec_open_alerts.threshold
|| '"} '
|| rec_open_alerts.VALUE);
END LOOP;
END IF;

IF i_debug IN (0, 4)
THEN
--
-- Invoice metric thresholds
--
FOR rec_threshold IN c_metric_thresholds
LOOP
HTP.PRINT ('# HELP threshold for ' || rec_threshold.metric_name || ' ' || rec_threshold.metric_desc);
HTP.PRINT ('# HELP ' || rec_threshold.metric_name || ' ' || rec_threshold.metric_type);
HTP.PRINT ('th_' || rec_threshold.metric_name || '{db_name="' || v_db_name || '",hostname="' || v_db_host || '"} ' || rec_threshold.threshold);
END LOOP;
END IF;


--
--
-- Invoice open alerts
--
SELECT COUNT (*)
INTO v_failed_count
FROM metric_current
WHERE err_msg IS NOT NULL;

HTP.PRINT ('# HELP failed_metrics_count Number of metrics with error');
HTP.PRINT ('# HELP failed_metrics_count counter');
HTP.PRINT ('failed_metrics_count{db_name="' || v_db_name || '",hostname="' || v_db_host || '"} ' || v_failed_count);
END get_prometheus_metrics;
/
Созданному пользователю выдаются права на вызов процедуры и создается синоним

SQL> grant execute on smaster.GET_PROMETHEUS_METRICS to prometheus;

SQL> create synonym prometheus.GET_PROMETHEUS_METRICS for smaster.GET_PROMETHEUS_METRICS;
В результате сервис работает, процедура создана. Открываем в браузере URL в формате:

https://<hostname>:<port>/ords/<db name>/get_prometheus_metrics
Рис.4 Пример работы ORDS сервиса
Рис.4 Пример работы ORDS сервиса

Сбор и накопление метрик в Prometheus​

В конфигурационный файл Prometheus добавлен следующий блок:

- job_name: billing_monitor
metrics_path: /ords/db2104/get_prometheus_metrics
params:
i_debug: ['0']
scrape_interval: 60s
scrape_timeout: 50s
static_configs:
- targets:
- 192.168.хх.хх:7778
labels:
module: "billingdb"
Параметр i_debug я добавил для разделения списка всех метрик на 4 группы. Бывает, при наличии синтаксической ошибки, Prometheus не может распарсить вывод сервиса ORDS, и определить конкретную строку с ошибкой трудно. Мне не удалось найти средство, которое позволяет распарсить вывод и указать, на какой конкретно строке Prometheus спотыкается. Если вам известно решение, напишите в комментариях.

Проверяем статус джоба в прометее:

Рис.5 Статус джоба сбора метрик с биллинга в Prometheus
Рис.5 Статус джоба сбора метрик с биллинга в Prometheus
И доступность метрик:

Рис. 6 Просмотр метрики в Prometheus
Рис. 6 Просмотр метрики в Prometheus

Визуализация метрик в Grafana​

Для качественноого выполнения задачи нужны реальные данные. С одним из наших заказчиков была согласована разработка пилотной версии мониторинга, и уже на реальных данных с небольшого, но боевого комплекса я создал дашборд в Grafana. Источником выступают данные Prometheus.

В верхней левой части скриншота присутствует информация о текущей и исторической утилизации CPU, количество выполняющихся сессий в БД и график событий ожидания активных сессий БД. Зеленый означает выполнение на CPU, синий ― ожидание чтения с диска. В итоге ― полет нормальный. Правее ― таблица текущих открытых алертов. Метрик гораздо больше, чем графиков, и не всегда по каждой метрике требуется график. Метрика попадает на экран, если ее текущее значение превысило установленный порог. Рядом правее ― два счетчика, количество метрик с ошибками расчета и время отставания репликации данных на резервную БД, с которой снимается большинство метрик. Ниже ― метрики самой биллинговой системы.

Рис.7 Дашборд, часть 1
Рис.7 Дашборд, часть 1
Рис. 8 Дашборд, часть 2
Рис. 8 Дашборд, часть 2
Для удобства фильтрации по выбранной БД и хосту я создал переменные hostname и db_name:

Рис. 9 Переменные для фильтров
Рис. 9 Переменные для фильтров
Для большинства метрик используется просто публикация метрики, с фильтром по имени хоста:

Рис. 10  График активности сессий в БД
Рис. 10 График активности сессий в БД
На указанном выше графике отображается статистика ASH по активным сессиям в Oracle с группировкой по классам ожидания ― требуется лицензия Oracle Diagnostic and Tuning Pack.

В ходе разработки дашборда с метриками типа Counter я стокнулся с проблемой. Т.к. метрики собирались асинхронно с опросом источника со стороны Prometheus, т.е. метрика вычислялась джобом и сохранялась в таблицу, а Prometheus с равным интервалом считывал значение метрик через сервис ORDS, то периодически возникала ситуация, когда Prometheus считывал то же значение, что было в предыдущий раз. В какой-то момент дельта изменений счетчика была равна 0, а при следующем опросе получался двойной прирост счетчика. В итоге на графике была гребенка, не соответствующая действительности:

Рис.11 Некорректный график по метрике Скорость тарификации
Рис.11 Некорректный график по метрике "Скорость тарификации"
Я сделал доработку и добавил признак realtime для метрик. Метрики с установленным флагом realtime теперь вычисляются не джобом, а в режиме реального времени, в момент запроса со стороны Prometheus. Все стало правдиво и красиво:

Рис.12 Правильный график по метрике Скорость тарификации
Рис.12 Правильный график по метрике "Скорость тарификации"
Еще одна отличная функция, которую позволяет реализовать Prometheus, ― выявление аномалий. На основе статьи и ее перевода я написал формулы и сделал график, которые позволяют отслеживать факт нахождения текущих показателей метрики в границах, полученных на основе наблюдений за предыдущие 3 недели, с учетом отбрасывания нетипичных (праздничных) дней. И хотя визуальное отслеживание графика в масштабе нескольких часов или даже дней и так позволяет понять, находится ли показатель в норме, функция будет очень полезна именно для формирования алертов в автоматическом режиме. Конкретно эта метрика, по числу тарифицируемых звонков, показала себя не очень хорошо. Случаются "ложные" срабатывания в сторону увеличения числа тарифицируемых событий в момент массовых рассылок. Но такова действительность: запуск рассылки от МЧС или маркетологов трудно предсказать и учесть. В перспективе планирую допилить формулу так, чтобы она не реагировала на резкое увеличение значения метрики.

Рис.13 Выявление аномалий в скорости тарификации
Рис.13 Выявление аномалий в скорости тарификации
Формулы графика:

Рис.14 Формула текущего значения скорости тарификации
Рис.14 Формула текущего значения скорости тарификации
Рис.15 Формула верхней границы типичной скорости тарификации
Рис.15 Формула верхней границы типичной скорости тарификации
Рис.16 Формула нижней границы типичной скорости тарификации
Рис.16 Формула нижней границы типичной скорости тарификации
Дополнительно привожу настройки графика Series overrides, т.к. из оригинальной статьи не совсем очевидно следовало, как отображать именно границу допустимых значений. Я взял коэффициент 1,5:

Рис. 17 Настройки графика
Рис. 17 Настройки графика
Формула алерта:

root# vi alerts.billing.yml
groups:

- name: 'Invoice Alerts'
rules:
- alert: 'calls_ratings_speed_outside_range'
expr: >
abs(
(
job:inv_rating_calls_speed:rate5m -job:inv_rating_calls_speed:rate5m_prediction
) / job:inv_rating_calls_speed:rate5m:stddev_over_time_1w
) > 1.5
for: 15m
labels:
severity: 'warning'
annotations:
title: "Deviation for inv_rating_calls_speed > 1.5x"
description: 'Invoice calls rating speed {{ $labels.db_name }} is outside of expected operating parameters based observations over the past 3 weeks'
Скриншот сформированного алерта:

Рис.18 Пример алерта
Рис.18 Пример алерта
Чтобы визуализация отслеживания аномалий заработала, пришлось подождать несколько недель, пока накопилась история наблюдений и формулы стали выдавать результат.

Рассылка уведомлений​

Помимо отправки уведомлений, реализованной в PL/SQL пакете, имеется возможность настроить Prometheus Alertmanager, который может отправлять алерты Slack, Teams, Telegram, Teams через специальные адаптеры, либо настроить Karma dashboard для удобного отображения алертов из Prometheus Alertmanager.

Пример сформированного алерта в Karma dashboard:

Рис.19 Пример алерта в Karma Alerts dashboard
Рис.19 Пример алерта в Karma Alerts dashboard
На данный момент alertmanager планируется использовать как дополнительную опцию, только в случае явной потребности заказчика в уведомлениях по каналам, отличным от email.

Выводы​

Решение с использованием Oracle REST Data Services, Prometheus, Grafana и с небольшой разработкой на PL/SQL позволило мне быстро и без закупки дополнительных лицензий реализовать мониторинг комплекса биллинговой системы. В едином графическом интерфейсе Grafana я объединил метрики приложения из БД, некоторые статистики работы самой БД Oracle и статистики сервера БД. Теперь в Grafana инженерам Bercut и заказчика доступен мониторинг всего работающего оборудования и ПО с использованием прочих экспортеров для OS Solaris, СХД, SAN и пр. С помощью единой платформы мониторинга время на выявление и локализацию проблемы, по моей оценке, сокращается на 15-60 минут. Используемое ПО позволяет легко добавить в комплекс мониторинга допы в виде отправки уведомлений по различным каналам; быстро адаптировать визуальное отображение данных и комбинировать разные источники метрик в одном дашборде. Grafana + Prometheus + ORDS ― это круто и современно!

 
Сверху