Prometheus + Grafana で Oracle Transactional Event Queues の監視を行う

# はじめに
Oracle Database 23ai(21c) には、多機能なメッセージ・キューイング・システムであるAdvanced Queuing(AQ)と、高スループットのメッセージ交換を可能にするTransactional Event Queues(TxEventQ)という無償で使用できる機能があります。
今回は、この機能を使うにあたり、パフォーマンス監視を行うTxEventQ Monitorシステムを構築してみたいと思います。TxEventQの詳細はこちらの<a href="https://docs.oracle.com/cd/F82042_01/adque/aq-introduction.html#GUID-95868022-ECDA-4685-9D0A-52ED7663C84B" target="_blank" rel="noopener noreferrer">ドキュメント</a>や以下のQiita記事を参照してください。

https://qiita.com/wahagon/items/12be299a47f9a9646b57

コンポーネントとしては、以下の3つです。
+ Oracle Database Observability Exporter: Oracle Database用のPrometheusエクスポータ。データベースに接続し、メトリックを問い合せて、メトリックをPrometheus様式のメトリックに書式化します。

+ Prometheus: 監視システムおよび時系列データベース。Oracle DBエクスポータから収集したメトリックを時系列形式で管理します。

+ Grafana: 分析およびインタラクティブな視覚化プラットフォーム。データ・ソースとしてPrometheusを指定します。

ドキュメントは<a href="https://docs.oracle.com/cd/F82042_01/adque/monitoring_TEQ_AQ.html#GUID-9F073B3D-F4E8-4F40-A82C-89B85176F759" target="_blank" rel="noopener noreferrer">こちら</a>です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/44586776-8864-d917-9d97-183e13122dae.png)

# 環境情報
監視フレームワークを構築する仮想マシンの情報
+ シェイプ: VM.Standard.E4.Flex
+ イメージ: Oracle Linux 8.8
+ Docker: 24.0.7
+ Docker Compose: 2.21.0

監視対象のOracle Database
+ Oracle Database 23.3(OCIのBaseDB)

# 監視用データベース・ユーザーの作成
今回はOCI上に23ai(23.3)のBaseDBを作成し、そのうえに`pdb23ai`というPDBを作成しています。これを監視対象のPDBとし、その中に監視用のデータベース・ユーザー`monitor`を作成しています。

1. SYSでユーザー作成
`SELECT_CATALOG_ROLE`を付与することで、Prometheusで収集するメトリックのディレクトリなどの参照権限が与えられます。
    ```sql
    create user monitor identified by Welcome12345#;
    grant connect, resource, DBA, SELECT_CATALOG_ROLE, unlimited tablespace to monitor; 
    ```

2. TxEventQ関連のロール付与
    ```sql
    grant execute on dbms_aq to monitor;
    grant execute on dbms_aqadm to monitor;
    grant execute on dbms_aqin to monitor;
    grant execute on dbms_aqjms_internal to monitor;
    grant execute on dbms_teqk to monitor;
    grant execute on DBMS_RESOURCE_MANAGER to monitor;
    grant select on sys.aq$_queue_shards to monitor;
    grant select on user_queue_partition_assignment_table to monitor;
    ```

3. 仮想マシンからSQL*Plusで`monitor`でpdb23aiに接続できるか確認(簡易接続)
    ```sql
    sqlplus monitor/Welcome12345#@xxxx:1521/pdb23ai.xxxx.vcn1.oraclevcn.com

    SQL*Plus: Release 21.0.0.0.0 - Production on Tue Nov 14 06:26:35 2023
    Version 21.9.0.0.0
    
    Copyright (c) 1982, 2022, Oracle.  All rights reserved.
    
    Last Successful login time: Tue Nov 14 2023 06:26:23 +00:00
    
    Connected to:
    Oracle Database 23c Enterprise Edition Release 23.0.0.0.0 - Production
    Version 23.3.0.23.09
    ```

# Oracle Database Observability Exporter の設定
Oracle Database Observability Exporterは、<a href="https://container-registry.oracle.com/ords/f?p=113:4:118142858228365:::4:P4_REPOSITORY,AI_REPOSITORY,AI_REPOSITORY_NAME,P4_REPOSITORY_NAME,P4_EULA_ID,P4_BUSINESS_AREA_ID:1611,1611,Oracle%20Database%20Observability%20Exporter%20(Metrics,%20Logs,%20and%20Tracing),Oracle%20Database%20Observability%20Exporter%20(Metrics,%20Logs,%20and%20Tracing),1,0&cs=3ZQnic1zRSEsbS_hzZqd-uTgogK1IUuMliNWfh3KCL2c4-yRATS-VM2j3DkVvTvAyrPFEODnYbcPer8SrPSW2qw" target="_blank" rel="noopener noreferrer">Oracle Container Registry</a>からダウンロードできるコンテナ・イメージを使って、ローカルコンテナ内で利用できます。なお認証やライセンスの提示/承認は必要ありません。

