排除 Azure PostgreSQL 数据库弹性群集中的 CPU 使用率过高问题

本文介绍如何确定 CPU 利用率高的根本原因。 它还提供了在 Azure Database for PostgreSQL 中使用 Elastic 群集时,控制 CPU 利用率的可能补救措施。

在本文中,你将了解:

  • 如何使用Azure指标、pg_stat_statementscitus_stat_activitypg_stat_activity 等工具来识别高 CPU 使用率。
  • 如何确定根本原因,例如长时间运行的查询和总连接。
  • 如何通过使用 EXPLAIN ANALYZE 和对表进行真空处理来解决 CPU 使用率过高的问题。

用于识别高 CPU 利用率的工具

使用以下工具来识别高 CPU 使用率:

Azure指标

Azure指标是检查特定时间段的 CPU 使用率的良好起点。 指标提供有关在监视期间使用的资源的信息。 可以使用 “应用拆分 ”选项和 “按服务器名称拆分 ”查看弹性群集中每个节点的详细信息。 然后,可以将 写入 IOP、读取 IOP、读取吞吐量字节数/秒写入吞吐量字节数/秒的性能CPU 百分比进行比较,以查看在发现工作负荷占用大量 CPU 时各个节点的性能。

识别 CPU 使用率高于预期的特定节点(或节点)后,可以直接连接到一个或多个相关节点,并使用以下 Postgres 工具执行更深入的分析:

pg_stat_statements

pg_stat_statements 扩展可帮助识别服务器上消耗时间的查询。 有关此扩展的详细信息,请参阅详细 文档

调用/平均值和总执行时间

以下查询按最高总执行时间返回前五个 SQL 语句:

SELECT userid::regrole, dbid, query, total_exec_time, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY total_exec_time
DESC LIMIT 5;

pg_stat_activity

pg_stat_activity 视图显示当前在特定节点上运行的查询。 使用它监视该节点上的活动查询、会话和状态。

SELECT *, now() - xact_start AS duration
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'active') AND pid <> pg_backend_pid()
ORDER BY duration DESC;

citus_stat_activity

视图 citus_stat_activitypg_stat_activity 的超集。 它显示在所有节点上运行的分布式查询。 它还显示分派到工作节点的子查询相关的任务、任务状态和工作节点。

SELECT *, now() - xact_start AS duration
FROM citus_stat_activity
WHERE state IN ('idle in transaction', 'active') AND pid <> pg_backend_pid()
ORDER BY duration DESC;

确定根本原因

如果 CPU 消耗级别较高,则以下情况可能是根本原因:

特定节点上长时间运行的事务

长时间运行的事务消耗 CPU 资源并导致 CPU 使用率较高。

以下查询提供有关长时间运行的事务的信息:

SELECT
    pid,
    datname,
    usename,
    application_name,
    client_addr,
    backend_start,
    query_start,
    now() - query_start AS duration,
    state,
    wait_event,
    wait_event_type,
    query
FROM pg_stat_activity
WHERE state != 'idle' AND pid <> pg_backend_pid() AND state IN ('idle in transaction', 'active')
ORDER BY now() - query_start DESC;

所有节点上长时间运行的事务

长时间运行的事务消耗 CPU 资源并导致 CPU 使用率较高。

以下查询提供有关所有节点上长时间运行的事务的信息:

SELECT
    global_pid, pid,
    nodeid,
    datname,
    usename,
    application_name,
    client_addr,
    backend_start,
    query_start,
    now() - query_start AS duration,
    state,
    wait_event,
    wait_event_type,
    query
FROM citus_stat_activity
WHERE state != 'idle' AND pid <> pg_backend_pid() AND state IN ('idle in transaction', 'active')
ORDER BY now() - query_start DESC;

查询速度缓慢

查询速度缓慢,消耗 CPU 资源并导致 CPU 使用率高。

以下查询可帮助你识别运行时间较长的查询:

SELECT
    query,
    calls,
    mean_exec_time,
    total_exec_time,
    rows,
    shared_blks_hit,
    shared_blks_read,
    shared_blks_dirtied,
    shared_blks_written,
    temp_blks_read,
    temp_blks_written,
    wal_records,
    wal_fpi,
    wal_bytes
FROM pg_stat_statements
WHERE query ILIKE '%select%' OR query ILIKE '%insert%' OR query ILIKE '%update%' OR query ILIKE '%delete%' OR queryid = <queryid>
ORDER BY total_exec_time DESC;

节点上按状态排序的连接总数和连接数

与数据库建立的许多连接会导致 CPU 使用率增加。

以下查询提供有关单个节点上按状态连接数的信息:

SELECT state, COUNT(*)
FROM pg_stat_activity
WHERE pid <> pg_backend_pid()
GROUP BY state
ORDER BY state ASC;

所有节点上按状态排序的连接总数和连接数

与数据库建立的许多连接会导致 CPU 使用率增加。

