すべてのプロダクト
Search
ドキュメントセンター

ApsaraDB RDS:Simple Log Serviceを使用したApsaraDB RDS for PostgreSQLインスタンスのセッションのモニタリング

最終更新日:Aug 27, 2024

ApsaraDB RDS for PostgreSQLインスタンスでのセッションモニタリングは、RDSインスタンスのステータスを把握し、パフォーマンスの問題をトラブルシューティングするのに役立ちます。 このトピックでは、pg_stat_activityシステムビュー、Simple Log Service、およびCloudMonitorを使用してRDSインスタンスのセッションをモニタリングする方法について説明します。 モニタリング結果を使用して、特定の期間におけるRDSインスタンスのステータスとパフォーマンスを把握できます。

背景情報

ほとんどの場合、データベースシステムは比較的大きなアプリケーションです。 データベースシステムの負荷が大きいと、メモリ、CPU、I/O、ネットワークのリソースが大量に消費されます。 ApsaraDB RDS for PostgreSQLはプロセスモデルを使用し、各セッションはバックグラウンドプロセスに対応します。 セッションモニタリングは、RDSインスタンスのステータスを把握し、RDSインスタンスのパフォーマンスボトルネックをトラブルシューティングするのに役立ちます。

pg_stat_アクティビティ

pg_stat_activityビューは、データベースシステムで実行されているすべてのセッションに関する情報を提供します。 データベースシステム内のセッションを定期的にクエリして、セッション情報を保持できます。 このプロセスは、データベースシステムの通常のスナップショット作成と同等です。 これにより、データベースシステムのステータスを把握し、データベースシステムのパフォーマンスの問題をトラブルシューティングできます。

セッション関連のパラメータ

次の表に、pg_stat_activityビューのパラメーターを示します。

パラメーター

データ型

説明

datid

oid

プロセスが接続されているデータベースのオブジェクト識別子 (OID) 。

datname

name

プロセスが接続されているデータベースの名前。

pid

integer

プロセスID (PID) 。

リーダー_pid

integer

並列処理またはアプリケーションジョブのリーダープロセス。 リーダープロセスまたはワーカープロセスが存在しない場合、このパラメーターの値はNULLです。

usesysid

oid

プロセスとやり取りするユーザーのOID。

usename

name

プロセスとやり取りするユーザーのユーザー名。

application_name

text

プロセスが接続されているアプリケーションの名前。

client_addr

inet

プロセスが接続されているクライアントのIPアドレス。

client_hostname

text

クライアントのホスト名。 client_addrの逆ドメインネームシステム (DNS) ルックアップを実行して、このパラメーターの値を取得できます。

client_port

integer

クライアントによって使用されるTCPポート。 UNIXソケットを使用する場合、このパラメーターの値は-1です。

backend_start

タイムゾーンのタイムスタンプ

プロセスの開始時間。

xact_start

タイムゾーンのタイムスタンプ

プロセスの現在のトランザクションの開始時刻。 アクティブなトランザクションが存在しない場合、このパラメーターの値はNULLです。

query_start

タイムゾーンのタイムスタンプ

進行中のクエリの開始時刻。

state_change

タイムゾーンのタイムスタンプ

ステータスが変更された最新の時刻。

wait_event_タイプ

text

プロセスで待機しているイベントのタイプ。

wait_event

text

プロセスで待機しているイベントの名前。 待機中のイベントがない場合、このパラメーターの値はNULLです。

state

text

現在のプロセスのステータス。

backend_xid

xid

現在のプロセスの最上位トランザクションID。

backend_xmin

xid

現在のプロセスの最小トランザクションID。

query_id

bigint

プロセスの最新のクエリID。

query

text

プロセスの最新のクエリのテキスト。

backend_type

text

プロセスタイプ。

セッション収集のSQL文

このセクションでは、セッション収集のサンプルSQL文を示します。 leader_idパラメーターは、PostgreSQL13以降を実行するRDSインスタンスでのみ使用できます。 RDSインスタンスのデータベースエンジンバージョンに基づいて、セッション収集用のSQL文を調整できます。

セッション収集のSQL文

SELECT
    (
        CASE
            WHEN leader_pid is NULL THEN pid
            ELSE leader_pid
        END
    ) AS leader_pid,
    (
        CASE
            WHEN state_change <= now() AND state != 'active' THEN extract(
                epoch
                FROM
                    state_change - query_start
            )
            ELSE extract(
                epoch
                FROM
                    now() - query_start
            )
        END
    ) AS query_duration,
    (
        CASE
            WHEN wait_event_type is NULL THEN 'CPU'
            ELSE coalesce(wait_event_type || '.' || wait_event, '')
        END
    ) AS wait_entry,
    query_id,
    (
        CASE
            WHEN state = 'active' THEN 'running'
            ELSE 'finished'
        END
    ) AS execute_state,
    query,
    datname,
    application_name,
    client_hostname,
    query_start