ちなみに、このOracle Container Registryには、Autonomous DatabaseのFreeイメージや、Oracle Transaction Manager for Microservices (MicroTx)のイメージもあります。

1. 作業用ディレクトリの作成
    ```sh
    mkdir -p monitoring/exporter
    cd monitoring/exporter
    ```
    
2. カスタムメトリックの設定ファイル`txeventq-metrics.toml`の作成
    今回はデフォルトのメトリック以外にも、TxEventQ用のメトリックを収集したいので、tomlファイルを作成し、それを利用したDocker Imageを作成します。
    ```txeventq-metrics.toml
    [[metric]]
    context = "ownership"
    metricsdesc = { inst_id = "Owner instance of the current queues." }
    request = '''
    SELECT
        inst_id
    FROM
        gv$persistent_queues
    WHERE
        ROWNUM < 2
    '''
    [[metric]]
    context = "teq"
    metricsdesc = { curr_inst_id = "ID of current instance" }
    request = "SELECT instance_number AS curr_inst_id FROM v$instance"
    
    [[metric]]
    context = "teq"
    labels = ["inst_id"]
    metricsdesc = { total_queues = "Total number of queues"}
    request = '''
    SELECT
        inst_id,
        COUNT(*) AS total_queues
    FROM
        (
            SELECT DISTINCT
                t1.inst_id,
                t2.queue_name
            FROM
                gv$aq_sharded_subscriber_stat   t1
                JOIN gv$persistent_queues            t2 ON t1.queue_id = t2.queue_id
        )
    GROUP BY
        inst_id
    '''
    
    [[metric]]
    context = "teq"
    labels = ["inst_id"]
    metricsdesc = { total_subscribers = "Total number of subscribers"}
    request = '''
    SELECT
        inst_id,
        COUNT(*) AS total_subscribers
    FROM
        (
            SELECT DISTINCT
                inst_id,
                subscriber_id
            FROM
                gv$aq_sharded_subscriber_stat
        )
    GROUP BY
        inst_id
    '''
    [[metric]]
    context = "teq"
    labels = ["inst_id", "queue_name", "subscriber_name"]
    metricsdesc = { enqueued_msgs = "Total enqueued messages.", dequeued_msgs = "Total dequeued messages.", remained_msgs = "Total remained messages.", time_since_last_dequeue = "Time since last dequeue.", estd_time_to_drain_no_enq = "Estimated time to drain if no enqueue.", message_latency_1 = "Message latency for last 5 mins.", message_latency_2 = "Message latency for last 1 hour.", message_latency_3 = "Message latency for last 5 hours."}
    request = '''
    SELECT DISTINCT
        t1.inst_id,
        t1.queue_id,
        t2.queue_name,
        t1.subscriber_id AS subscriber_name,
        t1.enqueued_msgs,
        t1.dequeued_msgs,
        t1.remained_msgs,
        t1.time_since_last_dequeue,
        t1.estd_time_to_drain_no_enq,
        t1.message_latency_1,
        t1.message_latency_2,
        t1.message_latency_3
    FROM
        (
            SELECT
                inst_id,
                queue_id,
                subscriber_id,
                SUM(enqueued_msgs) AS enqueued_msgs,
                SUM(dequeued_msgs) AS dequeued_msgs,
                SUM(enqueued_msgs - dequeued_msgs) AS remained_msgs,
                MIN(time_since_last_dequeue) AS time_since_last_dequeue,
                MAX(estd_time_to_drain_no_enq) AS estd_time_to_drain_no_enq,
                AVG(10) AS message_latency_1,
                AVG(20) AS message_latency_2,
                AVG(30) AS message_latency_3
            FROM
                gv$aq_sharded_subscriber_stat
            GROUP BY
                queue_id,
                subscriber_id,
                inst_id
        ) t1
        JOIN gv$persistent_queues t2 ON t1.queue_id = t2.queue_id
    '''
    ```
    Dockerfile
    
    ```Dockerfile
    FROM container-registry.oracle.com/database/observability-exporter:1.1.0
    COPY txeventq-metrics.toml /
    ENTRYPOINT ["/oracledb_exporter", "--custom.metrics", "/txeventq-metrics.toml"]
    ```

