如何排查 Amazon RDS 或 Amazon Aurora PostgreSQL 的高 CPU 利用率?

上次更新时间:2019 年 4 月 16 日

如何确定和解决 Amazon Relational Database Service (Amazon RDS) 或 Amazon Aurora PostgreSQL 中高 CPU 利用率的原因?

简短描述

如果您发现负载的 CPU 利用率很高,则可以使用以下工具的组合来确定 CPU 负载的原因:

  • Amazon CloudWatch 指标可帮助确定一段时间内的 CPU 使用模式。
  • 增强监控提供操作系统 (OS) 级别的视图,可帮助在粒度级别确定高 CPU 负载的原因。例如,您可以查看负载平均值、CPU 分配(system% 或 nice%)和操作系统进程列表。
  • 增强监控还可帮助确定微型突发的 CPU 使用情况。如果活动峰值或突发持续时间少于 60 秒,则 CloudWatch 可能会错过微型突发。增强监控可以确定这些峰值,因为您可以将监控配置为查看一秒钟的粒度。
  • 性能详情可帮助确定负责加载的确切查询以及启动查询的主机。
  • 本机 PostgreSQL 视图和目录(例如 pg_stat_statementspg_stat_activity pg_stat_user_tables)允许您查看数据库级详细信息。有关更多信息,请参阅有关监控数据库活动pg_stat_statements 的 PostgreSQL 文档。
  • PostgreSQL 提供各种日志记录参数来记录长时间运行的查询、autovacuum、锁定等待以及连接和断开连接请求。有关更多信息,请参阅如何使用 Amazon RDS for PostgreSQL 启用查询日志记录?

确定原因后,可以使用以下方法进一步降低 CPU 使用:

  • 如果有机会进行调整,请使用 EXPLAIN 和 EXPLAIN ANALYZE 来识别警告。有关更多信息,请参阅有关 EXPLAIN 的 PostgreSQL 文档。
  • 如果有重复运行的查询,请使用准备的语句来降低 CPU 的压力。

解决方法

Amazon CloudWatch 指标

您可以使用 CloudWatch 指标来确定长期的 CPU 模式。您可以比较 CloudWatch 指标以确定 CPU 使用最高的时间范围。确定时间范围后,您可以查看与数据库实例关联的增强监控数据。您可以将增强监控设置为以 1 秒、5 秒、10 秒、15 秒、30 秒或 60 秒的间隔收集数据,这样便可以比 CloudWatch 更精细的级别收集数据。有关更多信息,请参阅 CloudWatch 指标与增强监控指标的区别。

增强监控

通过使用增强监控,以 1 分钟、5 分钟和 15 分钟的间隔检查 loadAverageMinute 数据。如果负载平均值大于 vCPU 数量,这表示该实例负载很重。此外,如果负载平均值小于数据库实例类的 vCPU 数量,则应用程序延迟可能并非由 CPU 限流引起。检查负载平均值,以避免在诊断 CPU 使用原因时出现误报。

例如,如果您的数据库实例使用 db.m4.2xlarge 实例类且具有 3000 个预置 IOPS(达到 CPU 限制),则可查看以下示例指标来确定高 CPU 使用的根本原因。在以下示例中,实例类具有与之关联的八个 vCPU。对于相同的负载平均值,超过 170 表示在测量的时间范围内计算机处于重负载:

负载平均分钟
 
15 170.25
5 391.31
1 596.74
CPU 利用率  
用户 (%) 0.71
系统 (%) 4.9
Nice 值 (%) 93.92
总计 (%) 99.97

注意:您的工作负载优先于数据库实例上运行的其他任务。为了优先处理这些任务,为工作负载任务指定较高的 Nice 值。因此,在增强监控中,Nice 值 % 表示工作负载对数据库使用的 CPU 量。

启用增强监控后,还可检查与数据库实例关联的操作系统进程列表。增强监控仅显示前 50 个进程,但这可帮助您根据 CPU 和内存使用情况确定哪些进程对性能的影响最大。

