Amazon RDS 또는 Amazon Aurora PostgreSQL의 높은 CPU 사용률 문제를 해결하려면 어떻게 해야 합니까?

최종 업데이트 날짜: 2019년 4월 16일

Amazon Relational Database Service(Amazon RDS) 또는 Amazon Aurora PostgreSQL에서 높은 CPU 사용률을 유발시키는 원인을 식별하고 이를 해결하려면 어떻게 해야 합니까?

간략한 설명

로드에서 CPU 사용률이 높으면 다음 도구를 조합해 사용하여 CPU 로드의 원인을 식별할 수 있습니다.

  • Amazon CloudWatch 지표를 사용하면 시간 경과에 따른 CPU 사용량 패턴을 식별할 수 있습니다.
  • Enhanced Monitoring에서는 OS(운영 체제) 수준에서의 보기를 제공하며, 이를 통해 세분화된 수준에서 높은 CPU 로드의 원인을 식별할 수 있습니다. 예를 들어, 로드 평균, CPU 분산(system% 또는 nice%) 및 OS 프로세스 목록을 검토할 수 있습니다.
  • Enhanced Monitoring은 CPU 사용량의 마이크로 버스트도 식별하는 데 도움이 됩니다. 활동의 스파이크 또는 버스트가 60초 미만의 기간에 발생하면 CloudWatch에서 마이크로 버스트를 놓칠 수 있습니다. Enhanced Monitoring에서는 1초 단위로 세분화하여 보도록 모니터링을 구성할 수 있으므로 이러한 스파이크를 식별할 수 있습니다.
  • 성능 개선 도우미에서는 쿼리를 시작한 호스트와 로드를 책임지는 정확한 쿼리를 식별하는 데 도움이 됩니다.
  • 네이티브 PostgreSQL 보기 및 카탈로그(예: pg_stat_statements, pg_stat_activitypg_stat_user_tables)에서는 데이터베이스 수준의 세부 정보를 검토할 수 있습니다. 자세한 내용은 Monitoring Database Activitypg_stat_statements에 대한 PostgreSQL 설명서를 참조하십시오.
  • PostgreSQL에서는 장기 실행 쿼리, autovacuum, 잠금 대기 연결 및 연결 해제 요청을 기록하도록 다양한 로깅 파라미터를 제공합니다. 자세한 내용은 Amazon RDS for PostgreSQL을 사용하여 쿼리 로깅을 활성화하려면 어떻게 해야 합니까?를 참조하십시오.

원인을 식별한 후에는 다음 방법을 사용하여 CPU 사용량을 더 내릴 수 있습니다.

  • 조정 가능성이 있으면 EXPLAIN 및 EXPLAIN ANALYZE를 사용하여 주의 사항을 식별합니다. 자세한 내용은 EXPLAIN에 대한 PostgreSQL 문서를 참조하십시오.
  • 반복해서 실행하는 쿼리가 있는 경우 prepared 명령문을 사용하여 CPU의 부담을 줄입니다.

​해결 방법

Amazon CloudWatch 지표

CloudWatch 지표를 사용하여 오랫동안 CPU 패턴을 식별할 수 있습니다. CloudWatch 지표를 비교하여 CPU 사용량이 가장 높은 시간대를 식별할 수 있습니다. 시간대를 식별한 후에 DB 인스턴스에 연결된 Enhanced Monitoring 데이터를 검토할 수 있습니다. 1초, 5초, 10초, 15초, 30초 또는 60초 간격으로 데이터를 수집하도록 Enhanced Monitoring을 설정할 수 있습니다. 그러면 CloudWatch보다 더 세분화하여 데이터를 수집할 수 있습니다. 자세한 내용은 CloudWatch 측정치와 Enhanced Monitoring 측정치의 차이점을 참조하십시오.

Enhanced Monitoring

Enhanced Monitoring을 사용하여 1분, 5분, 15분 간격으로 loadAverageMinute 데이터를 확인할 수 있습니다. 로드 평균이 vCPU 수보다 크면 인스턴스에 로드가 많음을 나타냅니다. 또한 로드 평균이 DB 인스턴스 클래스의 vCPU 수보다 작으면 CPU 제한으로 인해 애플리케이션 지연 시간이 발생한 것이 아닐 수 있습니다. 로드 평균을 확인하여 CPU 사용량 원인을 진단할 때 거짓 긍정을 방지합니다.

예를 들어, 프로비저닝된 IOPS가 3,000개인 db.m4.2xlarge 인스턴스 클래스를 사용하는 DB 인스턴스에서 CPU 제한에 도달한 경우 다음 예제 지표를 검토하여 높은 CPU 사용량의 근본 원인을 식별할 수 있습니다. 다음 예제에서 인스턴스 클래스에 8개 vCPU가 연결되어 있습니다. 동일한 로드 평균에서, 170을 초과하는 경우 측정된 시간대에 컴퓨터에 로드가 많음을 나타냅니다.

로드 평균 시간(분)
 
15 170.25
5 391.31
1 596.74
CPU 사용률  
User(%) 0.71
System(%) 4.9
Nice(%) 93.92
Total(%) 99.97

참고: 워크로드에는 DB 인스턴스에서 실행 중인 다른 작업보다 더 높은 우선순위가 지정됩니다. 이러한 작업의 우선순위를 지정하기 위해 Nice 값보다 더 큰 값을 워크로드 작업에 지정합니다. 그러면 Enhanced Monitoring에서 Nice%는 데이터베이스에서 워크로드가 사용 중인 CPU 크기를 나타냅니다.

Enhanced Monitoring을 활성화한 후에 DB 인스턴스에 연결된 OS 프로세스 목록을 확인할 수도 있습니다. Enhanced Monitoring에서는 상위 50개 프로세스만 표시하지만, 이를 통해 CPU 및 메모리 사용량을 기준으로 성능에 가장 큰 영향을 주는 프로세스를 식별할 수 있습니다.

성능 개선 도우미

Amazon RDS 성능 개선 도우미를 사용하여 특정 시간대에 대응하는 SQL 탭을 확인한 후 데이터베이스 로드를 담당하는 쿼리를 식별할 수 있습니다.

네이티브 PostgreSQL 보기 및 카탈로그

데이터베이스 엔진 수준에서, 실시간으로 문제가 발생하면 pg_stat_activity 또는 pg_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

[즉시 적용]을 선택하고 DB 인스턴스를 재부팅합니다. 그런 다음 모니터링하려는 데이터베이스에서 다음과 비슷한 명령을 실행합니다.

참고: 다음 예제에서는 "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;

출력에서 이 쿼리는 총 실행 시간이 가장 길게 나타났습니다. 이 쿼리는 53,855회 실행되었고, 각 실행은 평균적으로 943밀리초가 걸렸습니다.

PostgreSQL 로깅 파라미터

Amazon RDS for PostgreSQL을 사용하여 쿼리 로깅을 활성화합니다. 그런 다음, PostgreSQL 오류 로그를 확인하고 log_min_duration_statementlog_statement 파라미터가 적절한 값으로 설정되었는지 확인합니다. 자세한 내용은 Error Reporting and Logging에 대한 PostgreSQL 설명서를 참조하십시오.


이 문서가 도움이 되었습니까?

AWS에서 개선해야 할 부분이 있습니까?


도움이 필요하십니까?