3. ビルド
    ```sh
    docker build -t txeventq_exporter:1.0 .
    ```

    ```sh
    docker images
    REPOSITORY          TAG       IMAGE ID       CREATED          SIZE
    txeventq_exporter   1.0       8169e80d23bb   12 minutes ago   709MB
    ```

4. テスト実行
    ```sh
    docker run -it --rm --name exporter -e DB_USERNAME=MONITOR -e DB_PASSWORD=Welcome12345# -e DB_CONNECT_STRING=xxxx:1521/pdb23ai.xxxx.vcn1.oraclevcn.com -p 9161:9161 txeventq_exporter:1.0
    ```
    
5. ローカル(http://localhost:9161/metrics) で確認
    ```
    # HELP oracledb_up Whether the Oracle database server is up.
    # TYPE oracledb_up gauge
    oracledb_up 1
    ```
   たくさんメトリックが取られていますが、その中の1つ、`oracledb_up`が1となっていれば、DBサーバーに接続できていることになります。

# Prometheusの設定
1. 作業用ディレクトリの作成
    ```sh
    mkdir -p monitoring/prometheus
    cd monitoring/prometheus
    ```

2. Prometheusの設定ファイル`prometheus.yaml`作成
    ```prometheus.yaml
    global:
      scrape_interval: 15s
      scrape_timeout: 10s
      evaluation_interval: 15s
    scrape_configs:
      - job_name: prometheus
        honor_timestamps: true
        scrape_interval: 15s
        scrape_timeout: 10s
        metrics_path: /metrics
        static_configs:
        - targets:
          - localhost:9090
      - job_name: oracle-exporter
        scrape_interval: 15s
        scrape_timeout: 10s
        metrics_path: /metrics
        static_configs:
        - targets:
          - exporter:9161
    ```

3. Prometheusの公式イメージのpull
    ```sh
    docker pull prom/prometheus:v2.47.2
    ```

# Grafanaの設定
1. 作業用ディレクトリの作成
    ```sh
    mkdir monitoring/grafana
    ```

2. datasource.yamlの設定
    ```sh
    mkdir monitoring/grafana/datasources
    ```
    
    ```datasource.yaml
    apiVersion: 1

    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus:9090
      isDefault: true
      access: proxy
      editable: true
    ```

3. dashboardの設定
    今回はDBインスタンスの監視用とTxEventQの監視用の2つの作成済みダッシュボードを使います。`dashboards`ディレクトリに、2つのjsonファイル`oracledb.json`と`txeventq.json`、それから設定ファイル`dashboard.yaml`を作成します。なお、oracledb.jsonは<a href="https://github.com/oracle/oracle-db-appdev-monitoring/blob/main/docker-compose/grafana/dashboards/3333_rev1.json" target="_blank" rel="noopener noreferrer">こちら</a>、txeventq.jsonは<a href="https://github.com/oracle/oracle-db-appdev-monitoring/blob/main/docker-compose/grafana/dashboards/txeventq.json" target="_blank" rel="noopener noreferrer">こちら</a>を使用します。
    ```sh
    mkdir monitoring/grafana/dashboards
    ```

    ```dashboard.yaml
    apiVersion: 1

    providers:
      - name: "Dashboard provider"
        orgId: 1
        type: file
        disableDeletion: false
        updateIntervalSeconds: 10
        allowUiUpdates: false
        options:
          path: /var/lib/grafana/dashboards
          foldersFromFilesStructure: true
    ```

4. Grafanaの公式イメージのpull
    ```sh
    docker pull grafana/grafana:10.2.1
    ```

# docker composeの設定
今回、実行はdocker composeを使いました。`monitoring`ディレクトリに以下の`docker-compose.yml`ファイルを作成します。
```docker-compose.yml
services:
  prometheus:
    image: prom/prometheus:v2.47.2
    container_name: prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yaml'
    ports:
      - 9090:9090
    restart: unless-stopped
    volumes:
      - ./prometheus:/etc/prometheus
      - prom_data:/prometheus

  grafana:
    image: grafana/grafana:10.2.1
    container_name: grafana
    ports:
      - 3000:3000
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=grafana
    volumes:
      - ./grafana/datasources:/etc/grafana/provisioning/datasources
      - ./grafana/dashboards:/var/lib/grafana/dashboards
      - ./grafana/datasources/datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yaml
      - ./grafana/dashboards/dashboard.yaml:/etc/grafana/provisioning/dashboards/main.yaml


  exporter:
    image: txeventq_exporter:1.0
    container_name: exporter
    ports:
      - 9161:9161
    environment:
      - DB_USERNAME=monitor
      - DB_PASSWORD=Welcome12345#
      - DB_CONNECT_STRING=xxxx.vcn1.oraclevcn.com:1521/pdb23ai.xxxx.vcn1.oraclevcn.com
    volumes:
      - ./exporter:/exporter

volumes:
  prom_data:
```

※DBの接続文字列`DB_CONNECT_STRING`は適宜修正してください。

最終的なディレクトリ構造はこちらになります。
```sh
[opc@devinsrd monitoring]$ tree
.
├── docker-compose.yml
├── exporter
│   ├── Dockerfile
│   └── txeventq-metrics.toml
├── grafana
│   ├── dashboards
│   │   ├── dashboard.yaml
│   │   ├── oracledb.json
│   │   └── txeventq.json
│   └── datasources
│       └── datasource.yaml
└── prometheus
    └── prometheus.yaml
```

docker composeを実行します。
```sh
docker compose up -d

[+] Running 5/5
 ✔ Network monitoring_default     Created                                                0.2s
 ✔ Volume "monitoring_prom_data"  Created                                                0.0s
 ✔ Container prometheus           Started                                                0.1s
 ✔ Container grafana              Started                                                0.1s
 ✔ Container exporter             Started                                                0.1s
```

ローカル(http://localhost:3000)で確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/90189009-a56c-676d-4339-7bf690aa0239.png)

ログインして、Dashboardを確認すると、`Oracledb`と`TxEventQ Monitor`の2つがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/1d253efe-bc7e-5959-097d-95ae473c0312.png)
`Oracledb`の方では、以下のようなメトリックがビジュアライズされています。
+ データベースのステータス
+ アクティブなセッション数(Background、user)
+ 実行数
+ 待機時間

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/a64ec38f-9673-c529-2c9a-78b3eb9c7d22.png)