FROM
    pg_stat_activity
WHERE
    usename NOT IN ('aurora', 'replicator')
    AND backend_type IN ('client backend','parallel worker');

前提条件

  • RAMユーザーが作成され、次の権限がRAMユーザーに付与されます。 詳細については、「RAMユーザーの作成とRAMユーザーへの権限付与」をご参照ください。

    • AliyunRDSFullAccess: すべてのApsaraDB RDSリソースを管理するための権限。

    • AliyunLogFullAccess: すべてのSimple Log Serviceリソースを管理するための権限。

    • AliyunCloudMonitorFullAccess: CloudMonitorリソースを管理するための権限。

  • プロジェクトと Logstore が作成済みである必要があります。 詳細については、「Simple Log Serviceの有効化」をご参照ください。

  • RDSインスタンスに必要なデータベースのユーザー名とパスワードが取得されます。 詳細については、「アカウントと権限」をご参照ください。

手順

  1. pg_stat_activityシステムビューでセッション情報を収集し、収集した情報を定期的にSimple Log ServiceのLogstoreに送信します。

    このセクションでは、Simple Log Service SDK for Javaを使用して定期的にデータを収集および送信する方法の例を示します。

    1. Simple Log Service SDK for Javaをインストールします。 詳細については、「Simple Log Service SDK For Javaのインストール」をご参照ください。

    2. ALIBABA_CLOUD_ACCESS_KEY_IDおよびALIBABA_CLOUD_ACCESS_KEY_SECRET環境変数を設定します。 詳細については、「環境変数の設定」をご参照ください。

    3. 必要な依存関係をpom.xmlファイルにインポートします。 この例では、Mavenプロジェクトが使用され、Mavenの依存関係が必要です。

      pom.xml

              <dependency>
                  <groupId>com.aliyun.openservices</groupId>
                  <artifactId>aliyun-log</artifactId>
                  <version>0.6.75</version>
              </dependency>
              <dependency>
                  <groupId>org.postgresql</groupId>
                  <artifactId>postgresql</artifactId>
                  <version>42.2.18</version>
              </dependency>
              <dependency>
                  <groupId>com.aliyun</groupId>
                  <artifactId>tea-openapi</artifactId>
                  <version>0.3.2</version>
              </dependency>
              <dependency>
                  <groupId>com.aliyun</groupId>
                  <artifactId>tea-console</artifactId>
                  <version>0.0.1</version>
              </dependency>
              <dependency>
                  <groupId>com.aliyun</groupId>
                  <artifactId>tea-util</artifactId>
                  <version>0.2.21</version>
              </dependency>
              <!-- Add the Lombok dependency. -->
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>1.18.4</version> <!-- Make sure that the most recent version is used. -->
                  <scope>provided</scope>
              </dependency>
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>RELEASE</version>
                  <scope>provided</scope>
              </dependency>
              <dependency>
                  <groupId>ch.qos.logback</groupId>
                  <artifactId>logback-classic</artifactId>
                  <version>1.2.3</version>
              </dependency>
              <dependency>
                  <groupId>ch.qos.logback</groupId>
                  <artifactId>logback-core</artifactId>
                  <version>1.2.3</version>
              </dependency>
              <dependency>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-api</artifactId>
                  <version>1.7.30</version>
              </dependency>
              <dependency>
                  <groupId>com.aliyun</groupId>
                  <artifactId>rds20140815</artifactId>
                  <version>5.0.1</version>
              </dependency>
              <dependency>
                  <groupId>com.aliyun</groupId>
                  <artifactId>alibabacloud-sls20201230</artifactId>
                  <version>4.0.7</version>
              </dependency>
    4. 次のサンプルプログラムを実行して、RDSインスタンスに関するセッション情報を収集し、収集した情報をSimple Log ServiceのLogstoreに送信します。

      PgMonitor.java

      package org.example;
      
      import com.aliyun.openservices.log.Client;
      import com.aliyun.openservices.log.common.LogItem;
      import com.aliyun.openservices.log.exception.LogException;
      import com.aliyun.openservices.log.request.PutLogsRequest;
      
      import java.sql.*;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      
      public class PgMonitor {
      
          // The information that is used to connect to the RDS instance on which the database is connected.
          private static final String PG_URL = "<jdbc:postgresql://your-host:5432/mydb>";
          private static final String PG_USER = "<your-user>";
          private static final String PG_PASSWORD = "<your-passwd>";
      
          // Information about Alibaba Cloud Simple Log Service.
          private static final String LOG_ENDPOINT = "<your-sls-endpoint>";
          private static final String LOG_PROJECT = "<your-project>";
          private static final String LOG_STORE = "<your-logStore>";
          private static final String ACCESS_KEY_ID = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
          private static final String ACCESS_KEY_SECRET = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
      
          // The interval at which the scheduled task is executed. Unit: minutes.
          private static final int INTERVAL_MINUTES = 1;
      
          public static void main(String[] args) {
              ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
              Runnable task = () -> {
                  try {
                      // Collect activity data from the RDS instance on which the database is connected.
                      List<LogItem> logItems = fetchPgStatActivity();
      
                      // Send collected data to Alibaba Cloud Simple Log Service.
                      sendLogsToSLS(logItems);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              };
      
              // Execute the task at regular intervals. INTERVAL_MINUTES specifies the time interval.
              scheduler.scheduleAtFixedRate(task, 0, INTERVAL_MINUTES, TimeUnit.MINUTES);
          }
      
          private static List<LogItem> fetchPgStatActivity() throws SQLException {
              List<LogItem> logItems = new ArrayList<>();
      
              Connection conn = DriverManager.getConnection(PG_URL, PG_USER, PG_PASSWORD);
              Statement stmt = conn.createStatement();
              String query = "SELECT ( CASE WHEN leader_pid is NULL THEN pid ELSE leader_pid END ) as leader_pid, ( CASE WHEN state_change <= now() AND state != 'active' THEN extract( epoch from state_change - query_start ) ELSE extract( epoch from now() - query_start ) END ) AS query_duration, ( CASE WHEN wait_event_type is NULL THEN 'CPU' ELSE coalesce(wait_event_type || '.' || wait_event, '') END ) AS wait_entry, query_id, ( CASE WHEN state = 'active' THEN 'running' ELSE 'finished' END ) AS execute_state, query, datname, application_name, client_hostname, query_start FROM pg_stat_activity WHERE usename NOT IN ('aurora', 'replicator') AND backend_type IN ('client backend','parallel worker')";
              ResultSet rs = stmt.executeQuery(query);
      
              while (rs.next()) {
                  LogItem logItem = new LogItem();
                  logItem.PushBack("leader_pid", rs.getString("leader_pid"));
                  logItem.PushBack("query_duration", rs.getString("query_duration"));
                  logItem.PushBack("wait_entry", rs.getString("wait_entry"));
                  logItem.PushBack("query", rs.getString("query"));
                  logItem.PushBack("datname", rs.getString("datname"));
                  logItem.PushBack("application_name", rs.getString("application_name"));
                  logItem.PushBack("client_hostname", rs.getString("client_hostname"));
                  logItem.PushBack("query_start", rs.getString("query_start"));
                  logItems.add(logItem);
              }
      
              rs.close();
              stmt.close();
              conn.close();
      
              return logItems;
          }
      
          private static void sendLogsToSLS(List<LogItem> logItems) throws LogException {
              Client client = new Client(LOG_ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
              String topic = "pg_stat_activity";
              String source = "postgresql-monitor";
      
              PutLogsRequest request = new PutLogsRequest(LOG_PROJECT, LOG_STORE, topic, source, logItems);
              client.PutLogs(request);
          }
      }

      特定のパラメーターを次の表に示します。 ビジネス要件に基づいてパラメーターを変更できます。

      パラメーター

      説明

      PG_URL

      RDSインスタンスへの接続に使用されるURL。 値には、監視するデータベースの名前を含める必要があります。

      jdbc:postgresql:// pgm-bp1c82mky1avip **** .pg.rds.aliyuncs.com:5432/testdb01

      PG_ユーザー

      データベースへのログインに使用されるアカウントのユーザー名。

      testdbuser

      PG_パスワード

      データベースへのログインに使用されるアカウントのパスワード。

      ****

      LOG_ENDPOINT

      Simple Log Serviceのエンドポイント。 詳細については、「エンドポイント」をご参照ください。

      cn-hangzhou.log.aliyuncs.com

      LOG_プロジェクト

      Simple Log Serviceのプロジェクトの名前。

      rdspg-test

      LOG_STORE

      Simple Log ServiceのLogstoreの名前。

      rdspg-sls

      上記の操作が完了したら、Simple Log ServiceのLogstoreでRDSインスタンスのセッションログを照会できます。 詳細については、「ログの照会と分析」をご参照ください。

      重要

      Simple Log ServiceのLogstoreでインデックスを作成した後にのみ、セッションログを照会および分析できます。 詳細については、「インデックスの作成」をご参照ください。

  2. Simple Log ServiceのセッションログをCloudMonitorにインポートします。

    CloudMonitorコンソールにログインし、メトリックを作成し、Simple Log ServiceからCloudMonitorにログをインポートします。 Simple Log ServiceからCloudMonitorにログをインポートした後、ダッシュボードを作成して測定値のモニタリングチャートを表示できます。 詳細については、「Simple Log Serviceからインポートされたログのメトリクスの管理」をご参照ください。

  3. オプションです。 ApsaraDB RDS APIを使用して、スロークエリログとエラーログをSimple Log Serviceにインポートします。

    RDSインスタンスのステータスとパフォーマンスを分析し、インスタンスセッション、スロークエリログ、およびエラーログに関するモニタリング情報に基づいてパフォーマンスの問題をトラブルシューティングできます。

    このセクションでは、ApsaraDB RDSのDescribeSlowLogRecordsおよびDescribeErrorLogs操作とともにSimple Log Service SDK for Javaを使用して、RDSインスタンスのスロークエリログとエラーログを取得し、取得したログを定期的にSimple Log Serviceに送信する方法について説明します。 サンプルコードを次に示します。

    説明

    操作のAPIドキュメントで [デバッグ] をクリックすると、OpenAPI Explorerの [デバッグ] ページに移動し、操作のインストール方法と使用方法を確認できます。

    PgCollectLogOpt.java

    package org.example;
    
    import com.aliyun.openservices.log.Client;
    import com.aliyun.openservices.log.common.LogItem;
    import com.aliyun.openservices.log.exception.LogException;
    import com.aliyun.openservices.log.request.PutLogsRequest;
    import lombok.extern.slf4j.Slf4j;
    
    import java.time.*;
    import java.time.format.DateTimeFormatter;
    import java.time.temporal.ChronoUnit;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    public class PgCollectLogOpt {
        private static final String LOG_ENDPOINT = "your-endpoint";
        private static final String LOG_PROJECT = "your-project";
        private static final String LOG_STORE = "your-log-store";
        private static final String ACCESS_KEY_ID = "your-access-key-id";
        private static final String ACCESS_KEY_SECRET = "your-access-key-secret";
        private static final int INTERVAL_MINUTES = 10;
        private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'");
        private static volatile String lastEndTime = getCurrentTime();
    
        public static com.aliyun.rds20140815.Client createClient() throws Exception {
            com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
                    .setAccessKeyId(ACCESS_KEY_ID)
                    .setAccessKeySecret(ACCESS_KEY_SECRET);
            config.endpoint = "rds.aliyuncs.com";
            return new com.aliyun.rds20140815.Client(config);
        }
    
        public static void main(String[] args) throws Exception {
            ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
            executor.scheduleAtFixedRate(() -> {
                try {
                    collectAndSendLogs();
                } catch (Exception e) {
                    log.error("Error during log collection and sending: ", e);
                }
            }, 0, INTERVAL_MINUTES, TimeUnit.MINUTES);
        }
    
        private static void collectAndSendLogs() throws Exception {
            com.aliyun.rds20140815.Client client = createClient();
            String instanceId = "pgm-bp1nz4ed24u6679d";
            String startTime = lastEndTime;
            String endTime = getNextEndTime(startTime);
    
            try {
                List<LogItem> slowLogs = getLogs(client, instanceId, startTime, endTime, LogType.SLOW);
                if (!slowLogs.isEmpty()) sendLogsToSLS(slowLogs);
    
                List<LogItem> errorLogs = getLogs(client, instanceId, startTime, endTime, LogType.ERROR);
                if (!errorLogs.isEmpty()) sendLogsToSLS(errorLogs);
    
                lastEndTime = endTime;
            } catch (Exception e) {
                log.error("Log collection error: ", e);
            }
        }
    
        private static List<LogItem> getLogs(com.aliyun.rds20140815.Client client, String instanceId, String startTime, String endTime, LogType logType) throws Exception {
            List<LogItem> logItems = new ArrayList<>();
            int pageNumber = 1, totalPage;
    
            do {
                totalPage = fetchAndProcessLogs(client, instanceId, startTime, endTime, logType, pageNumber, logItems);
                pageNumber++;
            } while (pageNumber <= totalPage);
    
            return logItems;
        }
    
        private static int fetchAndProcessLogs(com.aliyun.rds20140815.Client client, String instanceId, String startTime, String endTime, LogType logType, int pageNumber, List<LogItem> logItems) throws Exception {
            if (logType == LogType.SLOW) {
                var request = new com.aliyun.rds20140815.models.DescribeSlowLogRecordsRequest()
                        .setDBInstanceId(instanceId)
                        .setStartTime(startTime)
                        .setEndTime(endTime)
                        .setPageNumber(pageNumber)
                        .setPageSize(100);
                var response = client.describeSlowLogRecordsWithOptions(request, new com.aliyun.teautil.models.RuntimeOptions());
                var items = response.getBody().getItems().getSQLSlowRecord();
                items.forEach(item -> logItems.add(createLogItem(item.getExecutionStartTime(), item.getSQLText(), item.getHostAddress(), item.getDBName(), item.getQueryTimes().toString(), item.getLockTimes().toString())));
                return (int) Math.ceil((double) response.getBody().getTotalRecordCount() / response.getBody().getPageRecordCount());
            } else {
                var request = new com.aliyun.rds20140815.models.DescribeErrorLogsRequest()
                        .setDBInstanceId(instanceId)
                        .setStartTime(startTime)
                        .setEndTime(endTime)
                        .setPageNumber(pageNumber)
                        .setPageSize(100);
                var response = client.describeErrorLogsWithOptions(request, new com.aliyun.teautil.models.RuntimeOptions());
                var items = response.getBody().getItems().getErrorLog();
                items.forEach(item -> logItems.add(createLogItem(item.getCreateTime(), item.getErrorInfo())));
                return (int) Math.ceil((double) response.getBody().getTotalRecordCount() / response.getBody().getPageRecordCount());
            }
        }
    
        private static LogItem createLogItem(String utcTime, String... fields) {
            LogItem logItem = new LogItem();
            String collectTime = convertToBeijingTime(utcTime);
            logItem.PushBack("collectTime", collectTime);
            String[] fieldNames = {"sql", "hostAddress", "dbName", "queryTimes", "lockTimes", "errorInfo"};
    
            for (int i = 0; i < fields.length; i++) {
                logItem.PushBack(fieldNames[i], fields[i]);
            }
    
            return logItem;
        }
    
        private static String convertToBeijingTime(String utcTime) {
            Instant instant = Instant.parse(utcTime);
            ZonedDateTime utcDateTime = instant.atZone(ZoneId.of("UTC"));
            ZonedDateTime beijingDateTime = utcDateTime.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
            return DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX").format(beijingDateTime);
        }
    
        private static void sendLogsToSLS(List<LogItem> logItems) throws LogException {
            Client slsClient = new Client(LOG_ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
            PutLogsRequest request = new PutLogsRequest(LOG_PROJECT, LOG_STORE, "pg_stat_activity", "postgresql-monitor", logItems);
            slsClient.PutLogs(request);
        }
    
        private static String getCurrentTime() {
            return formatToIsoInstantWithoutMillis(Instant.now().minus(Duration.ofMinutes(10)).atZone(ZoneId.of("UTC")));
        }
    
        private static String getNextEndTime(String startTime) {
            Instant startInstant = Instant.parse(startTime.replace("Z", ":00Z"));
            return formatToIsoInstantWithoutMillis(startInstant.atZone(ZoneId.of("UTC")).plusMinutes(10).plusSeconds(1));
        }
    
        private static String formatToIsoInstantWithoutMillis(ZonedDateTime zdt) {
            return DATE_FORMATTER.format(zdt.truncatedTo(ChronoUnit.MINUTES));
        }
    
        private enum LogType {
            SLOW, ERROR;
        }
    }
    

    特定のパラメーターを次の表に示します。 ビジネス要件に基づいてパラメーターを変更できます。

    パラメーター

    説明

    LOG_ENDPOINT

    Simple Log Serviceのエンドポイント。 詳細については、「エンドポイント」をご参照ください。

    cn-hangzhou.log.aliyuncs.com

    LOG_プロジェクト

    Simple Log Serviceのプロジェクトの名前。

    rdspg-test

    LOG_STORE

    Simple Log ServiceのLogstoreの名前。

    rdspg-sls

    instanceId

    インスタンス ID 。

    pgm-bp1c82mky1av ****

  4. 作成したメトリックに基づいてCloudMonitorでアラートルールを作成します。 リソースのメトリック値がアラート条件を満たしている場合、CloudMonitorは自動的にアラート通知を送信します。 詳細については、「アラートルールの作成」をご参照ください。