性能详情

检查对应特定时间范围的 SQL 选项卡后,您可以使用 Amazon RDS 性能详情来确定负责数据库负载的查询。

本机 PostgreSQL 视图和目录

在数据库引擎级别,如果问题实时发生,您可以使用 pg_stat_activitypg_stat_statements。这些可帮助您对发送最多流量的计算机、客户端和 IP 地址进行分组。您还可以使用此数据来查看是否存在随时间增加、应用程序服务器增加或者应用程序服务器是否卡住会话或锁定问题的情况。有关更多信息,请参阅有关 pg_stat_activitypg_stat_statements 的 PostgreSQL 文档。要启用 pg_stat_statements请修改现有的自定义参数组并设置以下值:

  • shared_preload_libraries = pg_stat_statements
  • track_activity_query_size = 2048
  • pg_stat_statements.track = ALL
  • pg_stat_statements.max = 10000

选择立即应用,然后重启数据库实例。然后,在要监控的数据库上运行与以下内容类似的命令:

注意:以下示例在“demo”数据库中安装扩展。

demo=> select current_database();
current_database
------------------
demo
(1 row)

demo=> CREATE EXTENSION pg_stat_statements;

设置 pg_stat_statements 后,您可以使用以下方法之一监控输出:

  • 按 total_time 列出查询
  • 列出查询及调用总数、总行数和返回的行数
  • 基于每次执行列出查询

有关以下示例查询中各列的信息,请参阅有关 pg_stat_statements 的 PostgreSQL 文档。

total_time 列出查询,并查看哪个查询在数据库中花费的时间最多:

SELECT round(total_time*1000)/1000 AS total_time,query
FROM pg_stat_statements
ORDER BY total_time DESC limit 5;

示例输出:

benchmark=> SELECT round(total_time*1000)/1000 AS total_time,query 
FROM pg_stat_statements
ORDER BY total_time DESC limit 5;
-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
total_time | 50795183.089
query      | SELECT MD5((bbalance*random()*bid+?)::char) from pgbench_branches where (MD5((bid*random()*bbalance+?)::char))>substr(?,((random()*(?-?)+?)::integer),?) order by (bbalance*random()*bid+?) desc;
-[ RECORD 2 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
total_time | 37131467.568
query      | UPDATE pgbench_accounts SET abalance = abalance + ? WHERE aid = ?;
-[ RECORD 3 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
total_time | 27716213.981
query      | SELECT abalance FROM pgbench_accounts WHERE aid = ?;
-[ RECORD 4 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
total_time | 25529446.83
query      | UPDATE pgbench_branches SET bbalance = bbalance + ? WHERE bid = ?;
-[ RECORD 5 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
total_time | 5094847.077
query      | UPDATE pgbench_tellers SET tbalance = tbalance + ? WHERE tid = ?;

列出查询及调用总数、总行数和返回的行数:

SELECT query, calls, total_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_time DESC LIMIT 5;

示例输出:

benchmark=> SELECT query, calls, total_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_time DESC LIMIT 5;
-[ RECORD 1 ]--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
query       | SELECT MD5((bbalance*random()*bid+?)::char) from pgbench_branches where (MD5((bid*random()*bbalance+?)::char))>substr(?,((random()*(?-?)+?)::integer),?) order by (bbalance*random()*bid+?) desc;
calls       | 53855
total_time  | 50795183.089
rows        | 184638251
hit_percent | 99.9981448776361289
-[ RECORD 2 ]--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
query       | UPDATE pgbench_accounts SET abalance = abalance + ? WHERE aid = ?;
calls       | 100
total_time  | 37131467.568
rows        | 100
hit_percent | 99.0085070587482269
-[ RECORD 3 ]--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
query       | SELECT abalance FROM pgbench_accounts WHERE aid = ?;
calls       | 100
total_time  | 27716213.981
rows        | 100
hit_percent | 71.5744839710469907
-[ RECORD 4 ]--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
query       | UPDATE pgbench_branches SET bbalance = bbalance + ? WHERE bid = ?;
calls       | 4164050
total_time  | 25529446.8299978
rows        | 4164050
hit_percent | 99.9986231277663809
-[ RECORD 5 ]--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
query       | UPDATE pgbench_tellers SET tbalance = tbalance + ? WHERE tid = ?;
calls       | 4164070
total_time  | 5094847.07700154
rows        | 4164070
hit_percent | 99.9976317644971741

基于每次执行列出查询,以便随时间对查询进行抽样:

SELECT query, calls, total_time/calls as avg_time_ms, rows/calls as avg_rows,
temp_blks_read/calls as avg_tmp_read, temp_blks_written/calls as avg_temp_written
FROM pg_stat_statements
WHERE calls != 0
ORDER BY total_time DESC LIMIT 5;

示例输出:

benchmark=> SELECT query, calls, total_time/calls as avg_time_ms, rows/calls as avg_rows,
temp_blks_read/calls as avg_tmp_read, temp_blks_written/calls as avg_temp_written
FROM pg_stat_statements
WHERE calls != 0
ORDER BY total_time DESC LIMIT 5;
-[ RECORD 1 ]----+--------------------------------------------------------------
query            | SELECT MD5((bbalance*random()*bid+?)::char) from pgbench_branches where (MD5((bid*random()*bbalance+?)::char))>substr(?,((random()*(?-?)+?)::integer),?) order by (bbalance*random()*bid+?) desc;
calls            | 53855
avg_time_ms      | 943.184162826107
avg_rows         | 3428
avg_tmp_read     | 0
avg_temp_written | 0
-[ RECORD 2 ]----+--------------------------------------------------------------
query            | UPDATE pgbench_accounts SET abalance = abalance + ? WHERE aid = ?;
calls            | 100
avg_time_ms      | 371314.67568
avg_rows         | 1
avg_tmp_read     | 0
avg_temp_written | 0
-[ RECORD 3 ]----+--------------------------------------------------------------
query            | SELECT abalance FROM pgbench_accounts WHERE aid = ?;
calls            | 100
avg_time_ms      | 277162.13981
avg_rows         | 1
avg_tmp_read     | 0
avg_temp_written | 0
-[ RECORD 4 ]----+--------------------------------------------------------------
query            | UPDATE pgbench_branches SET bbalance = bbalance + ? WHERE bid = ?;
calls            | 4164050
avg_time_ms      | 6.13091745536144
avg_rows         | 1
avg_tmp_read     | 0
avg_temp_written | 0
-[ RECORD 5 ]----+--------------------------------------------------------------
query            | UPDATE pgbench_tellers SET tbalance = tbalance + ? WHERE tid = ?;
calls            | 4164070
avg_time_ms      | 1.22352579975878
avg_rows         | 1
avg_tmp_read     | 0
avg_temp_written | 0

在查看之前执行的示例输出后,以下查询被确定为高 CPU 利用率的来源:

SELECT MD5((bbalance*random()*bid+?)::char) from pgbench_branches where (MD5((bid*random()*bbalance+?)::char))>substr(?,((random()*(?-?)+?)::integer),?) order by (bbalance*random()*bid+?) desc;

在输出中,此查询具有最高的总执行时间。此查询执行了 53855 次,每次执行的平均时间为 943 毫秒。

PostgreSQL 日志记录参数

使用 Amazon RDS for PostgreSQL 启用查询日志记录。然后,检查 PostgreSQL 错误日志,确认您的 log_min_duration_statementlog_statement 参数已设置为适当的值。有关更多信息,请参阅有关错误报告和日志记录的 PostgreSQL 文档。


这篇文章对您有帮助吗?

您觉得我们哪些地方需要改进?


需要更多帮助?