続いて、TxEventQの監視をしてみます。まだキューを作成していないので、以下で作成しておきます。
```plsql
declare
    subscriber sys.aq$_agent;
begin
  -- トピック(キュー表の作成)
  dbms_aqadm.create_transactional_event_queue(
    queue_name => 'my_topic',
    multiple_consumers => true  -- pub/subトピックの作成
  );

  -- トピックの開始
  dbms_aqadm.start_queue(
    queue_name => 'my_topic'
  );

  -- サブスクライバの登録
  dbms_aqadm.add_subscriber(
    queue_name => 'my_topic',
    subscriber => sys.aq$_agent(
      'my_subscriber',    -- サブスクライバ名
      null,               -- 通知に使用されるメールアドレス
      0                   -- プロトコル
    ),
    rule => 'correlation = ''my_subscriber'''
  );
end;
/
```
作成に失敗する場合は、権限を確認してください。なお、`dbms_aqadm.create_transactional_event_queue`はOracle Database 21c、23aiでのみ使用可能です。

キュー表を作成できたので、以下でキュー表にメッセージを送信(エンキュー)してみます。
```plsql
declare
    enqueue_options    dbms_aq.enqueue_options_t;
    message_properties dbms_aq.message_properties_t;
    message_handle     raw(16);
    message            SYS.AQ$_JMS_TEXT_MESSAGE;
begin
    -- メッセージ・ペイロードの作成
    message := SYS.AQ$_JMS_TEXT_MESSAGE.construct;
    message.set_text('{"orderid": 12345, "username": "Jessica Smith"}');

    -- コンシューマ名の登録
    message_properties.correlation := 'my_subscriber';
    
    -- メッセージのエンキュー
    dbms_aq.enqueue(
        queue_name           => 'my_topic',
        enqueue_options      => enqueue_options,
        message_properties   => message_properties,
        payload              => message,
        msgid                => message_handle);

    -- トランザクションのコミット
    commit;
end;
/
```

エンキューができたので、Grafana側で確認します。Dashboardの`TxEventQ Monitor`を確認してみます。
以下のようなメトリックが確認できます。
+ TxEventQのステータス
+ キュー表(トピック)の数
+ サブスクライバの数
+ エンキュー/デキュー率(性能指標)
+ エンキュー/デキューされたメッセージ数

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/5bf0c872-bfee-04a3-69ba-c1de91724d47.png)

また、以下のように各トピックやサブスクライバに限定したメトリックも取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1930162/a7a0b238-926b-39e1-9849-4920d4c7bfe0.png)

# おわりに
今回はOracle Transactional Event Queuesの監視を、Oracle Database Observability Exporter + Prometheus + Grafanaで行ってみました。Oracle Databaseの監視には、Enterprise Manager(EM)や、クラウドではDatabase Managementといったサービスを使用できますが、特定のキューに関するメトリック監視を行う場合には、このようなOSSツールを組み合わせて行うことができます。