Updated and added more tips to the examining query performance docs

This commit is contained in:
Kevin Brolly committed 2023-02-21 11:55:41 +00:00
1 parent ac0bec65e3
commit ec286b138c
1 file changed
+146 -27
+146 -27
View File
@@ -8,6 +8,152 @@ export const meta = {
The Supabase platform automatically optimizes your Postgres database to take advantage of the compute resources of the tier your project is on. However, these optimizations are based on assumptions about the type of workflow the project is being utilized for, and it is likely that better results can be obtained by tuning the database for your particular workflow.
## Examining Query Performance
A major cause of poor database performance is unoptimized queries. The techniques on this page can help you identify and understand queries that take the most time and resources from your database.
Database performance is a large topic and many factors can contribute, but some of the most common causes of poor performance include:
* An inefficiently designed schema
* Inefficiently designed queries
* A lack of indexes causing slower than required queries over large tables
* Unused indexes causing slow `INSERT`, `UPDATE` and `DELETE` operations
* Not enough compute resources, such as memory, causing your database to go to disk for results too often
* Lock contention from multiple queries operating on highly utilized tables
* Large amount of bloat on your tables causing poor query planning
Thankfully there are solutions to all these issues, which we will cover in the following sections.
### Postgres Cumulative Statistics system
Postgres itself collects lots of data about its own operations using the [cumulative statistics system](https://www.postgresql.org/docs/current/monitoring-stats.html). In addition to this, every Supabase project has [the pg_stat_statements extension](https://www.postgresql.org/docs/14/pgstatstatements.html) enabled by default. This extension records query execution performance details and is the best way to find queries that take the most time to execute. This information can be combined with the Postgres query plan analyzer to develop more efficient queries.
It is easy to use `pg_stat_statements` and other Postgres statistics tables to get a wealth of information about your database. Here are some example queries to get you started.
Most frequently called queries:
```sql
select
auth.rolname,
statements.query,
statements.calls,
-- -- Postgres 13, 14, 15
statements.total_exec_time + statements.total_plan_time as total_time,
statements.min_exec_time + statements.min_plan_time as min_time,
statements.max_exec_time + statements.max_plan_time as max_time,
statements.mean_exec_time + statements.mean_plan_time as mean_time,
-- -- Postgres <= 12
-- total_time,
-- min_time,
-- max_time,
-- mean_time,
statements.rows / statements.calls as avg_rows
from pg_stat_statements as statements
inner join pg_authid as auth on statements.userid = auth.oid
order by
statements.calls desc
limit
100;
```
This query will show you statistics about queries ordered by the number of times they have been called. It shows the role that ran the query, the number of times it has been called, the average number of rows returned, the cumulative total time the query has spent running as well as the min, max and mean query times.
This can provide useful information about queries that you are running often. You should try and ensure that any queries that are being called often are performant and have good mean exeuction times. Queries which are showing as having high max or mean times and are being called often can be good candidates for optimisation.
Slowest queries by execution time:
```sql
select
auth.rolname,
statements.query,
statements.calls,
-- -- Postgres 13, 14, 15
statements.total_exec_time + statements.total_plan_time as total_time,
statements.min_exec_time + statements.min_plan_time as min_time,
statements.max_exec_time + statements.max_plan_time as max_time,
statements.mean_exec_time + statements.mean_plan_time as mean_time,
-- -- Postgres <= 12
-- total_time,
-- min_time,
-- max_time,
-- mean_time,
statements.rows / statements.calls as avg_rows
from pg_stat_statements as statements
inner join pg_authid as auth on statements.userid = auth.oid
order by
max_time desc
limit
100;
```
This query will show you statistics about queries ordered by the maximum execution time. It is similar to the query above ordered by calls, but this one highlights outliers that may have high executions times. Queries which have high or mean execution times are good candidates for optimisation.
Most time consuming queries:
```sql
select
auth.rolname,
statements.query,
statements.calls,
statements.total_exec_time + statements.total_plan_time as total_time,
to_char(((statements.total_exec_time + statements.total_plan_time)/sum(statements.total_exec_time + statements.total_plan_time) OVER()) * 100, 'FM90D0') || '%' AS prop_total_time
from pg_stat_statements as statements
inner join pg_authid as auth on statements.userid = auth.oid
order by
total_time desc
limit
100;
```
This query will show you statistics about queries ordered by the cumulative total execution time. It shows the total time the query has spent running as well as the proportion of total execution time the query has taken up.
Queries which are the most time consuming are not necessarily bad, you may have a very effiecient and frequently ran queries that end up taking a large total % time, but it can be useful to help spot queries that are taking up more time than they should.
### Hit rate
Generally for most applications a small percentage of data is accessed more regularly than the rest. To make sure that your regularly accessed data is available, Postgres tracks your data access patterns and keeps this in its [shared_buffers](https://www.postgresql.org/docs/15/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY) cache.
Applications with lower cache hit rates generally perform more poorly since they have to hit the disk to get results rather than serving them from memory. Very poor hit rates can also cause you to burst past your [Disk I/O limits](https://supabase.com/docs/guides/platform/compute-add-ons#disk-io-bandwidth) causing significat performance issues.
You can view your cache and index hit rate by executing the following query:
```
select
'index hit rate' as name,
(sum(idx_blks_hit)) / nullif(sum(idx_blks_hit + idx_blks_read),0) * 100 as ratio
from pg_statio_user_indexes
union all
select
'table hit rate' as name,
sum(heap_blks_hit) / nullif(sum(heap_blks_hit) + sum(heap_blks_read),0) * 100 as ratio
from pg_statio_user_tables;
```
This shows the ratio of data blocks fetched from the Postgres [shared_buffers](https://www.postgresql.org/docs/15/runtime-config-resource.html#RUNTIME-CONFIG-RESOURCE-MEMORY) cache against the data blocks that were read from disk/OS cache.
If either of your index or table hit rate are <99% then this can indicate your compute plan is too small for your current workload and you would benefit from more memory. [Upgrading your compute](https://supabase.com/docs/guides/platform/compute-add-ons) is easy and can be done from your [project dashboard](https://app.supabase.com/project/_/settings/billing/subscription).
### Optimizing poor performing queries
Postgres has built in tooling to help you optimize poorly performing queries. You can use the [query plan analyzer](https://www.postgresql.org/docs/current/sql-explain.html) on any expensive queries that you have identified:
```sql
explain analyze <query-statement-here>;
```
Be careful using `explain analyze` with `insert`/`update`/`delete` queries, because the query will actually run, and could have unintended side-effects.
Using the query plan analyzer to optimize your queries is a large topic, with a number of online resources available:
- [Official docs.](https://www.postgresql.org/docs/current/using-explain.html)
- [The Art of PostgreSQL.](https://theartofpostgresql.com/explain-plan-visualizer/)
- [Postgres Wiki.](https://wiki.postgresql.org/wiki/Using_EXPLAIN)
- [Enterprise DB.](https://www.enterprisedb.com/blog/postgresql-query-optimization-performance-tuning-with-explain-analyze)
You can pair the information available from `pg_stat_statements` with the detailed system metrics available [via your metrics endpoint](../platform/metrics) to better understand the behavior of your DB and the queries you're executing against it.
## Optimizing the number of connections
By default, the number of connections allowed to Postgres and PgBouncer is configured based on the resources available to the database.
@@ -64,33 +210,6 @@ alter system reset <other-overridden-conf>;
Configuring the number of PgBouncer connections is not supported at this time.
## Examining Query Performance
Every Supabase project has [the pg_stat_statements extension](https://www.postgresql.org/docs/14/pgstatstatements.html) enabled by default. This extension records query execution performance details and is the best way to find queries that take the most time to execute. This information can be combined with the Postgres query plan analyzer to develop more efficient queries.
Obtaining information from pg_stat_statements:
```sql
select mean_exec_time + stddev_exec_time, * from pg_stat_statements order by 1 desc;
```
Using the query plan analyzer on your expensive queries:
```sql
explain analyze <query-statement-here>;
```
Be careful using `explain analyze` with `insert`/`update`/`delete` queries, because the query will actually run, and could have unintended side-effects.
Using the query plan analyzer to optimize your queries is a large topic, with a number of online resources available:
- [Official docs.](https://www.postgresql.org/docs/current/using-explain.html)
- [The Art of PostgreSQL.](https://theartofpostgresql.com/explain-plan-visualizer/)
- [Postgres Wiki.](https://wiki.postgresql.org/wiki/Using_EXPLAIN)
- [Enterprise DB.](https://www.enterprisedb.com/blog/postgresql-query-optimization-performance-tuning-with-explain-analyze)
You can pair the information available from `pg_stat_statements` with the detailed system metrics available [via your metrics endpoint](../platform/metrics) to better understand the behavior of your DB and the queries you're executing against it.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page