以下查询提供有关跨所有节点的状态连接数的信息:

SELECT state, COUNT(*)
FROM citus_stat_activity
WHERE pid <> pg_backend_pid()
GROUP BY state
ORDER BY state ASC;

清扫和表统计信息

使表统计信息保持最新状态有助于提高查询性能。 监控常规的自动autovacuum是否发生。

以下查询可帮助你识别需要清扫的表:

SELECT *
FROM run_command_on_all_nodes($$
  SELECT json_agg(t)
  FROM (
    SELECT schemaname, relname
    ,n_live_tup, n_dead_tup
    ,n_dead_tup / (n_live_tup) AS bloat
    ,last_autovacuum, last_autoanalyze
    ,last_vacuum, last_analyze
    FROM pg_stat_user_tables
    WHERE n_live_tup > 0 AND relname LIKE '%orders%'
    ORDER BY n_dead_tup DESC
  ) t
$$);

下图突出显示了上述查询的输出。 该 result 列是包含统计信息信息的 JSON 数据类型。

从查询响应返回的结果 - 包括“result”列作为 json 数据类型

last_autovacuumlast_autoanalyze提供表上次自动清理或分析的日期和时间。 如果表没有定期自动真空,请执行以下步骤以调整 autovacuum。

以下查询提供有关架构级别冗余量的信息:

SELECT *
FROM run_command_on_all_nodes($$
  SELECT json_agg(t) FROM (
    SELECT schemaname, sum(n_live_tup) AS live_tuples
    , sum(n_dead_tup) AS dead_tuples
    FROM pg_stat_user_tables
    WHERE n_live_tup > 0
    GROUP BY schemaname
    ORDER BY sum(n_dead_tup) DESC
  ) t
$$);

解决 CPU 利用率较高的问题

使用 EXPLAIN ANALYZE 检查任何缓慢查询并终止任何不当的长时间运行的事务。 请考虑使用内置的 PgBouncer 连接池器,并清除过多的膨胀,以解决 CPU 使用率过高的问题。

使用 EXPLAIN ANALYZE

确定使用更多 CPU 的查询后,请使用 EXPLAIN ANALYZE 进一步调查和优化它们。

有关 EXPLAIN ANALYZE 命令的详细信息,请参阅其 文档

终止节点上长时间运行的事务

如果事务运行时间超过预期,请考虑终止长时间运行的事务。

若要终止会话的 PID,请先使用以下查询查找 PID:

SELECT
    pid,
    datname,
    usename,
    application_name,
    client_addr,
    backend_start,
    query_start,
    now() - query_start AS duration,
    state,
    wait_event,
    wait_event_type,
    query
FROM pg_stat_activity WHERE state != 'idle' AND pid <> pg_backend_pid() AND state IN ('idle in transaction', 'active')
ORDER BY now() - query_start DESC;

还可以按其他属性进行筛选,比如 usename (用户名)、datname (数据库名称)等。

获取会话的 PID 后,使用以下查询终止该会话:

SELECT pg_terminate_backend(pid);

终止 PID 将结束与节点相关的特定会话。

终止所有节点上长时间运行的事务

请考虑结束长时间运行的事务。

若要终止会话的 PID,请使用以下查询查找其 PID 和global_pid:

SELECT
    global_pid,
    pid,
    nodeid,
    datname,
    usename,
    application_name,
    client_addr,
    backend_start,
    query_start,
    now() - query_start AS duration,
    state,
    wait_event,
    wait_event_type,
    query
FROM citus_stat_activity WHERE state != 'idle' AND pid <> pg_backend_pid() AND state IN ('idle in transaction', 'active')
ORDER BY now() - query_start DESC;

还可以按其他属性进行筛选,比如 usename (用户名)、datname (数据库名称)等。

获取会话的 PID 后,使用以下查询终止该会话:

SELECT pg_terminate_backend(pid);

终止 pid 将结束与工作器节点相关的特定会话。

在不同工作节点上运行的同一查询可能具有相同的global_pid。 在这种情况下,可以使用global_pid在所有工作器节点上结束长时间运行的事务。

以下屏幕截图显示了global_pid与会话pid之间的关系。

全局 pid 到会话 pid 引用示例

SELECT pg_terminate_backend(global_pid);

注释

若要终止长时间运行的事务,请设置服务器参数 statement_timeoutidle_in_transaction_session_timeout

清除冗余

短期解决方案是手动清空,然后分析出现慢查询的表:

VACUUM ANALYZE <table>;

管理连接

如果您的应用程序使用许多短时间连接或者许多在大部分时间里处于空闲状态的连接,请考虑使用像 PgBouncer 这样的连接池工具。

PgBouncer,一个内置连接池器

有关 PgBouncer 的详细信息,请参阅 连接池管理器PostgreSQL 连接管理最佳实践

Azure Database for PostgreSQL弹性群集提供 PgBouncer 作为内置连接池解决方案。 有关详细信息,请参阅 PgBouncer