Get Hands-on with Amazon EKS - Workshop Event Series
Whether you're taking your first steps with Kubernetes or you're an experienced practitioner looking to sharpen your skills, our Amazon EKS workshop series delivers practical, real-world experience that moves you forward. Learn directly from AWS solutions architects and EKS specialists through hands-on sessions designed to build your confidence with Kubernetes. Register now and start building with Amazon EKS!
How do I improve the performance of long-running queries for my Amazon RDS for PostgreSQL or Amazon Aurora DB instance?
I want to identify, monitor, and improve the performance of slow-running queries in my Amazon Relational Database Service (Amazon RDS) for PostgreSQL or Amazon Aurora PostgreSQL-Compatible Edition database (DB) instance.
Resolution
Identify long-running queries
To use the pg_stat_activity view to list queries that are running for more than 5 minutes, run the following query:
SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state, usename, datname, client_addr FROM pg_stat_activity WHERE state = 'active' AND now() - pg_stat_activity.query_start > interval '5 minutes' ORDER BY duration DESC;
Note: The preceding query returns the duration, query text, user context, and process ID that's required to shut down the instance.
To use the pg_locks view to identify potential blocking scenarios, run the following query:
SELECT blocked_locks.pid AS blocked_pid, blocked_activity.usename AS blocked_user, blocking_locks.pid AS blocking_pid, blocking_activity.usename AS blocking_user, blocked_activity.query AS blocked_statement, blocking_activity.query AS blocking_statement, now() - blocked_activity.query_start AS blocked_duration FROM pg_catalog.pg_locks blocked_locks JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid WHERE NOT blocked_locks.granted;
If you have nested locks, then run the following query to identify potential blocking scenarios instead:
WITH lok AS ( SELECT array_cat(array_agg(pid), array_agg((pg_blocking_pids(pid))[array_length(pg_blocking_pids(pid),1)])) pids FROM pg_locks WHERE NOT granted ) SELECT p.pid, p.usename, p.datname, p.state, p.wait_event_type || ': ' || p.wait_event AS wait_event, current_timestamp-p.state_change time_in_state, current_timestamp-p.xact_start time_in_xact, l.relation::regclass relname, l.locktype, l.mode, l.page, l.tuple, pg_blocking_pids(l.pid) blocking_pids, (pg_blocking_pids(l.pid))[array_length(pg_blocking_pids(l.pid),1)] last_session, coalesce((pg_blocking_pids(l.pid))[1]||'.'||coalesce(case when locktype='transactionid' then 1 else array_length(pg_blocking_pids(l.pid),1)+1 end,0),p.pid||'.0') lock_depth, p.query FROM pg_stat_activity p JOIN lok s on (p.pid = any(s.pids)) LEFT OUTER JOIN pg_locks l on (p.pid = l.pid and not l.granted) ORDER BY lock_depth;
Monitor long-running queries
Complete the following steps:
-
Run the following query to create the pg_stat_statements extension in your database:
CREATE EXTENSION pg_stat_statements; -
Run the following query to get aggregate statistics on query performance:
SELECT query, calls, total_time, mean_time, rows FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10;Note: The preceding query shows the top 10 queries by total execution time, including the number of calls, average time for each call, and affected rows.
-
Use Performance Insights to monitor DB load and identify performance bottlenecks.
Note: On the Performance Insights dashboard, you can filter and group loads by waits, SQL statements, hosts, or users. -
Set up Enhanced Monitoring to get metrics on your DB instance's resource usage, processes, and operating system (OS).
-
Create an Amazon CloudWatch alarm so that you can get automated alerts and trend analyses.
Note: To monitor performance, use CloudWatch metrics such as DatabaseConnections, CPUUtilization, FreeableMemory, ReadLatency, WriteLatency, Read/Write IOPS, and Read/Write Throughput.
Important: Performance Insights will reach its end of life on June 30, 2026. You can upgrade to the Advanced mode of Database insights before June 30, 2026. If you don't upgrade, then DB clusters that use Performance Insights will default to the Standard mode of Database Insights. Only the Advanced mode of Database Insights will support execution plans and on-demand analysis. If your clusters default to the Standard mode, then you might not be able to use these features on the console. To turn on the Advanced mode, see Turning on the Advanced mode of Database Insights for Amazon RDS and Turning on the Advanced mode of Database Insights for Amazon Aurora.
Use query logging to find slow performing queries
Turn on database query logging. Then, configure database parameters, such as log_min_duration_statement, log_statement, and log_lock_waits, to capture queries, lock waits, checkpoints, and other database operations.
Set up auto_explain monitoring
Use auto_explain to automate execution plan logging for slow queries. For more information, see auto_explain — log execution plans of slow queries on the PostgreSQL website.
Improve the performance of long-running queries
Complete the following steps:
To analyze your query plans and print the query execution plan, run the following EXPLAIN query:
EXPLAIN ANALYZE SELECT * FROM your_table WHERE condition;
Note: To identify inefficient operations, review the output that shows how PostgreSQL plans to run the query.
To improve the performance of long-running queries, take the following actions:
- Based on the EXPLAIN output, create indexes on columns that you frequently use in WHERE clauses, JOIN conditions, and ORDER BY clauses.
- Verify that JOIN operations are performed on indexed columns, and denormalize data to reduce the use of complex JOIN operations.
- For tables with millions of rows, use table partitioning to improve query performance.
- Regularly run ANALYZE and VACUUM operations on your tables so that the query planner has up-to-date statistics and the dead tuples are removed.
- Use materialized views for complex queries that you frequently run but don't need real-time data.
- Use appropriate data types for your columns to minimize storage and improve query performance.
- Simplify complex queries, or use Common Table Expressions (CTEs) to improve query readability and performance.
Important: Regularly monitor and tune your database performance, and review and reduce your most resource-intensive queries.
Set up hints
Install the pg_hint_plan extension
Complete the following steps:
- Add pg_hint_plan to the shared_preload_libraries parameter of your database parameter group. Use the pg_hint_plan extension to modify the execution plans of your queries. For more information, see pg_hint_plan on the GitHub website.
- Create the pg_hint_plan extension in your database.
Add the hints as comments in the format, /*+ hint */. When you use the hints to specify join methods, scan methods, and planner parameters, you don't alter the core SQL syntax. You can troubleshoot performance, test query behavior, or apply temporary workarounds in production environments.
Edit the database parameter group
Complete the following steps:
-
Open the Aurora and RDS console.
-
In the navigation pane, choose Parameter groups, and then select the parameter group of your DB instance.
-
Choose Actions, and then choose Edit.
-
In the search box, enter shared_preload_libraries.
-
In the Values field, add pg_hint_plan to the list. Use a comma to separate items in the list of values.
-
Chose Save changes.
-
Restart your DB Instance.
-
To verify that pg_hint_plan initialized after your instance is available, run the following command to connect to your RDS for PostgreSQL instance:
SHOW shared_preload_libraries; -
After pg_hint_plan initializes, create the pg_hint_plan extension:
CREATE EXTENSION pg_hint_plan;Note: To identify available pg_hint_plan versions, use the pg_available_extensions or pg_available_extension_versions system views.
If you can't edit the query, then create a hint table. For more information, see The hint table on the GitHub website. The extension owner owns the hint table and has the same default permissions as during CREATE EXTENSION. Hints in the hint table take priority over the hints that you specify in comments.
Example hint_plan.hints table:
postgres=# SHOW pg_hint_plan.enable_hint; pg_hint_plan.enable_hint on (1 row) postgres=# SHOW pg_hint_plan.enable_hint_table; pg_hint_plan.enable_hint_table on (1 row) -- hint_plan.hints defines the hint of using SeqScan(account) when executing EXPLAIN (COSTS false) SELECT * FROM account WHERE account.id = 50 postgres=# INSERT INTO hint_plan.hints(norm_query_string, application_name, hints) VALUES('EXPLAIN (COSTS false) SELECT * FROM account WHERE account.id = ?;', '', 'SeqScan(account)'); INSERT 0 1 postgres=# update hint_plan.hints set hints='IndexScan(account a_pk)' where id=1; UPDATE 1
Note: The hint in the preceding example applies to sessions that you connect to from the psql command line interface (CLI). An empty string is a session with an application_name.
To retrieve the query ID, use pg_stat_statements or EXPLAIN VERBOSE.
Related information
Best practices for working with PostgreSQL
Performance tips on the PostgreSQL website
- Language
- English

Relevant content
- Accepted Answerasked 2 years ago
- Accepted Answerasked a year ago
- asked 4 months ago