Ao trabalhar com bancos de dados OLTP (OnLine Transaction Processing), o desempenho da consulta é fundamental, pois afeta diretamente a experiência do usuário. Consultas lentas significam que o aplicativo parece lento e sem resposta e isso resulta em taxas de conversão ruins, usuários insatisfeitos e todos os conjuntos de problemas.
OLTP é um dos casos de uso comuns do PostgreSQL, portanto, você deseja que suas consultas sejam executadas o mais suavemente possível. Neste blog, gostaríamos de falar sobre como você pode identificar problemas com consultas lentas no PostgreSQL.
Compreendendo o registro lento
De um modo geral, a maneira mais comum de identificar problemas de desempenho com o PostgreSQL é coletar consultas lentas. Existem algumas maneiras de fazer isso. Primeiro, você pode habilitá-lo em um único banco de dados:
pgbench=# ALTER DATABASE pgbench SET log_min_duration_statement=0;
ALTER DATABASE
Após isso, todas as novas conexões com o banco de dados ‘pgbench’ serão registradas no log do PostgreSQL.
Também é possível habilitar isso globalmente adicionando:
log_min_duration_statement = 0
para a configuração do PostgreSQL e recarregue a configuração:
pgbench=# SELECT pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)
Isso permite o registro de todas as consultas em todos os bancos de dados em seu PostgreSQL. Se você não vir nenhum log, convém habilitar logging_collector =on também. Os logs incluirão todo o tráfego que chega às tabelas do sistema PostgreSQL, tornando-o mais ruidoso. Para nossos propósitos, vamos nos ater ao registro em nível de banco de dados.
O que você verá no log são as entradas abaixo:
2020-02-21 09:45:39.022 UTC [13542] LOG: duration: 0.145 ms statement: SELECT abalance FROM pgbench_accounts WHERE aid = 29817899;
2020-02-21 09:45:39.022 UTC [13544] LOG: duration: 0.107 ms statement: SELECT abalance FROM pgbench_accounts WHERE aid = 11782597;
2020-02-21 09:45:39.022 UTC [13529] LOG: duration: 0.065 ms statement: SELECT abalance FROM pgbench_accounts WHERE aid = 16318529;
2020-02-21 09:45:39.022 UTC [13529] LOG: duration: 0.082 ms statement: UPDATE pgbench_tellers SET tbalance = tbalance + 3063 WHERE tid = 3244;
2020-02-21 09:45:39.022 UTC [13526] LOG: duration: 16.450 ms statement: UPDATE pgbench_branches SET bbalance = bbalance + 1359 WHERE bid = 195;
2020-02-21 09:45:39.023 UTC [13523] LOG: duration: 15.824 ms statement: UPDATE pgbench_accounts SET abalance = abalance + -3726 WHERE aid = 5290358;
2020-02-21 09:45:39.023 UTC [13542] LOG: duration: 0.107 ms statement: UPDATE pgbench_tellers SET tbalance = tbalance + -2716 WHERE tid = 1794;
2020-02-21 09:45:39.024 UTC [13544] LOG: duration: 0.112 ms statement: UPDATE pgbench_tellers SET tbalance = tbalance + -3814 WHERE tid = 278;
2020-02-21 09:45:39.024 UTC [13526] LOG: duration: 0.060 ms statement: INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (4876, 195, 39955137, 1359, CURRENT_TIMESTAMP);
2020-02-21 09:45:39.024 UTC [13529] LOG: duration: 0.081 ms statement: UPDATE pgbench_branches SET bbalance = bbalance + 3063 WHERE bid = 369;
2020-02-21 09:45:39.024 UTC [13523] LOG: duration: 0.063 ms statement: SELECT abalance FROM pgbench_accounts WHERE aid = 5290358;
2020-02-21 09:45:39.024 UTC [13542] LOG: duration: 0.100 ms statement: UPDATE pgbench_branches SET bbalance = bbalance + -2716 WHERE bid = 210;
2020-02-21 09:45:39.026 UTC [13523] LOG: duration: 0.092 ms statement: UPDATE pgbench_tellers SET tbalance = tbalance + -3726 WHERE tid = 67;
2020-02-21 09:45:39.026 UTC [13529] LOG: duration: 0.090 ms statement: INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (3244, 369, 16318529, 3063, CURRENT_TIMESTAMP);
Você pode ver informações sobre a consulta e sua duração. Não muito mais, mas é definitivamente um bom lugar para começar. A principal coisa a ter em mente é que nem toda consulta lenta é um problema. Às vezes, as consultas precisam acessar uma quantidade significativa de dados e espera-se que demorem mais para acessar e analisar todas as informações solicitadas pelo usuário. Outra questão é o que significa “lento”? Isso depende principalmente da aplicação. Se estamos falando de aplicativos interativos, provavelmente algo mais lento que um segundo é perceptível. Idealmente, tudo é executado dentro do limite de 100 - 200 milissegundos.
Desenvolvendo um plano de execução de consulta
Uma vez que determinamos que determinada consulta é realmente algo que queremos melhorar, devemos dar uma olhada no plano de execução da consulta. Em primeiro lugar, pode acontecer que não haja nada que possamos fazer sobre isso e teremos que aceitar que determinada consulta é apenas lenta. Em segundo lugar, os planos de execução de consultas podem mudar. Os otimizadores sempre tentam escolher o plano de execução mais ideal, mas tomam suas decisões com base apenas em uma amostra de dados, portanto, pode acontecer que o plano de execução da consulta mude com o tempo. No PostgreSQL você pode verificar o plano de execução de duas maneiras. Primeiro, o plano de execução estimado, usando EXPLAIN:
pgbench=# EXPLAIN SELECT abalance FROM pgbench_accounts WHERE aid = 5290358;
QUERY PLAN
----------------------------------------------------------------------------------------------
Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.56..8.58 rows=1 width=4)
Index Cond: (aid = 5290358)
Como você pode ver, espera-se que acessemos os dados usando a pesquisa de chave primária. Se quisermos verificar como exatamente a consulta será executada, podemos usar EXPLAIN ANALYZE:
pgbench=# EXPLAIN ANALYZE SELECT abalance FROM pgbench_accounts WHERE aid = 5290358;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------
Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.56..8.58 rows=1 width=4) (actual time=0.046..0.065 rows=1 loops=1)
Index Cond: (aid = 5290358)
Planning time: 0.053 ms
Execution time: 0.084 ms
(4 rows)
Agora, o PostgreSQL executou esta consulta e pode nos dizer não apenas as estimativas, mas os números exatos quando se trata do plano de execução, número de linhas acessadas e assim por diante. Lembre-se de que registrar todas as consultas pode se tornar uma sobrecarga séria em seu sistema. Você também deve ficar de olho nos logs e garantir que eles sejam girados corretamente.
Pg_stat_statements
Pg_stat_statements é a extensão que coleta estatísticas de execução para diferentes tipos de consulta.
pgbench=# select query, calls, total_time, min_time, max_time, mean_time, stddev_time, rows from public.pg_stat_statements order by calls desc LIMIT 10;
query | calls | total_time | min_time | max_time | mean_time | stddev_time | rows
------------------------------------------------------------------------------------------------------+-------+------------------+----------+------------+---------------------+---------------------+-------
UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2 | 30437 | 6636.83641200002 | 0.006533 | 83.832148 | 0.218051595492329 | 1.84977058799388 | 30437
BEGIN | 30437 | 231.095600000001 | 0.000205 | 20.260355 | 0.00759258796859083 | 0.26671126085716 | 0
END | 30437 | 229.483213999999 | 0.000211 | 16.980678 | 0.0075396134310215 | 0.223837608828596 | 0
UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2 | 30437 | 290021.784321001 | 0.019568 | 805.171845 | 9.52859297305914 | 13.6632712046825 | 30437
UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2 | 30437 | 6667.27243200002 | 0.00732 | 212.479269 | 0.219051563294674 | 2.13585110968012 | 30437
SELECT abalance FROM pgbench_accounts WHERE aid = $1 | 30437 | 3702.19730600006 | 0.00627 | 38.860846 | 0.121634763807208 | 1.07735927551245 | 30437
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP) | 30437 | 2349.22475800002 | 0.003218 | 61.372127 | 0.0771831901304325 | 0.971590327400244 | 30437
SELECT $1 | 6847 | 60.785467 | 0.002321 | 7.882384 | 0.00887767883744706 | 0.105198744982906 | 6847
insert into pgbench_tellers(tid,bid,tbalance) values ($1,$2,$3) | 5000 | 18.592042 | 0.001572 | 0.741427 | 0.0037184084 | 0.0137660355678027 | 5000
insert into pgbench_tellers(tid,bid,tbalance) values ($1,$2,$3) | 3000 | 7.323788 | 0.001598 | 0.40152 | 0.00244126266666667 | 0.00834442591085048 | 3000
(10 rows)
Como você pode ver nos dados acima, temos uma lista de diferentes consultas e informações sobre seus tempos de execução - isso é apenas uma parte dos dados que você pode ver nos pg_stat_statements, mas é suficiente para entendermos que nossa pesquisa de chave primária às vezes leva quase 39 segundos para ser concluída - isso não parece bom e é definitivamente algo que queremos investigar.
Se você não tiver pg_stat_statements ativado, você pode fazer isso de maneira padrão. Seja via arquivo de configuração e
shared_preload_libraries = 'pg_stat_statements'
Ou você pode habilitá-lo via linha de comando do PostgreSQL:
pgbench=# CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION
Usando ClusterControl para eliminar consultas lentas
Se você usar o ClusterControl para gerenciar seu banco de dados PostgreSQL, poderá usá-lo para coletar dados sobre consultas lentas.
Como você pode ver, ele coleta dados sobre execução de consultas - linhas enviadas e examinados, estatísticas de tempo de execução e assim por diante. Com ele, você pode identificar facilmente as consultas mais caras e ver como são os tempos médio e máximo de execução. Por padrão, o ClusterControl coleta consultas que demoraram mais de 0,5 segundo para serem concluídas, você pode alterar isso nas configurações:
Conclusão
Este pequeno blog não cobre todos os aspectos e ferramentas úteis para identificar e resolver problemas de desempenho de consultas no PostgreSQL. Esperamos que seja um bom começo e que ajude você a entender o que pode fazer para identificar a causa raiz das consultas lentas.